Files
2026-10-27 22:10:43 +01:00

74 lines
1.9 KiB
Python

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