diff --git a/scripts/led-matrix-viz.py b/scripts/led-matrix-viz.py index c0a5d39..287d0b5 100644 --- a/scripts/led-matrix-viz.py +++ b/scripts/led-matrix-viz.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 """Listen for MIDI events via UDP and display note activity on LED matrix. -Uses RPC requests (not notifications) for reliability, with rate limiting. +Uses select() for polling both UDP and router RPC responses. """ -import socket, msgpack, time, os, re +import socket, msgpack, time, os, select VIZ_PORT = int(os.environ.get("VIZ_PORT", "8082")) ROUTER_SOCK = os.environ.get("ROUTER_SOCK", "/var/run/arduino-router.sock") DECAY_FRAMES = int(os.environ.get("DECAY_FRAMES", "8")) -MIN_INTERVAL = 1 / 15 +FPS = 15 COLS = 13 ROWS = 8 @@ -35,35 +35,45 @@ def main(): active_notes = {} last_frame = None - last_send = 0 buf = b"" msg_id = 0 + frame_interval = 1.0 / FPS + next_frame = time.monotonic() + + poller = select.poll() + poller.register(sock, select.POLLIN) while True: - now = time.monotonic() - + timeout = max(0, int((next_frame - time.monotonic()) * 1000)) try: - while True: - data, _ = sock.recvfrom(1024) - buf += data - except BlockingIOError: - pass + events = poller.poll(timeout) + except OSError: + events = [] - while b";" in buf: - msg, buf = buf.split(b";", 1) - msg = msg.decode().strip() - parts = msg.split() - if len(parts) >= 3 and parts[0] == "midi": - status = int(parts[1]) - note = int(parts[2]) - vel = int(parts[3]) if len(parts) >= 4 else 0 - msg_type = status & 0xF0 - if msg_type == 0x90 and vel > 0: - active_notes[note] = DECAY_FRAMES - elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0): - active_notes.pop(note, None) + for fd, flags in events: + if fd == sock.fileno() and flags & select.POLLIN: + try: + data, _ = sock.recvfrom(1024) + buf += data + while b";" in buf: + msg, buf = buf.split(b";", 1) + msg = msg.decode().strip() + parts = msg.split() + if len(parts) >= 3 and parts[0] == "midi": + status = int(parts[1]) + note = int(parts[2]) + vel = int(parts[3]) if len(parts) >= 4 else 0 + msg_type = status & 0xF0 + if msg_type == 0x90 and vel > 0: + active_notes[note] = DECAY_FRAMES + elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0): + active_notes.pop(note, None) + except BlockingIOError: + pass + + if time.monotonic() >= next_frame: + next_frame = time.monotonic() + frame_interval - if now - last_send >= MIN_INTERVAL: pixels = [[0] * COLS for _ in range(ROWS)] for note, decay in list(active_notes.items()): if decay <= 0: @@ -94,7 +104,6 @@ def main(): router.settimeout(2) router.connect(ROUTER_SOCK) last_frame = frame - last_send = now if __name__ == "__main__": main() diff --git a/scripts/midi-bridge.py b/scripts/midi-bridge.py index e5375bf..40bdc26 100644 --- a/scripts/midi-bridge.py +++ b/scripts/midi-bridge.py @@ -75,6 +75,8 @@ def main(): elif len(buf) >= 2 and (buf[0] & 0xE0) in (0xC0, 0xD0): _flush(sock, buf) buf = bytearray() + if not events: + time.sleep(0.01) except OSError as e: if e.errno == 19: os.close(fd)