159 lines
5.0 KiB
Python
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())
|