Repository navigation
bledev.midi: BLE-MIDI as a MIDI port, with a portable codec - #85
Conversation
flush() could be re-entered by the retry timer's callback, which sent one chunk twice and dropped the next: a 16,890-character print came back corrupt five times out of five. And re-arming a one-shot machine.Timer while its alarm was being dispatched panicked the S3 in esp_timer's task. One periodic timer, never re-armed, and a re-entry guard. The REPL gate checks a long print.
About one reconnect in fifty from Windows came up dead: bleak's connect returned in 70-440 ms (a real one takes 1.7 s or more) with the device reported connected and the session active, the board never saw a connection, and the first GATT operation hung until Windows gave up nine seconds later. connect_and_set_up() bounds the setup and tries again; nus uses it. tests/bledev_board/reconnect_loop.py is the rapid-reconnect gate.
midi_codec is the BLE-MIDI 1.0 packet format in pure Python with no imports: 13-bit timestamps and their rollover, running status (kept across packets when decoding, used only inside one when encoding), SysEx split at the packet size, and real-time messages slipped into a SysEx. Its vectors (tests/bledev_midi_vectors.json) were worked out by hand and run on CPython and unix MicroPython, with five planted codec faults that must fail. midi.serve() and midi.connect() hand out a Port that is usbif's MidiPort: plain MIDI bytes through read(buf) and write(data), so a loop written for a USB controller runs on a BLE one. When usbif is installed the Port subclasses usbif.MidiPort. serve() sets the GAP name, which is what a paired host calls the port.
…easured 1,000 mixed messages each way, complete, in order and intact, board to board and from the laptop; planted drops and bit flips fail. Half a note-on's round trip: 10.8 ms median (p99 15.9) between two S3s at a 7.5 ms interval, 44.3 ms at the default, 13.4 ms from Windows with throughput parameters. Windows lists a paired board as a MIDI device through WinRT, but not through winmm.
|
JFYI The Windows MIDI Services updates going into Windows 11 25h2 and higher in November include native BLE MIDI 1.0, and will have BLE MIDI 2.0 as a fast-follow after the spec is ratified. We make the BLE devices available to WinRT MIDI 1.0 and WinMM just like any other MIDI Port. The apps don't need to know they are BLE or do anything special. Device compatibility tracking here: Pete |
Thanks Pete. I plan to resume work on BLE next week, and your work is coming at the perfect time! I'll post feedback on your issue 1173 when I have data to report. |
The first profile after bledev v1 (docs/ble.md §7 in the workspace repo). Stacked on
bledev-host(#81, itself on #78), and it carries #84's three commits (the reconnect and REPL-output fixes), becausemidi.connect()uses #84's retry. Merge #84 first and this diff shrinks to the MIDI commits.What you can do with it
The port is usbif's
MidiPortcontract (and ausbif.MidiPortsubclass when usbif is installed), so a loop written for a USB MIDI controller, feedingusbif.MidiParser, runs unchanged on a BLE one.await port.receive()also gives you the sender's timestamp. It's the standard BLE-MIDI service and characteristic, so Macs, phones and controllers speak it too.The codec (
bledev.midi_codec) is pure Python with no imports: 13-bit timestamps and their rollover, running status (accepted across packets, sent only within one), SysEx split at the packet size, real-time slipped into a SysEx. Its 31 vectors intests/bledev_midi_vectors.jsonwere worked out by hand from the packet format, not captured from the code, and both interpreters read the same file.Gates, run 2026-09-24
Latency BLE adds, one way (half a note-on's round trip through an echo, 400 notes at random moments):
Players feel about 10 ms, so BLE by itself uses that budget even at the fastest interval. The codec costs 0.5 ms a note on the S3, about 1 ms of the 10.8.
Windows as a MIDI host: once paired, Windows lists the board as
bledev-midi (Bluetooth MIDI IN/OUT)through WinRT's MIDI device interfaces. winmm doesn't see it, and this build's Windows MIDI Services has no Bluetooth transport, so a winmm DAW can't open it.serve()now sets the GAP name; before, Windows called itMPY ESP32.Not done:
ble.config(bond=True)plus aioble'ssecurity), then unpaired.