8d3666cdaa
Validate and Test / validate-patches (push) Failing after 13s
hsl GUI objects don't initialize in -nogui mode, causing cutoff at 0 Hz and volume at 0 → no audio output. Use loadbang + float for headless-safe initialization.
124 lines
3.9 KiB
Python
124 lines
3.9 KiB
Python
#!/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}")
|