BLE-MIDI transport: advertise the phone as a Bluetooth LE MIDI peripheral so a computer (or another phone) connects to it as a central and plays it.
This is the "phone keyboard drives a computer synth, wirelessly" path. It's a
thin layer over MobBluetooth.Le (the generic GATT-peripheral primitive): the
only MIDI-specific parts here are the two well-known BLE-MIDI UUIDs and the
packet framing (a 13-bit-millisecond timestamp header per the BLE-MIDI spec).
Everything underneath — advertising, the GATT server, notifications, incoming
writes — is generic Bluetooth and lives in mob_bluetooth.
Once connected, the peripheral shows up in the host's MIDI stack (macOS: Audio MIDI Setup → Bluetooth; iOS/other: any BLE-MIDI client).
Sending (phone → computer)
socket = MobMidi.Ble.advertise(socket, "Mob MIDI")
# wait for {:bt_le, :subscribed, _} (the central enabled notifications)
ts = System.monotonic_time(:millisecond)
socket = MobMidi.Ble.send_note_on(socket, 0, 60, 100, ts)
socket = MobMidi.Ble.send_note_off(socket, 0, 60, 0, ts)Receiving (computer → phone)
Incoming BLE-MIDI arrives as a MobBluetooth.Le write on the MIDI
characteristic:
{:bt_le, :write, %{characteristic: char, bytes: packet}}Strip the BLE framing with decode_packet/1, then MobMidi.parse/1:
def handle_info({:bt_le, :write, %{bytes: packet}}, socket) do
for ev <- MobMidi.parse(MobMidi.Ble.decode_packet(packet)), do: react(ev)
{:noreply, socket}
end
Summary
Functions
Advertise this device as a BLE-MIDI peripheral named name.
The BLE-MIDI data I/O characteristic UUID.
Strip the BLE-MIDI framing from a packet, returning the concatenated raw MIDI
bytes (feed the result to MobMidi.parse/1).
Wrap raw MIDI bytes in a single BLE-MIDI packet.
Send a Control Change to subscribed centrals.
Send raw MIDI bytes (a single channel-voice or system message) to subscribed
centrals, wrapped in a BLE-MIDI packet stamped at ts milliseconds.
Send a Note Off to subscribed centrals.
Send a Note On to subscribed centrals (ts is a :millisecond clock).
The BLE-MIDI service UUID.
Stop advertising / tear down the BLE-MIDI peripheral.
Functions
Advertise this device as a BLE-MIDI peripheral named name.
Delegates to MobBluetooth.Le.start_advertising/2 with the BLE-MIDI service
and a single read/write/write-without-response/notify characteristic.
Lifecycle + connection events arrive tagged :bt_le (see MobBluetooth.Le).
@spec characteristic_uuid() :: String.t()
The BLE-MIDI data I/O characteristic UUID.
Strip the BLE-MIDI framing from a packet, returning the concatenated raw MIDI
bytes (feed the result to MobMidi.parse/1).
Handles the common shape — a header followed by timestamp + status + data
groups. Running-status messages (a timestamp followed directly by data bytes,
reusing the prior status) are not reconstructed; the first complete
message in such a packet is still recovered. Returns <<>> for a malformed or
empty packet.
Wrap raw MIDI bytes in a single BLE-MIDI packet.
A BLE-MIDI packet is a header byte carrying the high 6 bits of a 13-bit millisecond timestamp, then a timestamp byte (low 7 bits) before the MIDI message. Both framing bytes have bit 7 set; the timestamp wraps every 8192 ms.
Send a Control Change to subscribed centrals.
Send raw MIDI bytes (a single channel-voice or system message) to subscribed
centrals, wrapped in a BLE-MIDI packet stamped at ts milliseconds.
Send a Note Off to subscribed centrals.
Send a Note On to subscribed centrals (ts is a :millisecond clock).
@spec service_uuid() :: String.t()
The BLE-MIDI service UUID.
Stop advertising / tear down the BLE-MIDI peripheral.