fix: event-driven viz with immediate rendering, zero decay, clean stats
Validate and Test / validate-patches (push) Failing after 12s
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:
+23
-41
@@ -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,42 +104,30 @@ 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)
|
status = int(parts[1])
|
||||||
continue
|
note = int(parts[2])
|
||||||
if parts[0] != "midi":
|
vel = int(parts[3]) if len(parts) >= 4 else 0
|
||||||
print(f"[viz] not midi: {msg!r}", flush=True)
|
msg_type = status & 0xF0
|
||||||
continue
|
changed = False
|
||||||
if len(parts) < 3:
|
if msg_type == 0x90 and vel > 0:
|
||||||
print(f"[viz] too short: {parts}", flush=True)
|
if note not in active_notes:
|
||||||
continue
|
active_notes.add(note)
|
||||||
midi_matched += 1
|
changed = True
|
||||||
status = int(parts[1])
|
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
|
||||||
note = int(parts[2])
|
if note in active_notes:
|
||||||
vel = int(parts[3]) if len(parts) >= 4 else 0
|
active_notes.discard(note)
|
||||||
msg_type = status & 0xF0
|
changed = True
|
||||||
changed = False
|
if changed:
|
||||||
if msg_type == 0x90 and vel > 0:
|
events_processed += 1
|
||||||
if note not in active_notes:
|
params = render(active_notes)
|
||||||
active_notes.add(note)
|
if params != last_sent_params:
|
||||||
changed = True
|
msg_id = (msg_id + 1) % 65535
|
||||||
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
|
pending_bytes = packer.pack([0, msg_id, "draw_frame", params])
|
||||||
if note in active_notes:
|
last_sent_params = params
|
||||||
active_notes.discard(note)
|
|
||||||
changed = True
|
|
||||||
if changed:
|
|
||||||
changed_true += 1
|
|
||||||
events_processed += 1
|
|
||||||
print(f"[viz] note {note} {'on' if vel > 0 else 'off'} events={events_processed}", flush=True)
|
|
||||||
params = render(active_notes)
|
|
||||||
if params != last_sent_params:
|
|
||||||
msg_id = (msg_id + 1) % 65535
|
|
||||||
pending_bytes = packer.pack([0, msg_id, "draw_frame", params])
|
|
||||||
last_sent_params = params
|
|
||||||
|
|
||||||
# Try to send pending frame — no POLLOUT dependency
|
# Try to send pending frame — no POLLOUT dependency
|
||||||
if pending_bytes is not None:
|
if pending_bytes is not None:
|
||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user