Fix MIDI bridge: remove semicolons (corrupts Pd velocity parsing), retry on device busy
Validate and Test / validate-patches (push) Failing after 13s

This commit is contained in:
2026-06-24 19:10:13 -07:00
parent eff3c45849
commit 3725df926e
2 changed files with 48 additions and 48 deletions
+37 -45
View File
@@ -17,12 +17,6 @@ SERVICES = ["midi-bridge", "pd-synth-onboard", "led-matrix-viz", "prometheus-exp
STATUS_CACHE = {} STATUS_CACHE = {}
LAST_STATUS_CHECK = 0.0 LAST_STATUS_CHECK = 0.0
STATUS_ROW = 0
SERVICE_COLS = {0: 0, 1: 1, 2: 2, 3: 3}
AUDIO_COL = 4
NOTE_ROWS = 7
NOTE_ROW_OFFSET = 1
def _check_services(): def _check_services():
global STATUS_CACHE, LAST_STATUS_CHECK global STATUS_CACHE, LAST_STATUS_CHECK
now = time.monotonic() now = time.monotonic()
@@ -55,31 +49,28 @@ def render(active_notes):
pixels = [[0] * COLS for _ in range(ROWS)] pixels = [[0] * COLS for _ in range(ROWS)]
note_min, note_max = 36, 96 note_min, note_max = 36, 96
for note in active_notes: for note in active_notes:
row = NOTE_ROWS - 1 - int((note - note_min) / (note_max - note_min) * (NOTE_ROWS - 1)) row = 7 - int((note - note_min) / (note_max - note_min) * 7)
row = max(0, min(NOTE_ROWS - 1, row)) + NOTE_ROW_OFFSET row = max(0, min(6, row))
col = note % 12 col = note % 12
pixels[row][col] = 1 pixels[row][col] = 1
status = _check_services() status = _check_services()
for idx, svc in enumerate(SERVICES): ok = sum(1 for s in SERVICES if status.get(s))
col = SERVICE_COLS[idx]
if status.get(svc):
pixels[STATUS_ROW][col] = 1
if len(active_notes) > 0: if len(active_notes) > 0:
pixels[STATUS_ROW][AUDIO_COL] = 1 ok += 1
for c in range(ok):
pixels[7][c] = 1
return pack_frame(pixels) return pack_frame(pixels)
def _diag_frame(active_notes): def _diag_frame(active_notes):
pixels = [[0] * COLS for _ in range(ROWS)] pixels = [[0] * COLS for _ in range(ROWS)]
status = _check_services() status = _check_services()
for idx, svc in enumerate(SERVICES): ok = sum(1 for s in SERVICES if status.get(s))
col = SERVICE_COLS[idx]
if status.get(svc):
for r in range(ROWS):
pixels[r][col] = 1
if len(active_notes) > 0: if len(active_notes) > 0:
for r in range(ROWS): ok += 1
pixels[r][AUDIO_COL] = 1 for r in range(ROWS):
for c in range(ok):
pixels[r][c] = 1
return pack_frame(pixels) return pack_frame(pixels)
@@ -193,34 +184,35 @@ def main():
except BlockingIOError: except BlockingIOError:
pass pass
while b";" in buf: msg = data.decode().strip()
raw_msg, buf = buf.split(b";", 1) parts = msg.split()
msg = raw_msg.decode().strip() if len(parts) >= 3 and parts[0] == "midi":
parts = msg.split() try:
if len(parts) >= 3 and parts[0] == "midi":
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
msg_type = status & 0xF0 except ValueError:
changed = False continue
if msg_type == 0x90 and vel > 0: msg_type = status & 0xF0
if note not in active_notes: changed = False
active_notes.add(note) if msg_type == 0x90 and vel > 0:
changed = True if note not in active_notes:
elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0): active_notes.add(note)
if note in active_notes: changed = True
active_notes.discard(note) elif msg_type == 0x80 or (msg_type == 0x90 and vel == 0):
changed = True if note in active_notes:
if changed: active_notes.discard(note)
if diag_active: changed = True
diag_active = False if changed:
last_diag = now if diag_active:
events_processed += 1 diag_active = False
params = render(active_notes) last_diag = now
if params != last_sent_params: events_processed += 1
msg_id = (msg_id + 1) % 65535 params = render(active_notes)
pending_bytes = packer.pack([0, msg_id, "draw_frame", params]) if params != last_sent_params:
last_sent_params = params msg_id = (msg_id + 1) % 65535
pending_bytes = packer.pack([0, msg_id, "draw_frame", params])
last_sent_params = params
if pending_bytes is not None: if pending_bytes is not None:
router, ok = try_send(router, pending_bytes) router, ok = try_send(router, pending_bytes)
+11 -3
View File
@@ -46,8 +46,8 @@ def _find_midi_dev():
def midi_msg(status, data1, data2=None): def midi_msg(status, data1, data2=None):
if data2 is not None: if data2 is not None:
return f"midi {status} {data1} {data2};" return f"midi {status} {data1} {data2}"
return f"midi {status} {data1};" return f"midi {status} {data1}"
def _open_midi(): def _open_midi():
@@ -55,7 +55,15 @@ def _open_midi():
while not os.path.exists(midi_dev): while not os.path.exists(midi_dev):
time.sleep(0.5) time.sleep(0.5)
midi_dev = _find_midi_dev() midi_dev = _find_midi_dev()
fd = os.open(midi_dev, os.O_RDWR | os.O_NONBLOCK) while True:
try:
fd = os.open(midi_dev, os.O_RDWR | os.O_NONBLOCK)
break
except OSError as e:
if e.errno == 16:
time.sleep(0.5)
continue
raise
os.set_blocking(fd, False) os.set_blocking(fd, False)
return fd return fd