Files
uno-q-audio-synth/scripts/gen_synth_pd.py
T
david 191c70fd18
Validate and Test / validate-patches (push) Failing after 12s
fix: replace cyclone env~ with vanilla line~ envelope, fix stripnote outlet
- env~ is a cyclone external not available on board's Pd 0.55
- Replace with line~ + *~ 0.0055 for velocity-scaled AR envelope
- Fix stripnote outlet 1 (gate trigger) connected to envelope instead of outlet 0 (pitch)
- Change dac~ to dac~ 1 for mono output matching -outchannels 1
- Envelope: attack ~50ms, release ~50ms, sustain scaled by velocity
2026-06-24 16:47:05 -07:00

131 lines
4.4 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")
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~ 1")
# ── Vanilla AR envelope (line~, not cyclone env~) ──
# stripnote out1 = velocity (0-127) on note-on, 0 on note-off
# Scale velocity/127 * 0.7 ≈ velocity * 0.0055 for sustain approx 0.7
env_ramp = Obj(20, 290, "line~ 0 50")
env_scale = Obj(20, 330, "*~ 0.0055")
env_mult = Obj(100, 370, "*~")
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)
# stripnote out1 (gate: velocity on note-on, 0 on note-off) → line~
connect(c, strip, 1, env_ramp, 0)
connect(c, env_ramp, 0, env_scale, 0)
connect(c, env_scale, 0, env_mult, 0)
connect(c, amp, 0, env_mult, 1)
connect(c, env_mult, 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 (mono, matches -outchannels 1) ──
connect(c, panscale, 0, dac, 0)
# ── 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}")