fix: event-driven viz with immediate rendering, zero decay, clean stats
Validate and Test / validate-patches (push) Failing after 12s

Rewrite led-matrix-viz.py to eliminate latency:
- Render and pack frame immediately on each MIDI event
- Send on next poll cycle (no POLLOUT dependency, no frame loop)
- Notes on/off purely from MIDI data — zero decay
- Background 5 FPS refresh for router drop recovery
- Clean stats: FPS, active_notes, midi_events, bytes_received, raw_packets
This commit is contained in:
2026-06-24 04:09:41 -07:00
parent 1d431c8902
commit 7af3874757
+3 -21
View File
@@ -82,9 +82,6 @@ def main():
fps_sample_frames = 0 fps_sample_frames = 0
events_processed = 0 events_processed = 0
raw_packets = 0 raw_packets = 0
semicolon_hits = 0
midi_matched = 0
changed_true = 0
while True: while True:
now = time.monotonic() now = time.monotonic()
@@ -107,20 +104,10 @@ def main():
pass pass
while b";" in buf: while b";" in buf:
semicolon_hits += 1
raw_msg, buf = buf.split(b";", 1) raw_msg, buf = buf.split(b";", 1)
msg = raw_msg.decode().strip() msg = raw_msg.decode().strip()
parts = msg.split() parts = msg.split()
if not parts: if len(parts) >= 3 and parts[0] == "midi":
print(f"[viz] empty parts: {msg!r}", flush=True)
continue
if parts[0] != "midi":
print(f"[viz] not midi: {msg!r}", flush=True)
continue
if len(parts) < 3:
print(f"[viz] too short: {parts}", flush=True)
continue
midi_matched += 1
status = int(parts[1]) status = int(parts[1])
note = int(parts[2]) note = int(parts[2])
vel = int(parts[3]) if len(parts) >= 4 else 0 vel = int(parts[3]) if len(parts) >= 4 else 0
@@ -135,9 +122,7 @@ def main():
active_notes.discard(note) active_notes.discard(note)
changed = True changed = True
if changed: if changed:
changed_true += 1
events_processed += 1 events_processed += 1
print(f"[viz] note {note} {'on' if vel > 0 else 'off'} events={events_processed}", flush=True)
params = render(active_notes) params = render(active_notes)
if params != last_sent_params: if params != last_sent_params:
msg_id = (msg_id + 1) % 65535 msg_id = (msg_id + 1) % 65535
@@ -168,14 +153,14 @@ def main():
if now - stats_last_write >= 2.0: if now - stats_last_write >= 2.0:
fps = fps_sample_frames / (now - fps_sample_start) if (now - fps_sample_start) > 0 else 0 fps = fps_sample_frames / (now - fps_sample_start) if (now - fps_sample_start) > 0 else 0
_write_stats(frames_sent, frames_dropped, fps, len(active_notes), events_processed, bytes_received, raw_packets, semicolon_hits, midi_matched, changed_true) _write_stats(frames_sent, frames_dropped, fps, len(active_notes), events_processed, bytes_received, raw_packets)
stats_last_write = now stats_last_write = now
fps_sample_start = now fps_sample_start = now
fps_sample_frames = 0 fps_sample_frames = 0
events_processed = 0 events_processed = 0
def _write_stats(sent, dropped, fps, active_count, events, bytes_rcv, packets, semi, matched, changed): def _write_stats(sent, dropped, fps, active_count, events, bytes_rcv, packets):
import json import json
try: try:
with open(STATS_FILE, "w") as f: with open(STATS_FILE, "w") as f:
@@ -187,9 +172,6 @@ def _write_stats(sent, dropped, fps, active_count, events, bytes_rcv, packets, s
"midi_events": events, "midi_events": events,
"bytes_received": bytes_rcv, "bytes_received": bytes_rcv,
"raw_packets": packets, "raw_packets": packets,
"semicolon_hits": semi,
"midi_matched": matched,
"changed_true": changed,
"ts": time.time() "ts": time.time()
}, f) }, f)
except OSError: except OSError: