#!/usr/bin/env python3 """Generate synth.pd programmatically.""" from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent OUTPUT = SCRIPT_DIR.parent / "pd" / "synth.pd" def connect(c, src, sout, dst, din): c.append((src, sout, dst, din)) def obj(o, x, y, *args): idx = len(o) o.append((x, y, "obj", *args)) return idx def gen(): o = [] # objects c = [] # connections def Obj(x, y, *args): return obj(o, x, y, *args) # ── ALSA MIDI input (gadget mode) ── notein = Obj(20, 20, "notein") pack_note = Obj(80, 50, "pack 0 0") unpack_note = Obj(80, 80, "unpack 0 0") s_pitch_n = Obj(20, 110, "s midi-pitch") s_vel_n = Obj(80, 110, "s midi-vel") connect(c, notein, 0, pack_note, 0) connect(c, notein, 1, pack_note, 1) connect(c, pack_note, 0, unpack_note, 0) connect(c, unpack_note, 0, s_pitch_n, 0) connect(c, unpack_note, 1, s_vel_n, 0) # ── UDP MIDI input (host mode via midi-bridge) ── net = Obj(400, 20, "netreceive -u 8081") route_midi = Obj(400, 60, "route midi") route_note = Obj(400, 100, "route 144 128") unpack2_on = Obj(400, 140, "unpack 0 0") unpack2_off = Obj(480, 140, "unpack 0 0") s_pitch_u = Obj(400, 180, "s midi-pitch") s_vel_u = Obj(480, 180, "s midi-vel") connect(c, net, 0, route_midi, 0) connect(c, route_midi, 0, route_note, 0) connect(c, route_note, 0, unpack2_on, 0) connect(c, route_note, 1, unpack2_off, 0) connect(c, unpack2_on, 0, s_pitch_u, 0) connect(c, unpack2_on, 1, s_vel_u, 0) connect(c, unpack2_off, 0, s_pitch_u, 0) connect(c, unpack2_off, 1, s_vel_u, 0) # ── Synth voice ── r_vel = Obj(20, 160, "r midi-vel") r_pitch = Obj(20, 190, "r midi-pitch") strip = Obj(20, 220, "stripnote") mtof = Obj(20, 260, "mtof") env = Obj(20, 290, "env~ 50 200 0.7 400") osc1 = Obj(140, 260, "osc~") mul1 = Obj(140, 290, "*~ 0.3") osc2 = Obj(220, 260, "osc~") mul2 = Obj(220, 290, "*~ 0.3") sum = Obj(180, 330, "+~") scale = Obj(180, 360, "*~ 0.5") filt = Obj(180, 400, "lop~") amp = Obj(180, 460, "*~") panscale = Obj(320, 460, "*~") dac = Obj(250, 530, "dac~") connect(c, r_vel, 0, strip, 1) connect(c, r_pitch, 0, strip, 0) connect(c, strip, 0, mtof, 0) connect(c, mtof, 0, osc1, 0) connect(c, mtof, 0, osc2, 0) connect(c, osc1, 0, mul1, 0) connect(c, osc2, 0, mul2, 0) connect(c, mul1, 0, sum, 0) connect(c, mul2, 0, sum, 1) connect(c, sum, 0, scale, 0) connect(c, scale, 0, filt, 0) connect(c, filt, 0, amp, 0) connect(c, strip, 0, env, 0) connect(c, env, 0, amp, 1) connect(c, amp, 0, panscale, 0) # ── Controls (headless-safe: loadbang-initialized floats, not hsl) ── lb_cut = Obj(350, 300, "loadbang") float_cut = Obj(350, 320, "float 10000") connect(c, lb_cut, 0, float_cut, 0) connect(c, float_cut, 0, filt, 1) lb_vol = Obj(350, 400, "loadbang") float_vol = Obj(350, 420, "float 80") div = Obj(350, 450, "/ 100") connect(c, lb_vol, 0, float_vol, 0) connect(c, float_vol, 0, div, 0) connect(c, div, 0, panscale, 1) # ── Output ── connect(c, panscale, 0, dac, 0) connect(c, panscale, 0, dac, 1) # ── Write ── lines = [ "#N canvas 0 0 900 550 10;", "# DO NOT EDIT — generated by scripts/gen_synth_pd.py;", ] for x, y, t, *args in o: joined = " ".join(str(a) for a in args) lines.append(f"#X obj {x} {y} {joined};") for src, sout, dst, din in c: lines.append(f"#X connect {src} {sout} {dst} {din};") lines.append("") return "\n".join(lines) if __name__ == "__main__": content = gen() OUTPUT.parent.mkdir(parents=True, exist_ok=True) OUTPUT.write_text(content) print(f"Wrote {OUTPUT}") # Print connections for debugging for line in content.splitlines(): if line.startswith("#X connect"): print(f" {line}")