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
This commit is contained in:
2026-06-24 02:08:57 -07:00
parent 242c74236f
commit 3cc497f632
5 changed files with 64 additions and 47 deletions
+5 -1
View File
@@ -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]) 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 { violation contains msg if {
some rec in input.records some rec in input.records
rec.type == "connect" rec.type == "connect"
@@ -110,6 +110,7 @@ violation contains msg if {
dst_rec.type == "obj" dst_rec.type == "obj"
is_signal(src_rec.name) is_signal(src_rec.name)
not is_signal(dst_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]) 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) # Inlet exceeds known count (skip -1 = variable)
# Skip outlet objects (valid in abstractions) and unknown abstractions
violation contains msg if { violation contains msg if {
some rec in input.records some rec in input.records
rec.type == "connect" rec.type == "connect"
dst_rec := record_by_idx(rec.dst) dst_rec := record_by_idx(rec.dst)
dst_rec.type == "obj" dst_rec.type == "obj"
dst_rec.name != "outlet"
dst_rec.name != "outlet~"
known_inlets[dst_rec.name] known_inlets[dst_rec.name]
known_inlets[dst_rec.name] != -1 known_inlets[dst_rec.name] != -1
rec.inlet >= known_inlets[dst_rec.name] rec.inlet >= known_inlets[dst_rec.name]
+5
View File
@@ -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;
+14
View File
@@ -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;
+29 -36
View File
@@ -1,40 +1,33 @@
#N canvas 0 0 350 500 10; #N canvas 0 0 500 600 10;
#X obj 40 20 netreceive -u 8081; #X obj 40 20 midi-in;
#X obj 40 50 route midi; #X obj 40 80 stripnote;
#X obj 40 80 route 144 128; #X obj 40 140 mtof;
#X obj 40 110 unpack 0 0; #X obj 40 180 osc~;
#X obj 40 150 s midi-pitch; #X obj 160 180 osc~;
#X obj 120 150 s midi-vel; #X obj 40 220 *~ 1;
#X obj 40 200 r midi-vel; #X obj 160 220 *~ 1;
#X obj 120 200 r midi-pitch; #X obj 100 260 +~;
#X obj 40 250 stripnote; #X obj 100 300 lop~ 10000;
#X obj 40 310 mtof; #X obj 40 340 env~ 2 10 1 40;
#X obj 40 350 env~ 2 10 1 40; #X obj 100 380 *~;
#X obj 40 430 osc~; #X obj 100 440 audio-out;
#X obj 160 430 osc~; #X text 200 180 osc~ 1 (main);
#X obj 40 470 *~ 1; #X text 320 180 osc~ 2 (+7ct);
#X obj 160 470 *~ 1; #X text 155 220 mix;
#X obj 100 510 +~; #X text 155 300 lop~;
#X obj 100 550 lop~ 10000; #X text 155 340 envelope;
#X obj 100 610 *~; #X text 155 380 amp;
#X obj 100 670 dac~;
#X connect 0 0 1 0; #X connect 0 0 1 0;
#X connect 0 1 1 1;
#X connect 1 0 2 0; #X connect 1 0 2 0;
#X connect 1 1 9 1;
#X connect 2 0 3 0; #X connect 2 0 3 0;
#X connect 3 0 4 0; #X connect 2 0 4 0;
#X connect 3 1 5 0; #X connect 3 0 5 0;
#X connect 6 0 8 1; #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 7 0 8 0;
#X connect 8 1 10 0; #X connect 8 0 10 0;
#X connect 8 0 9 0; #X connect 9 0 10 1;
#X connect 9 0 11 0; #X connect 10 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;
+11 -10
View File
@@ -26,9 +26,10 @@ def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", VIZ_PORT)) sock.bind(("0.0.0.0", VIZ_PORT))
sock.setblocking(False)
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
router.settimeout(2) router.setblocking(False)
router.connect(ROUTER_SOCK) router.connect(ROUTER_SOCK)
packer = msgpack.Packer() packer = msgpack.Packer()
@@ -38,9 +39,11 @@ def main():
msg_id = 0 msg_id = 0
frame_interval = 1.0 / FPS frame_interval = 1.0 / FPS
next_frame = time.monotonic() next_frame = time.monotonic()
router_buf = b""
poller = select.poll() poller = select.poll()
poller.register(sock, select.POLLIN) poller.register(sock, select.POLLIN)
poller.register(router, select.POLLIN)
while True: while True:
timeout = max(0, int((next_frame - time.monotonic()) * 1000)) timeout = max(0, int((next_frame - time.monotonic()) * 1000))
@@ -70,6 +73,12 @@ def main():
except BlockingIOError: except BlockingIOError:
pass pass
if fd == router.fileno() and flags & select.POLLIN:
try:
router_buf += router.recv(4096)
except BlockingIOError:
pass
if time.monotonic() >= next_frame: if time.monotonic() >= next_frame:
next_frame = time.monotonic() + frame_interval next_frame = time.monotonic() + frame_interval
@@ -92,16 +101,8 @@ def main():
req = packer.pack([0, msg_id, "draw_frame", params]) req = packer.pack([0, msg_id, "draw_frame", params])
try: try:
router.sendall(req) router.sendall(req)
router.settimeout(0.3) except BlockingIOError:
router.recv(128)
router.settimeout(2)
except socket.timeout:
pass pass
except OSError:
router.close()
router = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
router.settimeout(2)
router.connect(ROUTER_SOCK)
last_frame = frame last_frame = frame
if __name__ == "__main__": if __name__ == "__main__":