From 3cc497f6322c503457b59e7f0be3707fd7c8d3a8 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Wed, 24 Jun 2026 02:08:57 -0700 Subject: [PATCH] refactor: 3-module pd architecture + viz non-blocking fix - midi-in.pd: netreceive -> route -> unpack -> pitch/velocity outlets - audio-out.pd: inlet~ -> dac~ abstraction - synth.pd: consumer patch wiring midi-in -> voice -> audio-out - led-matrix-viz.py: router socket non-blocking, registered with poller - pd.rego: skip outlet/abstraction false positives in validation --- pd-validator/pd.rego | 6 +++- pd/audio-out.pd | 5 +++ pd/midi-in.pd | 14 +++++++++ pd/synth.pd | 65 +++++++++++++++++---------------------- scripts/led-matrix-viz.py | 21 +++++++------ 5 files changed, 64 insertions(+), 47 deletions(-) create mode 100644 pd/audio-out.pd create mode 100644 pd/midi-in.pd diff --git a/pd-validator/pd.rego b/pd-validator/pd.rego index e4dcf1b..81a4ec5 100644 --- a/pd-validator/pd.rego +++ b/pd-validator/pd.rego @@ -100,7 +100,7 @@ violation contains msg if { msg := sprintf("connect[%d]/[%d]: duplicate (%d,%d)->(%d,%d)", [a.x_index, b.x_index, a.src, a.outlet, a.dst, a.inlet]) } -# Signal-to-control connection +# Signal-to-control connection (skip unknown objects — might be abstractions with inlet~) violation contains msg if { some rec in input.records rec.type == "connect" @@ -110,6 +110,7 @@ violation contains msg if { dst_rec.type == "obj" is_signal(src_rec.name) not is_signal(dst_rec.name) + known_inlets[dst_rec.name] msg := sprintf("connect[%d]: signal (~) outlet %d (%s) to control inlet %d (%s)", [rec.x_index, rec.src, src_rec.name, rec.dst, dst_rec.name]) } @@ -137,11 +138,14 @@ violation contains msg if { } # Inlet exceeds known count (skip -1 = variable) +# Skip outlet objects (valid in abstractions) and unknown abstractions violation contains msg if { some rec in input.records rec.type == "connect" dst_rec := record_by_idx(rec.dst) dst_rec.type == "obj" + dst_rec.name != "outlet" + dst_rec.name != "outlet~" known_inlets[dst_rec.name] known_inlets[dst_rec.name] != -1 rec.inlet >= known_inlets[dst_rec.name] diff --git a/pd/audio-out.pd b/pd/audio-out.pd new file mode 100644 index 0000000..9affefd --- /dev/null +++ b/pd/audio-out.pd @@ -0,0 +1,5 @@ +#N canvas 0 0 200 100 10; +#X obj 40 20 inlet~; +#X obj 40 60 dac~; +#X connect 0 0 1 0; +#X connect 0 0 1 1; diff --git a/pd/midi-in.pd b/pd/midi-in.pd new file mode 100644 index 0000000..9bf75d6 --- /dev/null +++ b/pd/midi-in.pd @@ -0,0 +1,14 @@ +#N canvas 0 0 300 200 10; +#X obj 40 20 netreceive -u 8081; +#X obj 40 50 route midi; +#X obj 40 80 route 144 128; +#X obj 40 110 unpack 0 0; +#X obj 40 150 outlet; +#X obj 120 150 outlet; +#X text 155 150 pitch; +#X text 155 170 velocity; +#X connect 0 0 1 0; +#X connect 1 0 2 0; +#X connect 2 0 3 0; +#X connect 3 0 4 0; +#X connect 3 1 5 0; diff --git a/pd/synth.pd b/pd/synth.pd index 0011bee..01843f4 100644 --- a/pd/synth.pd +++ b/pd/synth.pd @@ -1,40 +1,33 @@ -#N canvas 0 0 350 500 10; -#X obj 40 20 netreceive -u 8081; -#X obj 40 50 route midi; -#X obj 40 80 route 144 128; -#X obj 40 110 unpack 0 0; -#X obj 40 150 s midi-pitch; -#X obj 120 150 s midi-vel; -#X obj 40 200 r midi-vel; -#X obj 120 200 r midi-pitch; -#X obj 40 250 stripnote; -#X obj 40 310 mtof; -#X obj 40 350 env~ 2 10 1 40; -#X obj 40 430 osc~; -#X obj 160 430 osc~; -#X obj 40 470 *~ 1; -#X obj 160 470 *~ 1; -#X obj 100 510 +~; -#X obj 100 550 lop~ 10000; -#X obj 100 610 *~; -#X obj 100 670 dac~; +#N canvas 0 0 500 600 10; +#X obj 40 20 midi-in; +#X obj 40 80 stripnote; +#X obj 40 140 mtof; +#X obj 40 180 osc~; +#X obj 160 180 osc~; +#X obj 40 220 *~ 1; +#X obj 160 220 *~ 1; +#X obj 100 260 +~; +#X obj 100 300 lop~ 10000; +#X obj 40 340 env~ 2 10 1 40; +#X obj 100 380 *~; +#X obj 100 440 audio-out; +#X text 200 180 osc~ 1 (main); +#X text 320 180 osc~ 2 (+7ct); +#X text 155 220 mix; +#X text 155 300 lop~; +#X text 155 340 envelope; +#X text 155 380 amp; #X connect 0 0 1 0; +#X connect 0 1 1 1; #X connect 1 0 2 0; +#X connect 1 1 9 1; #X connect 2 0 3 0; -#X connect 3 0 4 0; -#X connect 3 1 5 0; -#X connect 6 0 8 1; +#X connect 2 0 4 0; +#X connect 3 0 5 0; +#X connect 4 0 6 0; +#X connect 5 0 7 0; +#X connect 6 0 7 1; #X connect 7 0 8 0; -#X connect 8 1 10 0; -#X connect 8 0 9 0; -#X connect 9 0 11 0; -#X connect 9 0 12 0; -#X connect 10 0 17 1; -#X connect 11 0 13 0; -#X connect 12 0 14 0; -#X connect 13 0 15 0; -#X connect 14 0 15 1; -#X connect 15 0 16 0; -#X connect 16 0 17 0; -#X connect 17 0 18 0; -#X connect 17 0 18 1; +#X connect 8 0 10 0; +#X connect 9 0 10 1; +#X connect 10 0 11 0; diff --git a/scripts/led-matrix-viz.py b/scripts/led-matrix-viz.py index 8ce4fa3..4dd797a 100644 --- a/scripts/led-matrix-viz.py +++ b/scripts/led-matrix-viz.py @@ -26,9 +26,10 @@ def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("0.0.0.0", VIZ_PORT)) + sock.setblocking(False) router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - router.settimeout(2) + router.setblocking(False) router.connect(ROUTER_SOCK) packer = msgpack.Packer() @@ -38,9 +39,11 @@ def main(): msg_id = 0 frame_interval = 1.0 / FPS next_frame = time.monotonic() + router_buf = b"" poller = select.poll() poller.register(sock, select.POLLIN) + poller.register(router, select.POLLIN) while True: timeout = max(0, int((next_frame - time.monotonic()) * 1000)) @@ -70,6 +73,12 @@ def main(): except BlockingIOError: pass + if fd == router.fileno() and flags & select.POLLIN: + try: + router_buf += router.recv(4096) + except BlockingIOError: + pass + if time.monotonic() >= next_frame: next_frame = time.monotonic() + frame_interval @@ -92,16 +101,8 @@ def main(): req = packer.pack([0, msg_id, "draw_frame", params]) try: router.sendall(req) - router.settimeout(0.3) - router.recv(128) - router.settimeout(2) - except socket.timeout: + except BlockingIOError: pass - except OSError: - router.close() - router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - router.settimeout(2) - router.connect(ROUTER_SOCK) last_frame = frame if __name__ == "__main__":