import importlib from tkinter import Frame, Tk import synth from pyalsa import Alsa SAMPLE_RATE = 44_100 LATENCY = 50_000 # 50ms INT16_MAX = 2**15 - 1 alsa = Alsa(SAMPLE_RATE, LATENCY) keys = "qzsedftgyhujk" base_freq = 261.6256 # C4, piano middle C keys_map = [0] * len(keys) def keydown(e): if e.keysym in keys: i = keys.index(e.keysym) keys_map[i] = 1 def keyup(e): if e.keysym in keys: i = keys.index(e.keysym) keys_map[i] = 0 def key_to_freq(i): if 0 <= i < len(keys): return base_freq * (2 ** (i / 12)) return 0 root = Tk() # note parameters: freq, volume # note state: phase, volume class Note: def __init__(self): self.freq = 0 self.vol = 0 self.phase = 0 self.gain = 0 self.playing = False self.waveform = "sine" self.attack = 0.01 self.release = 0.05 self.waveshaping = False self.lowpass = False self.lp = 0 def reload_synth(event): importlib.reload(synth) print("reloaded synth module") def update(root, note): pressed_key = keys_map.index(1) if 1 in keys_map else -1 note_freq = key_to_freq(pressed_key) if note_freq != note.freq: note.freq = note_freq if not note.playing and note_freq != 0: note.playing = True if note.playing and note_freq == 0: note.playing = False buf = synth.synth(note) alsa.write(buf) # send sound to alsa root.after(0, update, root, note) # schedule next update def toggle_note_waveform(e): waves = ["sine", "saw", "square", "triangle"] current = waves.index(note.waveform) note.waveform = waves[(current + 1) % len(waves)] print("waveform:", note.waveform) def toggle_note_waveshaping(e): note.waveshaping = not note.waveshaping print("waveshaping:", note.waveshaping) def toggle_note_lowpass(e): note.lowpass = not note.lowpass print("lowpass:", note.lowpass) note = Note() frame = Frame(root, width=100, height=100) frame.bind("", keydown) frame.bind("", keyup) root.bind_all("", reload_synth) root.bind_all("w", toggle_note_waveform) root.bind_all("x", toggle_note_waveshaping) root.bind_all("c", toggle_note_lowpass) frame.pack() frame.focus_set() root.after(100, update, root, note) root.mainloop()