# -*- coding: utf-8 -*- """Quick USB MIDI (SAM5704) SysEx probe for App F0 60 frames.""" from __future__ import annotations import ctypes import ctypes.wintypes as w import sys import time winmm = ctypes.WinDLL("winmm") CALLBACK_FUNCTION = 0x30000 MIM_DATA, MIM_LONGDATA = 0x3C3, 0x3C4 MHDR_DONE = 0x01 class MIDIINCAPS(ctypes.Structure): _fields_ = [ ("wMid", w.WORD), ("wPid", w.WORD), ("vDriverVersion", w.DWORD), ("szPname", ctypes.c_char * 32), ("dwSupport", w.DWORD), ] class MIDIOUTCAPS(ctypes.Structure): _fields_ = [ ("wMid", w.WORD), ("wPid", w.WORD), ("vDriverVersion", w.DWORD), ("szPname", ctypes.c_char * 32), ("wTechnology", w.WORD), ("wVoices", w.WORD), ("wNotes", w.WORD), ("wChannelMask", w.WORD), ("dwSupport", w.DWORD), ] class MIDIHDR(ctypes.Structure): _fields_ = [ ("lpData", ctypes.c_void_p), ("dwBufferLength", w.DWORD), ("dwBytesRecorded", w.DWORD), ("dwUser", ctypes.c_void_p), ("dwFlags", w.DWORD), ("lpNext", ctypes.c_void_p), ("reserved", ctypes.c_void_p), ("dwOffset", w.DWORD), ("dwReserved", ctypes.c_size_t * 8), ] def find_sam(kind: str): n = winmm.midiInGetNumDevs() if kind == "in" else winmm.midiOutGetNumDevs() for i in range(n): if kind == "in": c = MIDIINCAPS() winmm.midiInGetDevCapsA(i, ctypes.byref(c), ctypes.sizeof(c)) else: c = MIDIOUTCAPS() winmm.midiOutGetDevCapsA(i, ctypes.byref(c), ctypes.sizeof(c)) name = c.szPname.decode("mbcs", "replace") if "SAM5704" in name: return i, name return None, None def hx(b: bytes) -> str: return " ".join(f"{x:02X}" for x in b) def main() -> int: sys.stdout.reconfigure(encoding="utf-8", errors="replace") in_i, in_n = find_sam("in") out_i, out_n = find_sam("out") print(f"IN={in_i} {in_n} OUT={out_i} {out_n}", flush=True) if in_i is None or out_i is None: print("SAM5704 not found") return 1 received: list[bytes] = [] keep = [] # keep buffer refs alive MidiInProc = ctypes.WINFUNCTYPE( None, w.HANDLE, w.UINT, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_size_t ) @MidiInProc def on_midi_in(h, msg, _inst, p1, p2): if msg == MIM_LONGDATA: hdr = ctypes.cast(p1, ctypes.POINTER(MIDIHDR)).contents n = hdr.dwBytesRecorded if n and hdr.lpData: data = ctypes.string_at(hdr.lpData, n) received.append(data) print("RX", hx(data), flush=True) winmm.midiInAddBuffer(h, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) elif msg == MIM_DATA: print( f"RX SHORT {(p1 & 0xFF):02X} {((p1 >> 8) & 0xFF):02X} {((p1 >> 16) & 0xFF):02X}", flush=True, ) h_in = w.HANDLE() r = winmm.midiInOpen(ctypes.byref(h_in), in_i, on_midi_in, 0, CALLBACK_FUNCTION) print("midiInOpen", r, flush=True) if r != 0: return r for _ in range(4): buf = ctypes.create_string_buffer(1024) hdr = MIDIHDR() hdr.lpData = ctypes.cast(buf, ctypes.c_void_p) hdr.dwBufferLength = 1024 keep.append((buf, hdr)) winmm.midiInPrepareHeader(h_in, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) winmm.midiInAddBuffer(h_in, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) winmm.midiInStart(h_in) h_out = w.HANDLE() r = winmm.midiOutOpen(ctypes.byref(h_out), out_i, 0, 0, 0) print("midiOutOpen", r, flush=True) if r != 0: return r def send_sysex(msg: bytes): buf = ctypes.create_string_buffer(msg) hdr = MIDIHDR() hdr.lpData = ctypes.cast(buf, ctypes.c_void_p) hdr.dwBufferLength = len(msg) keep.append((buf, hdr)) winmm.midiOutPrepareHeader(h_out, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) print("TX", hx(msg), flush=True) winmm.midiOutLongMsg(h_out, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) t0 = time.time() while not (hdr.dwFlags & MHDR_DONE) and time.time() - t0 < 2: time.sleep(0.01) winmm.midiOutUnprepareHeader(h_out, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) for label, msg in [ ("01 01", bytes.fromhex("F0600101F7")), ("01 02", bytes.fromhex("F0600102F7")), ("01 03", bytes.fromhex("F0600103F7")), ]: print("---", label, flush=True) n0 = len(received) send_sysex(msg) time.sleep(1.2) print(" new rx", len(received) - n0, flush=True) print("TOTAL RX", len(received), flush=True) app_like = [x for x in received if len(x) >= 2 and x[0] == 0xF0 and x[1] == 0x60] print("F0 60 replies", len(app_like), flush=True) winmm.midiInStop(h_in) winmm.midiInReset(h_in) for buf, hdr in keep[:4]: winmm.midiInUnprepareHeader(h_in, ctypes.byref(hdr), ctypes.sizeof(MIDIHDR)) winmm.midiInClose(h_in) winmm.midiOutClose(h_out) return 0 if __name__ == "__main__": raise SystemExit(main())