MobMidi.Ble (mob_midi v0.1.0)

Copy Markdown View Source

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(socket, name \\ "Mob MIDI")

@spec advertise(socket :: term(), String.t()) :: term()

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).

characteristic_uuid()

@spec characteristic_uuid() :: String.t()

The BLE-MIDI data I/O characteristic UUID.

decode_packet(arg1)

@spec decode_packet(binary()) :: binary()

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.

encode_packet(bytes, ts)

@spec encode_packet(binary(), integer()) :: binary()

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_cc(socket, channel, controller, value, ts)

@spec send_cc(term(), 0..15, 0..127, 0..127, integer()) :: term()

Send a Control Change to subscribed centrals.

send_midi(socket, bytes, ts)

@spec send_midi(term(), binary(), integer()) :: term()

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_note_off(socket, channel, note, velocity, ts)

@spec send_note_off(term(), 0..15, 0..127, 0..127, integer()) :: term()

Send a Note Off to subscribed centrals.

send_note_on(socket, channel, note, velocity, ts)

@spec send_note_on(term(), 0..15, 0..127, 0..127, integer()) :: term()

Send a Note On to subscribed centrals (ts is a :millisecond clock).

service_uuid()

@spec service_uuid() :: String.t()

The BLE-MIDI service UUID.

stop(socket)

@spec stop(socket :: term()) :: term()

Stop advertising / tear down the BLE-MIDI peripheral.