#!/usr/bin/env python3 """Bridge raw MIDI device to Pd via UDP. For host mode with MPK Mini. Sends parsed MIDI messages as Pd-style 'midi status data1 data2;' strings to UDP ports for Pd (8081) and LED matrix visualization (8082). """ import os, socket, time, select, struct MIDI_DEV = os.environ.get("MIDI_BRIDGE_DEV", "/dev/snd/midiC0D0") PD_HOST = os.environ.get("MIDI_BRIDGE_HOST", "127.0.0.1") PD_PORT = int(os.environ.get("MIDI_BRIDGE_PORT", "8081")) VIZ_PORT = int(os.environ.get("VIZ_PORT", "8082")) def midi_msg(status, data1, data2=None): if data2 is not None: return f"midi {status} {data1} {data2};" return f"midi {status} {data1};" def main(): while not os.path.exists(MIDI_DEV): time.sleep(0.5) fd = os.open(MIDI_DEV, os.O_RDWR | os.O_NONBLOCK) os.set_blocking(fd, False) poll = select.poll() poll.register(fd, select.POLLIN) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) running = True buf = bytearray() while running: try: events = poll.poll(500) for f, flags in events: if flags & select.POLLIN: data = os.read(fd, 1024) if not data: continue for byte in data: if byte & 0x80: if len(buf) > 0 and buf[0] >= 0x80: _flush(sock, buf, PD_HOST, PD_PORT, VIZ_PORT) buf = bytearray([byte]) else: buf.append(byte) if len(buf) >= 3 and (buf[0] & 0xE0) in (0x80, 0x90, 0xA0, 0xB0, 0xE0): _flush(sock, buf, PD_HOST, PD_PORT, VIZ_PORT) buf = bytearray() elif len(buf) >= 2 and (buf[0] & 0xE0) in (0xC0, 0xD0): _flush(sock, buf, PD_HOST, PD_PORT, VIZ_PORT) buf = bytearray() except (BlockingIOError, OSError) as e: if isinstance(e, OSError) and e.errno == 19: time.sleep(1) continue if not isinstance(e, BlockingIOError): raise except KeyboardInterrupt: running = False os.close(fd) sock.close() def _flush(sock, buf, pd_host, pd_port, viz_port): status = buf[0] if (buf[0] & 0xF0) == 0xF0: return data1 = buf[1] data2 = buf[2] if len(buf) >= 3 else None msg = midi_msg(status, data1, data2) sock.sendto(msg.encode(), (pd_host, pd_port)) sock.sendto(msg.encode(), (pd_host, viz_port)) if __name__ == "__main__": main()