Bluetooth Low Energy MIDI Profile
The BLE MIDI profile enables the transmission of MIDI (Musical Instrument Digital Interface) messages over Bluetooth Low Energy. It is designed for low-latency, low-power communication between MIDI devices such as musical instruments, controllers, and mobile applications.
BLE MIDI allows devices to exchange standard MIDI messages, including note events, control changes, and system messages, while benefiting from the reduced power consumption and flexible connection model provided by Bluetooth Low Energy.
The profile follows the MIDI over BLE specification defined by the MIDI Manufacturers Association (MMA) and the Bluetooth SIG.
Examples
API Reference
Header File
Functions
-
esp_err_t esp_ble_midi_profile_init(void)
Initialize the BLE MIDI profile.
This function must be called once before using any other BLE MIDI functions. It initializes internal synchronization primitives and state.
- Returns
ESP_OK on success
ESP_ERR_NO_MEM if mutex creation fails
ESP_OK if already initialized (idempotent)
-
esp_err_t esp_ble_midi_profile_deinit(void)
Deinitialize the BLE MIDI profile.
This function cleans up internal resources including mutex and clears all callbacks and state. After calling this function, esp_ble_midi_profile_init() must be called again before using any other BLE MIDI functions.
- Returns
ESP_OK on success
ESP_OK if already deinitialized (idempotent)
-
esp_err_t esp_ble_midi_register_rx_cb(esp_ble_midi_rx_cb_t cb, void *user_ctx)
Register application RX callback to receive BLE-MIDI event packets.
Called from the BLE host task; keep the handler short or hand off to another task.
Note
esp_ble_midi_profile_init() must be called before registering callbacks.
- Parameters
cb – [in] RX callback; pass NULL to unregister
user_ctx – [in] User context pointer to be passed to the callback
- Returns
ESP_OK on success
ESP_ERR_INVALID_STATE if esp_ble_midi_profile_init() has not been called
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_register_event_cb(esp_ble_midi_event_cb_t cb)
Register parsed MIDI event callback.
Called once per decoded MIDI message (including SysEx and real-time). Runs in the BLE host task; keep the handler short or post to an app task.
Note
esp_ble_midi_profile_init() must be called before registering callbacks.
- Parameters
cb – [in] Event callback; pass NULL to unregister
- Returns
ESP_OK on success
ESP_ERR_INVALID_STATE if esp_ble_midi_profile_init() has not been called
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send(const uint8_t *data, uint16_t len)
Send BLE-MIDI event packet via notification.
data must be a complete BEP payload (packet header timestamp high bits and a per-message timestamp byte before each message). Most applications should prefer esp_ble_midi_send_raw_midi() or the helper APIs.
Note
esp_ble_midi_profile_init() must be called before sending.
- Parameters
data – [in] Pointer to packet buffer
len – [in] Packet length
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on invalid args
ESP_ERR_INVALID_STATE if esp_ble_midi_profile_init() has not been called or notification is not enabled
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send_raw_midi(const uint8_t *midi, uint8_t midi_len, uint16_t timestamp)
Build and send a BLE-MIDI Event Packet from raw MIDI bytes.
Packs the BLE-MIDI timestamps (packet header high bits + per-message low byte).
- Parameters
midi – [in] Pointer to MIDI message bytes (status + data)
midi_len – [in] Length of MIDI message (1-3 bytes typical)
timestamp – [in] 13-bit timestamp in milliseconds (rolls at 8192)
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send_note_on(uint8_t channel, uint8_t note, uint8_t velocity, uint16_t timestamp)
Send MIDI Note On.
- Parameters
channel – [in] 0-15
note – [in] 0-127
velocity – [in] 0-127
timestamp – [in] 13-bit timestamp in ms
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send_note_off(uint8_t channel, uint8_t note, uint8_t velocity, uint16_t timestamp)
Send MIDI Note Off.
- Parameters
channel – [in] 0-15
note – [in] 0-127
velocity – [in] 0-127
timestamp – [in] 13-bit timestamp in ms
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send_cc(uint8_t channel, uint8_t controller, uint8_t value, uint16_t timestamp)
Send MIDI Control Change.
- Parameters
channel – [in] 0-15
controller – [in] 0-127
value – [in] 0-127
timestamp – [in] 13-bit timestamp in ms
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send_pitch_bend(uint8_t channel, uint16_t value, uint16_t timestamp)
Send MIDI Pitch Bend (14-bit value)
- Parameters
channel – [in] 0-15
value – [in] 0-16383 (8192 is center)
timestamp – [in] 13-bit timestamp in ms
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error
-
esp_err_t esp_ble_midi_send_multi(const uint8_t *const msgs[], const uint8_t msg_lens[], const uint16_t timestamps[], uint16_t count, size_t *first_unsent_index)
Send multiple MIDI events aggregated into a single BLE-MIDI packet.
Aggregates multiple MIDI messages in one notification to improve throughput. Each message will be prefixed with its own BLE-MIDI timestamp byte.
Notes:
The function packs as many messages as fit in an internal buffer (<= ATT MTU).
If not all messages fit, this function sends multiple notifications.
For SysEx, pass a complete 0xF0…0xF7 sequence. For automatic fragmentation, use esp_ble_midi_send_sysex(), or split by MTU and call this repeatedly.
On failure: If first_unsent_index is provided, it will be set to the index of the first message that was not sent. On success, it will be set to count. This allows callers to determine which messages were successfully transmitted before the error occurred.
- Parameters
msgs – [in] Array of pointers to MIDI messages (status + data bytes)
msg_lens – [in] Array of message lengths (1..n)
timestamps – [in] Array of 13-bit timestamps for each message (ms resolution)
count – [in] Number of messages in arrays
first_unsent_index – [out] Optional output parameter: on failure, set to the index of the first message that was not sent; on success, set to count. Pass NULL if not needed.
- Returns
ESP_OK on success (all messages sent)
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error (partial send may have occurred; check first_unsent_index)
-
esp_err_t esp_ble_midi_send_sysex(const uint8_t *sysex, uint16_t len, uint16_t timestamp)
Send a System Exclusive (SysEx) message with automatic multi-packet fragmentation.
The buffer must begin with 0xF0 and end with 0xF7. The function splits the SysEx into multiple BLE-MIDI Event Packets based on current ATT MTU.
Notes:
Uses the current MTU to choose fragment sizes (accounting for ATT and BEP overhead).
Real-time messages (0xF8..0xFF) are delivered as separate events on the receiver.
- Parameters
sysex – [in] Pointer to complete SysEx buffer (0xF0 … 0xF7)
len – [in] Length of the SysEx buffer
timestamp – [in] 13-bit timestamp in ms to use for all fragments
- Returns
ESP_OK on success
ESP_ERR_INVALID_ARG on wrong parameter
ESP_FAIL on other error
-
void esp_ble_midi_on_bep_received(const uint8_t *data, uint16_t len)
Profile entry for raw BLE-MIDI Event Packet received from GATT.
The service layer should call this to hand off incoming packets.
- Parameters
data – [in] Pointer to BEP payload buffer
len – [in] Length of BEP payload
-
uint16_t esp_ble_midi_get_timestamp_ms(void)
Get 13-bit rolling timestamp in milliseconds for BLE-MIDI.
Returns a 1 ms resolution counter modulo 8192 suitable for BLE-MIDI timestamps. Intended for use as the
timestampparameter in send helpers.- Returns
13-bit timestamp in milliseconds (0..8191)
-
esp_err_t esp_ble_midi_set_notify_enabled(bool enabled)
Set notification enabled state (typically called when CCCD is updated).
This should be called when the client writes to the CCCD (Client Characteristic Configuration Descriptor) to enable/disable notifications. MIDI send functions will only work when notification is enabled.
Note
esp_ble_midi_profile_init() must be called before setting notify state.
- Parameters
enabled – [in] true if notification is enabled, false otherwise
- Returns
ESP_OK on success
ESP_ERR_INVALID_STATE if esp_ble_midi_profile_init() has not been called
-
bool esp_ble_midi_is_notify_enabled(void)
Check if notification is currently enabled.
Note
esp_ble_midi_profile_init() must be called before checking notify state.
- Returns
true if notification is enabled, false otherwise (also returns false if not initialized)
Macros
-
BLE_MIDI_SERVICE_UUID128
BLE MIDI Service UUID (128-bit): 03B80E5A-EDE8-4B33-A751-6CE34EC4C700
-
BLE_MIDI_CHAR_UUID128
BLE MIDI IO Characteristic UUID (128-bit): 7772E5DB-3868-4112-A1A9-F2669D106BF3
-
BLE_MIDI_MAX_PKT_LEN
Maximum BLE-MIDI packet length (BLE 5.0+)
Used for buffer allocation and size validation in BLE-MIDI packets.
-
ESP_BLE_MIDI_TIMESTAMP_MAX
13-bit rolling timestamp (1 ms resolution)
Type Definitions
-
typedef void (*esp_ble_midi_rx_cb_t)(const uint8_t *data, uint16_t len, void *user_ctx)
RX callback for raw BLE-MIDI Event Packet (BEP) payloads.
The buffer contains the BEP payload as defined by the BLE-MIDI specification. For parsed per-message events, use esp_ble_midi_register_event_cb().
- Param data
[in] Pointer to BEP payload buffer
- Param len
[in] Length of BEP payload
- Param user_ctx
[in] User context pointer passed during registration
-
typedef void (*esp_ble_midi_event_cb_t)(uint16_t timestamp_ms, esp_ble_midi_event_type_t event_type, const uint8_t *msg, uint16_t msg_len)
Parsed event callback for a single decoded MIDI message.
msg contains one complete MIDI message (status + data).
SysEx is delivered as a complete 0xF0 … 0xF7 sequence (reassembled).
Real-time messages (0xF8..0xFF) are delivered as 1-byte events and may interleave.
On SysEx overflow (ESP_BLE_MIDI_EVENT_SYSEX_OVERFLOW), msg is NULL and msg_len is 0.
- Param timestamp_ms
[in] 13-bit rolling timestamp in milliseconds
- Param event_type
[in] Event type (normal message or overflow)
- Param msg
[in] Pointer to a single MIDI message (status + data), NULL on overflow
- Param msg_len
[in] Message length in bytes, 0 on overflow