K1Guitar/tools/_usb_midi_sysex_probe.py

159 lines
5.0 KiB
Python

# -*- 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())