summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZixun LI <[email protected]>2026-07-05 17:46:14 +0200
committerGitHub <[email protected]>2026-07-05 17:46:14 +0200
commit536157cb98fbc40329c9695281506fe7f04e526f (patch)
treeced35911cd15c1d6b7cca9b63b517168f123cc94
parent3579ea1888936bfe46b5a5635727707168b62223 (diff)
parent0309e7c016b5217734791ba5ad8f3fd8c82ac2e1 (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.c19
-rw-r--r--examples/device/midi2_device/src/tusb_config.h4
-rw-r--r--examples/device/midi2_device/src/usb_descriptors.c18
-rw-r--r--src/class/midi/midi2_device.c163
-rw-r--r--src/class/midi/midi2_device.h67
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), &gtb_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, &gtb_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)
//--------------------------------------------------------------------+