diff options
| author | Zixun LI <[email protected]> | 2026-07-05 17:46:14 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-07-05 17:46:14 +0200 |
| commit | 536157cb98fbc40329c9695281506fe7f04e526f (patch) | |
| tree | ced35911cd15c1d6b7cca9b63b517168f123cc94 | |
| parent | 3579ea1888936bfe46b5a5635727707168b62223 (diff) | |
| parent | 0309e7c016b5217734791ba5ad8f3fd8c82ac2e1 (diff) | |
Merge pull request #3738 from sauloverissimo/fix/midi2-fb-direction
feat(midi2): derive function blocks from the GTB descriptor
| -rw-r--r-- | examples/device/midi2_device/src/main.c | 19 | ||||
| -rw-r--r-- | examples/device/midi2_device/src/tusb_config.h | 4 | ||||
| -rw-r--r-- | examples/device/midi2_device/src/usb_descriptors.c | 18 | ||||
| -rw-r--r-- | src/class/midi/midi2_device.c | 163 | ||||
| -rw-r--r-- | src/class/midi/midi2_device.h | 67 |
5 files changed, 225 insertions, 46 deletions
diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c index ce052a20b..2c77652dc 100644 --- a/examples/device/midi2_device/src/main.c +++ b/examples/device/midi2_device/src/main.c @@ -546,6 +546,25 @@ void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { printf("[ALT] Host selected alt=%u\r\n", (unsigned)alt); } +// PROTOTYPE: two Function Blocks with independent directions. +// FB0: output only, group 0 FB1: input only, group 1 +static const uint8_t gtb_two_blocks[] = { + TUD_MIDI2_GTB_HEADER(2), + TUD_MIDI2_GTB_BLOCK(1, MIDI2_GTB_OUTPUT_ONLY, 0, 1, 0), // block 1: group 0 + TUD_MIDI2_GTB_BLOCK(2, MIDI2_GTB_INPUT_ONLY, 1, 1, 0), // block 2: group 1 +}; + +const uint8_t* tud_midi2_gtb_desc_cb(uint8_t itf, uint16_t* len) { + (void)itf; + *len = sizeof(gtb_two_blocks); + return gtb_two_blocks; +} + +const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx) { + (void)itf; + return (fb_idx == 0) ? "Synth Out" : "Keys In"; +} + //--------------------------------------------------------------------+ // Initial Setup - Program Change, CC, Per-Note Management //--------------------------------------------------------------------+ diff --git a/examples/device/midi2_device/src/tusb_config.h b/examples/device/midi2_device/src/tusb_config.h index 1ada0015f..e829c91c4 100644 --- a/examples/device/midi2_device/src/tusb_config.h +++ b/examples/device/midi2_device/src/tusb_config.h @@ -81,6 +81,10 @@ extern "C" { //------------- CLASS -------------// #define CFG_TUD_MIDI2 1 +// UMP Endpoint name shown by the host. Set to your device name; keep the USB +// iProduct string (usb_descriptors.c) in sync. +#define CFG_TUD_MIDI2_EP_NAME "TinyUSB MIDI 2.0" + #ifdef __cplusplus } #endif diff --git a/examples/device/midi2_device/src/usb_descriptors.c b/examples/device/midi2_device/src/usb_descriptors.c index f035f9c48..2cd6bf0f6 100644 --- a/examples/device/midi2_device/src/usb_descriptors.c +++ b/examples/device/midi2_device/src/usb_descriptors.c @@ -62,7 +62,7 @@ uint8_t const * tud_descriptor_device_cb(void) { enum { ITF_NUM_MIDI2 = 0, // Audio Control interface - ITF_NUM_MIDI2_STREAMING, // MIDI Streaming interface (auto-created by TUD_MIDI2_DESCRIPTOR) + ITF_NUM_MIDI2_STREAMING, // MIDI Streaming interface ITF_NUM_TOTAL }; @@ -80,8 +80,20 @@ static uint8_t const desc_fs_configuration[] = { // Config number, interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), - // MIDI 2.0 Interface - TUD_MIDI2_DESCRIPTOR(ITF_NUM_MIDI2, 0, EPNUM_MIDI2_OUT, EPNUM_MIDI2_IN, 64) + // MIDI 2.0 Interface, two Group Terminal Blocks associated per direction: + // bulk OUT (host to device) carries the input block (ID 2) + // bulk IN (device to host) carries the output block (ID 1) + // Alt Setting 0 (MIDI 1.0) + TUD_MIDI_DESC_HEAD(ITF_NUM_MIDI2, 0, 1), + TUD_MIDI_DESC_JACK_DESC(1, 0), + TUD_MIDI_DESC_EP(EPNUM_MIDI2_OUT, 64, 1), + TUD_MIDI_JACKID_IN_EMB(1), + TUD_MIDI_DESC_EP(EPNUM_MIDI2_IN, 64, 1), + TUD_MIDI_JACKID_OUT_EMB(1), + // Alt Setting 1 (UMP) + TUD_MIDI2_DESC_ALT1_HEAD(ITF_NUM_MIDI2, 0), + TUD_MIDI2_DESC_ALT1_EP(EPNUM_MIDI2_OUT, 64, 1, 2), + TUD_MIDI2_DESC_ALT1_EP(EPNUM_MIDI2_IN, 64, 1, 1) }; uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index 2ba7e442c..1d40a2efa 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -24,18 +24,18 @@ TU_ATTR_WEAK void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { (void) itf; ( TU_ATTR_WEAK bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request) { (void) rhport; (void) request; return false; } -TU_ATTR_WEAK uint8_t tud_midi2_num_groups_cb(uint8_t itf) { - (void) itf; return CFG_TUD_MIDI2_NUM_GROUPS; -} -TU_ATTR_WEAK uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf) { - (void) itf; return CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS; -} TU_ATTR_WEAK const char* tud_midi2_ep_name_cb(uint8_t itf) { (void) itf; return CFG_TUD_MIDI2_EP_NAME; } TU_ATTR_WEAK const char* tud_midi2_product_id_cb(uint8_t itf) { (void) itf; return CFG_TUD_MIDI2_PRODUCT_ID; } +TU_ATTR_WEAK const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx) { + (void) itf; (void) fb_idx; return NULL; +} +TU_ATTR_WEAK tud_midi2_stream_result_t tud_midi2_stream_msg_cb(uint8_t itf, const uint32_t* ump_words) { + (void) itf; (void) ump_words; return MIDI2_STREAM_PASS; +} //--------------------------------------------------------------------+ // Byte order note @@ -65,6 +65,7 @@ enum { STREAM_CONFIG_NOTIFY = 0x006, STREAM_FB_DISCOVERY = 0x010, STREAM_FB_INFO = 0x011, + STREAM_FB_NAME = 0x012, }; enum { @@ -72,11 +73,14 @@ enum { UMP_VER_MINOR = 1, }; -// Group Terminal Block descriptor types (USB-MIDI 2.0) +// Function Block Info Notification low byte: UI hint (bits 5:4) + direction +// (bits 1:0). See USB-MIDI 2.0 / UMP Function Block Info Notification. enum { - MIDI2_CS_GRP_TRM_BLOCK = 0x26, - MIDI2_GRP_TRM_BLOCK_HEADER = 0x01, - MIDI2_GRP_TRM_BLOCK_ENTRY = 0x02, + FB_DIR_INPUT = 0x1, + FB_DIR_OUTPUT = 0x2, + FB_DIR_BIDIR = 0x3, + FB_UI_RECEIVER = (1u << 4), + FB_UI_SENDER = (1u << 5), }; //--------------------------------------------------------------------+ @@ -121,8 +125,6 @@ CFG_TUD_MEM_SECTION static midi2d_epbuf_t _midi2d_epbuf[CFG_TUD_MIDI2]; TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_GROUPS >= 1 && CFG_TUD_MIDI2_NUM_GROUPS <= 16, "CFG_TUD_MIDI2_NUM_GROUPS must be 1..16"); -TU_VERIFY_STATIC(CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS >= 1 && CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS <= 32, - "CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS must be 1..32"); #define ITF_MEM_RESET_SIZE offsetof(midi2d_interface_t, ep_stream) @@ -150,6 +152,48 @@ static const uint8_t _default_gtb_desc[] = { 0, 0 // wMaxOutputBandwidth: unknown }; +// Map GTB bGrpTrmBlkType to the FB Info Notification low byte (UI hint + dir). +static inline uint8_t _fb_dir_byte(uint8_t gtb_type) { + switch (gtb_type) { + case MIDI2_GTB_INPUT_ONLY: return FB_UI_RECEIVER | FB_DIR_INPUT; + case MIDI2_GTB_OUTPUT_ONLY: return FB_UI_SENDER | FB_DIR_OUTPUT; + default: return FB_UI_RECEIVER | FB_UI_SENDER | FB_DIR_BIDIR; + } +} + +// Walk the GTB descriptor. Returns the number of block entries. When block +// `idx` exists, fills its type / first group / group count. +static uint8_t _gtb_blocks(const uint8_t* desc, uint16_t len, uint8_t idx, + uint8_t* type, uint8_t* first_group, uint8_t* num_groups) { + uint8_t count = 0; + uint16_t off = MIDI2_GTB_HEADER_LEN; // skip the list header + while (off + MIDI2_GTB_ENTRY_LEN <= len && desc[off] >= MIDI2_GTB_ENTRY_LEN) { + if (desc[off + 2] == MIDI2_GRP_TRM_BLOCK_ENTRY) { + if (count == idx) { + if (type) *type = desc[off + 4]; // bGrpTrmBlkType + if (first_group) *first_group = desc[off + 5]; // nGroupTrm + if (num_groups) *num_groups = desc[off + 6]; // nNumGroupTrm + } + count++; + } + off = (uint16_t)(off + desc[off]); + } + return count; +} + +static bool _gtb_desc_valid(const uint8_t* desc, uint16_t len) { + return desc != NULL && len >= TUD_MIDI2_GTB_DESC_LEN(1); +} + +// GTB descriptor source: the single source of truth for block topology. +// Override to expose multiple Group Terminal Blocks with independent +// directions and group spans. +TU_ATTR_WEAK const uint8_t* tud_midi2_gtb_desc_cb(uint8_t itf, uint16_t* len) { + (void) itf; + *len = (uint16_t) sizeof(_default_gtb_desc); + return _default_gtb_desc; +} + //--------------------------------------------------------------------+ // Common utility functions //--------------------------------------------------------------------+ @@ -158,6 +202,13 @@ static inline uint8_t _itf_idx(const midi2d_interface_t* p_midi) { return (uint8_t)(p_midi - _midi2d_itf); } +static uint8_t _gtb_block_count(midi2d_interface_t* p_midi) { + uint16_t len = 0; + const uint8_t* gtb = tud_midi2_gtb_desc_cb(_itf_idx(p_midi), &len); + TU_ASSERT(_gtb_desc_valid(gtb, len), 0); + return _gtb_blocks(gtb, len, 0xFF, NULL, NULL, NULL); +} + static inline bool _tx_opened(const midi2d_interface_t* p_midi) { return p_midi->ep_stream.tx.ep_addr != 0; } @@ -265,23 +316,31 @@ static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) { | ((uint32_t) UMP_VER_MAJOR << 8) | (uint32_t) UMP_VER_MINOR; msg[1] = (UINT32_C(1) << 31) // Static Function Blocks flag - | ((uint32_t)(tud_midi2_num_function_blocks_cb(_itf_idx(p_midi)) & 0x7F) << 24) + | ((uint32_t)(_gtb_block_count(p_midi) & 0x7F) << 24) | (UINT32_C(1) << 9) // MIDI 2.0 Protocol capability | (UINT32_C(1) << 8); // MIDI 1.0 Protocol capability _nego_send_ump(p_midi, msg, 4); } -static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, const char* str) { +// Send a UMP Stream text notification, multi-packet (Complete/Start/Continue/ +// End in the Format field). When has_index is set, word0 bits 15:8 carry an +// index byte (the Function Block number for FB Name) and 13 chars fit per +// packet; otherwise the text starts there and 14 chars fit (Endpoint Name, +// Product Instance Id). +static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, + bool has_index, uint8_t index, const char* str) { if (!str || str[0] == '\0') return; uint16_t total_len = (uint16_t) strlen(str); uint16_t offset = 0; + const uint8_t per_pkt = has_index ? 13 : 14; + const uint8_t head_chars = has_index ? 1 : 2; // chars carried in word0 while (offset < total_len) { uint16_t remaining = total_len - offset; - uint8_t n = (uint8_t)((remaining > 14) ? 14 : remaining); + uint8_t n = (uint8_t)((remaining > per_pkt) ? per_pkt : remaining); bool is_first = (offset == 0); - bool is_last = (remaining <= 14); + bool is_last = (remaining <= per_pkt); uint8_t form; if (is_first && is_last) form = 0; @@ -295,11 +354,16 @@ static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, | ((uint32_t) status << 16); const char* p = str + offset; - if (n > 0) msg[0] |= ((uint32_t)(uint8_t) p[0] << 8); - if (n > 1) msg[0] |= (uint32_t)(uint8_t) p[1]; - for (uint8_t i = 2; i < n; i++) { - uint8_t word_idx = (uint8_t)(1 + (i - 2) / 4); - uint8_t shift = (uint8_t)(24 - ((i - 2) % 4) * 8); + if (has_index) { + msg[0] |= ((uint32_t) index << 8); // bits 15:8 = index + if (n > 0) msg[0] |= (uint32_t)(uint8_t) p[0]; // bits 7:0 = char 0 + } else { + if (n > 0) msg[0] |= ((uint32_t)(uint8_t) p[0] << 8); // bits 15:8 = char 0 + if (n > 1) msg[0] |= (uint32_t)(uint8_t) p[1]; // bits 7:0 = char 1 + } + for (uint8_t i = head_chars; i < n; i++) { + uint8_t word_idx = (uint8_t)(1 + (i - head_chars) / 4); + uint8_t shift = (uint8_t)(24 - ((i - head_chars) % 4) * 8); msg[word_idx] |= ((uint32_t)(uint8_t) p[i] << shift); } @@ -317,25 +381,49 @@ static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protoco } static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { + // Derive direction and group span for this block from the GTB descriptor. + uint16_t gtb_len = 0; + const uint8_t* gtb = tud_midi2_gtb_desc_cb(_itf_idx(p_midi), >b_len); + TU_ASSERT(_gtb_desc_valid(gtb, gtb_len),); + uint8_t type = 0x00, first_group = 0, num_groups = (uint8_t) CFG_TUD_MIDI2_NUM_GROUPS; + _gtb_blocks(gtb, gtb_len, fb_idx, &type, &first_group, &num_groups); + uint32_t msg[4] = {0}; msg[0] = ((uint32_t) MT_STREAM << 28) | ((uint32_t) STREAM_FB_INFO << 16) | (UINT32_C(1) << 15) | ((uint32_t) fb_idx << 8) - | 0x02; // bDirection: bidirectional - msg[1] = ((uint32_t) 0 << 24) // bFirstGroup - | ((uint32_t) tud_midi2_num_groups_cb(_itf_idx(p_midi)) << 16); + | _fb_dir_byte(type); // UI hint + bDirection from the GTB block type + msg[1] = ((uint32_t) first_group << 24) + | ((uint32_t) num_groups << 16); _nego_send_ump(p_midi, msg, 4); } static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) { + // Let the application override this message before the built-in responder. + switch (tud_midi2_stream_msg_cb(_itf_idx(p_midi), words)) { + case MIDI2_STREAM_HANDLED: + return; + case MIDI2_STREAM_NEGOTIATED_MIDI1: + p_midi->protocol = MIDI_PROTOCOL_MIDI1; + p_midi->negotiated = true; + return; + case MIDI2_STREAM_NEGOTIATED_MIDI2: + p_midi->protocol = MIDI_PROTOCOL_MIDI2; + p_midi->negotiated = true; + return; + case MIDI2_STREAM_PASS: + default: + break; + } + uint16_t status = (words[0] >> 16) & 0x3FF; switch (status) { case STREAM_ENDPOINT_DISCOVERY: _nego_send_endpoint_info(p_midi); - _nego_send_stream_text(p_midi, STREAM_EP_NAME, tud_midi2_ep_name_cb(_itf_idx(p_midi))); - _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, tud_midi2_product_id_cb(_itf_idx(p_midi))); + _nego_send_stream_text(p_midi, STREAM_EP_NAME, false, 0, tud_midi2_ep_name_cb(_itf_idx(p_midi))); + _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, false, 0, tud_midi2_product_id_cb(_itf_idx(p_midi))); break; case STREAM_CONFIG_REQUEST: { @@ -350,13 +438,12 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* case STREAM_FB_DISCOVERY: { uint8_t fb_idx = (words[0] >> 8) & 0xFF; - uint8_t fb_count = tud_midi2_num_function_blocks_cb(_itf_idx(p_midi)); - if (fb_idx == 0xFF) { - for (uint8_t f = 0; f < fb_count; f++) { - _nego_send_fb_info(p_midi, f); - } - } else if (fb_idx < fb_count) { - _nego_send_fb_info(p_midi, fb_idx); + uint8_t filter = words[0] & 0xFF; // bit 0: FB Info, bit 1: FB Name + uint8_t fb_count = _gtb_block_count(p_midi); + for (uint8_t f = 0; f < fb_count; f++) { + if (fb_idx != 0xFF && fb_idx != f) continue; + if (filter & 0x01) _nego_send_fb_info(p_midi, f); + if (filter & 0x02) _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, tud_midi2_fb_name_cb(_itf_idx(p_midi), f)); } break; } @@ -700,11 +787,15 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re if (tud_midi2_get_req_itf_cb(rhport, request)) return true; + uint16_t gtb_len = 0; + const uint8_t* gtb = tud_midi2_gtb_desc_cb(idx, >b_len); + TU_ASSERT(_gtb_desc_valid(gtb, gtb_len), false); + uint16_t len = request->wLength; - if (len > sizeof(_default_gtb_desc)) { - len = sizeof(_default_gtb_desc); + if (len > gtb_len) { + len = gtb_len; } - tud_control_xfer(rhport, request, (void*)(uintptr_t) _default_gtb_desc, len); + tud_control_xfer(rhport, request, (void*)(uintptr_t) gtb, len); return true; } diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h index de4d85125..171b404b7 100644 --- a/src/class/midi/midi2_device.h +++ b/src/class/midi/midi2_device.h @@ -19,7 +19,7 @@ // Config defaults are in tusb_option.h: // CFG_TUD_MIDI2_RX_EPSIZE, CFG_TUD_MIDI2_TX_EPSIZE, // CFG_TUD_MIDI2_RX_BUFSIZE, CFG_TUD_MIDI2_TX_BUFSIZE, -// CFG_TUD_MIDI2_NUM_GROUPS, CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS, +// CFG_TUD_MIDI2_NUM_GROUPS, // CFG_TUD_MIDI2_EP_NAME, CFG_TUD_MIDI2_PRODUCT_ID #ifdef __cplusplus @@ -50,10 +50,6 @@ extern "C" { #define CFG_TUD_MIDI2_NUM_GROUPS 1 #endif -#ifndef CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS - #define CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS 1 -#endif - #ifndef CFG_TUD_MIDI2_EP_NAME #define CFG_TUD_MIDI2_EP_NAME "TinyUSB MIDI 2.0" #endif @@ -69,6 +65,39 @@ extern "C" { #endif //--------------------------------------------------------------------+ +// Group Terminal Block descriptor builders (USB-MIDI 2.0 Table 5-5/5-6) +//--------------------------------------------------------------------+ +// Group Terminal Block descriptor type and subtypes. +enum { + MIDI2_CS_GRP_TRM_BLOCK = 0x26, // bDescriptorType + MIDI2_GRP_TRM_BLOCK_HEADER = 0x01, // bDescriptorSubtype: list header + MIDI2_GRP_TRM_BLOCK_ENTRY = 0x02, // bDescriptorSubtype: block entry +}; + +// Block direction (bGrpTrmBlkType). +enum { + MIDI2_GTB_BIDIRECTIONAL = 0x00, + MIDI2_GTB_INPUT_ONLY = 0x01, + MIDI2_GTB_OUTPUT_ONLY = 0x02, +}; + +// GTB descriptor sizes (bytes). +enum { + MIDI2_GTB_HEADER_LEN = 5, // list header + MIDI2_GTB_ENTRY_LEN = 13, // one block entry +}; + +// Build a multi-block GTB descriptor for tud_midi2_gtb_desc_cb. Function Block +// Info is derived from these blocks (direction + group span). +#define TUD_MIDI2_GTB_DESC_LEN(_nblocks) (MIDI2_GTB_HEADER_LEN + MIDI2_GTB_ENTRY_LEN * (_nblocks)) +#define TUD_MIDI2_GTB_HEADER(_nblocks) \ + MIDI2_GTB_HEADER_LEN, MIDI2_CS_GRP_TRM_BLOCK, MIDI2_GRP_TRM_BLOCK_HEADER, \ + U16_TO_U8S_LE(TUD_MIDI2_GTB_DESC_LEN(_nblocks)) +#define TUD_MIDI2_GTB_BLOCK(_id, _type, _first_group, _num_groups, _stridx) \ + MIDI2_GTB_ENTRY_LEN, MIDI2_CS_GRP_TRM_BLOCK, MIDI2_GRP_TRM_BLOCK_ENTRY, \ + (_id), (_type), (_first_group), (_num_groups), (_stridx), 0x00, 0, 0, 0, 0 + +//--------------------------------------------------------------------+ // MIDI Protocol Values (returned by tud_midi2_n_protocol) //--------------------------------------------------------------------+ @@ -78,6 +107,17 @@ enum { MIDI_PROTOCOL_MIDI2 = 0x02, }; +// Result of the optional UMP Stream message callback (tud_midi2_stream_msg_cb). +// PASS lets the built-in responder handle the message; the others mean the +// application answered it. NEGOTIATED_* also record the protocol so +// tud_midi2_n_protocol / tud_midi2_n_negotiated stay accurate. +typedef enum { + MIDI2_STREAM_PASS = 0, + MIDI2_STREAM_HANDLED, + MIDI2_STREAM_NEGOTIATED_MIDI1, + MIDI2_STREAM_NEGOTIATED_MIDI2, +} tud_midi2_stream_result_t; + //--------------------------------------------------------------------+ // Application Callback API (weak, optional) //--------------------------------------------------------------------+ @@ -86,11 +126,24 @@ void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt); bool tud_midi2_get_req_itf_cb(uint8_t rhport, const tusb_control_request_t* request); // Per-interface UMP Stream config (override for per-itf values). -uint8_t tud_midi2_num_groups_cb(uint8_t itf); -uint8_t tud_midi2_num_function_blocks_cb(uint8_t itf); const char* tud_midi2_ep_name_cb(uint8_t itf); const char* tud_midi2_product_id_cb(uint8_t itf); +// Group Terminal Block descriptor source: the single source of truth for block +// topology. Override to expose multiple blocks with independent directions and +// group spans. Function Block Info is derived from this descriptor. +const uint8_t* tud_midi2_gtb_desc_cb(uint8_t itf, uint16_t* len); + +// Optional per-block name, sent as a Function Block Name Notification during +// discovery. Return NULL or "" for no name. +const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx); + +// Optional: intercept an incoming UMP Stream message (MT 0xF). Return PASS to +// let the built-in responder handle it, or HANDLED / NEGOTIATED_* if the app +// answered it (e.g. via tud_midi2_n_ump_write). Lets an app override a single +// response without reimplementing the whole negotiation. +tud_midi2_stream_result_t tud_midi2_stream_msg_cb(uint8_t itf, const uint32_t* ump_words); + //--------------------------------------------------------------------+ // Application API (Multiple Interfaces) //--------------------------------------------------------------------+ |
