import os import sys from math import pi, sin, tanh from keyboard import setup_term from pyalsa import Alsa SAMPLE_RATE = 44_100 LATENCY = 50_000 # 50ms INT16_MAX = 2**15 - 1 alsa = Alsa(SAMPLE_RATE, LATENCY) keys = "azertyuiop" gain = 0.5 base_freq = 440.0 freqs = [0.0] * len(keys) phases = [0.0] * len(keys) mix_mode = "voice_count" buf_size = 512 def tanh_mix(voices, gain): sample = tanh(gain * sum(voices)) # apply tanh with gain (soft clipping) return sample def voice_count_mix(voices, gain): sample = sum(voices) / len(voices) # normalize to [0, 1] range sample = sample * gain return sample def clamp(x, a, b): return max(a, min(b, x)) os.set_blocking(sys.stdin.fileno(), False) with setup_term(): while True: buf = [0] * buf_size for i in range(buf_size): voices = [0.0] * len(keys) for j in range(len(keys)): phase, freq = phases[j], freqs[j] if freq == 0: continue voices[j] = sin(phase) phases[j] += 2 * pi * freq / SAMPLE_RATE if voices == []: continue # mixing mixfn = tanh_mix if mix_mode == "tanh" else voice_count_mix mix = int(mixfn(voices, gain) * INT16_MAX) buf[i] = clamp( mix, -INT16_MAX - 1, INT16_MAX ) # final clamp to int16 range, just in case alsa.write(buf) # this is blocking # input handling key = sys.stdin.read(1) if key == "q": break elif key == " ": mix_mode = "tanh" if mix_mode == "voice_count" else "voice_count" print(mix_mode) elif key is not None and key != "" and key in keys: i = keys.index(key) if freqs[i] == 0: freqs[i] = base_freq * (2 ** (i / 12)) else: freqs[i] = 0