summaryrefslogtreecommitdiff
path: root/src/class/midi
diff options
context:
space:
mode:
Diffstat (limited to 'src/class/midi')
-rw-r--r--src/class/midi/midi.h18
-rw-r--r--src/class/midi/midi2_device.c769
-rw-r--r--src/class/midi/midi2_device.h193
-rw-r--r--src/class/midi/midi2_host.c635
-rw-r--r--src/class/midi/midi2_host.h107
-rw-r--r--src/class/midi/midi_device.c609
-rw-r--r--src/class/midi/midi_device.h157
-rw-r--r--src/class/midi/midi_host.c144
-rw-r--r--src/class/midi/midi_host.h69
9 files changed, 2213 insertions, 488 deletions
diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h
index cd67640e4..8121ec016 100644
--- a/src/class/midi/midi.h
+++ b/src/class/midi/midi.h
@@ -186,6 +186,24 @@ typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t;
TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct");
//--------------------------------------------------------------------+
+// MIDI 2.0 UMP Helpers
+//--------------------------------------------------------------------+
+
+// Return the number of 32-bit words for a UMP message given its Message Type
+static inline uint8_t midi2_ump_word_count(uint8_t mt) {
+ switch (mt) {
+ case 0x0: case 0x1: case 0x2: case 0x6: case 0x7:
+ return 1;
+ case 0x3: case 0x4: case 0x8: case 0x9: case 0xA:
+ return 2;
+ case 0xB: case 0xC:
+ return 3;
+ default: // 0x5, 0xD, 0xE, 0xF
+ return 4;
+ }
+}
+
+//--------------------------------------------------------------------+
// For Internal Driver Use
//--------------------------------------------------------------------+
typedef struct {
diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c
new file mode 100644
index 000000000..b14f439f8
--- /dev/null
+++ b/src/class/midi/midi2_device.c
@@ -0,0 +1,769 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if CFG_TUD_ENABLED && CFG_TUD_MIDI2
+
+#include <string.h>
+
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+#include "midi2_device.h"
+
+//--------------------------------------------------------------------+
+// Weak stubs
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_midi2_rx_cb(uint8_t itf) { (void) itf; }
+TU_ATTR_WEAK void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { (void) itf; (void) alt; }
+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;
+}
+
+//--------------------------------------------------------------------+
+// Byte order note
+//--------------------------------------------------------------------+
+// Per USB-MIDI 2.0 Section 3.2.2, each 32-bit UMP word is transmitted with the
+// least significant byte first. This driver reads and writes UMP words as
+// native uint32_t through tu_edpt_stream_read/write. All TinyUSB targets are
+// little-endian, so the in-memory layout already matches the wire order and no
+// swap is needed. If a big-endian target is ever supported, wrap access with
+// tu_htole32 / tu_le32toh at the buffer boundary.
+
+//--------------------------------------------------------------------+
+// UMP Stream Message Constants
+//--------------------------------------------------------------------+
+// UMP Message Type for Stream messages (bits 31:28)
+enum {
+ MT_STREAM = 0x0F,
+};
+
+// UMP Stream Status values (10-bit, bits 25:16)
+enum {
+ STREAM_ENDPOINT_DISCOVERY = 0x000,
+ STREAM_ENDPOINT_INFO = 0x001,
+ STREAM_EP_NAME = 0x003,
+ STREAM_PROD_INSTANCE_ID = 0x004,
+ STREAM_CONFIG_REQUEST = 0x005,
+ STREAM_CONFIG_NOTIFY = 0x006,
+ STREAM_FB_DISCOVERY = 0x010,
+ STREAM_FB_INFO = 0x011,
+};
+
+enum {
+ UMP_VER_MAJOR = 1,
+ UMP_VER_MINOR = 1,
+};
+
+// Group Terminal Block descriptor types (USB-MIDI 2.0)
+enum {
+ MIDI2_CS_GRP_TRM_BLOCK = 0x26,
+ MIDI2_GRP_TRM_BLOCK_HEADER = 0x01,
+ MIDI2_GRP_TRM_BLOCK_ENTRY = 0x02,
+};
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+typedef struct {
+ uint8_t ep_addr;
+ uint16_t mps;
+ tu_fifo_t ff;
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+ uint8_t* ep_buf;
+#endif
+} midi2d_tx_t;
+
+typedef struct {
+ uint8_t rhport;
+ uint8_t itf_num;
+ uint8_t alt_setting;
+ uint8_t protocol;
+ bool negotiated;
+
+ /*------------- From this point, data is not cleared by bus reset -------------*/
+ struct {
+ midi2d_tx_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUD_MIDI2_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUD_MIDI2_TX_BUFSIZE];
+ } ep_stream;
+} midi2d_interface_t;
+
+// Skip local EP buffer if dedicated hw FIFO is supported
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+typedef struct {
+ TUD_EPBUF_DEF(epin, CFG_TUD_MIDI2_TX_EPSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_MIDI2_RX_EPSIZE);
+} midi2d_epbuf_t;
+
+CFG_TUD_MEM_SECTION static midi2d_epbuf_t _midi2d_epbuf[CFG_TUD_MIDI2];
+#endif
+
+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)
+
+static midi2d_interface_t _midi2d_itf[CFG_TUD_MIDI2];
+
+// Default Group Terminal Block descriptor (USB-MIDI 2.0 spec, Table 5-5/5-6)
+static const uint8_t _default_gtb_desc[] = {
+ // GTB Header (5 bytes)
+ 5, // bLength
+ MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType
+ MIDI2_GRP_TRM_BLOCK_HEADER, // bDescriptorSubtype
+ U16_TO_U8S_LE(18), // wTotalLength (5 + 13 = 18)
+
+ // GTB Entry (13 bytes)
+ 13, // bLength
+ MIDI2_CS_GRP_TRM_BLOCK, // bDescriptorType
+ MIDI2_GRP_TRM_BLOCK_ENTRY, // bDescriptorSubtype
+ 1, // bGrpTrmBlkID
+ 0x00, // bGrpTrmBlkType: bidirectional
+ 0x00, // nGroupTrm: first group (0)
+ CFG_TUD_MIDI2_NUM_GROUPS, // nNumGroupTrm
+ CFG_TUD_MIDI2_BLOCK_STRIDX, // iBlockItem: string descriptor index (0 = none)
+ 0x00, // bMIDIProtocol: unknown/not fixed
+ 0, 0, // wMaxInputBandwidth: unknown
+ 0, 0 // wMaxOutputBandwidth: unknown
+};
+
+//--------------------------------------------------------------------+
+// Common utility functions
+//--------------------------------------------------------------------+
+
+static inline uint8_t _itf_idx(const midi2d_interface_t* p_midi) {
+ return (uint8_t)(p_midi - _midi2d_itf);
+}
+
+static inline bool _tx_opened(const midi2d_interface_t* p_midi) {
+ return p_midi->ep_stream.tx.ep_addr != 0;
+}
+
+static uint8_t _tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) {
+ if (offset < info->linear.len) {
+ return info->linear.ptr[offset];
+ }
+
+ offset = (uint16_t) (offset - info->linear.len);
+ if (offset < info->wrapped.len) {
+ return info->wrapped.ptr[offset];
+ }
+
+ return 0;
+}
+
+// Calculate the largest byte count that contains only whole UMP packets and
+// fits in one USB transfer (<= mps).
+static uint16_t _tx_nonseg_len_to_mps(midi2d_tx_t* tx) {
+ tu_fifo_buffer_info_t info;
+ tu_fifo_get_read_info(&tx->ff, &info);
+
+ const uint16_t available = (uint16_t) (info.linear.len + info.wrapped.len);
+ uint16_t bytes = 0;
+
+ while (bytes < tx->mps) {
+ if ((uint16_t) (available - bytes) < 4) break;
+
+ uint8_t mt = (uint8_t)((_tx_byte_at(&info, (uint16_t) (bytes + 3)) >> 4) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t) pkt_words * 4;
+
+ if (pkt_bytes == 0) break;
+ if ((uint16_t) (available - bytes) < pkt_bytes) break;
+ if ((uint16_t) (bytes + pkt_bytes) > tx->mps) break;
+
+ bytes = (uint16_t) (bytes + pkt_bytes);
+ }
+
+ return bytes;
+}
+
+// Start one IN transfer capped at mps, return number of bytes queued to the controller, or 0 if nothing was queued.
+static uint16_t _tx_start_xfer(midi2d_interface_t* p_midi) {
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+ uint16_t ff_count = tu_fifo_count(&tx->ff);
+
+ if (ff_count == 0) return 0;
+
+ if (!usbd_edpt_claim(p_midi->rhport, tx->ep_addr)) return 0;
+
+ uint16_t bytes;
+ if (p_midi->alt_setting == 1) {
+ bytes = _tx_nonseg_len_to_mps(tx);
+ } else {
+ bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps);
+ }
+ if (bytes == 0) {
+ usbd_edpt_release(p_midi->rhport, tx->ep_addr);
+ return 0;
+ }
+
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ TU_ASSERT(usbd_edpt_xfer_fifo(p_midi->rhport, tx->ep_addr, &tx->ff, bytes, false), 0);
+#else
+ tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes);
+ TU_ASSERT(usbd_edpt_xfer(p_midi->rhport, tx->ep_addr, tx->ep_buf, bytes, false), 0);
+#endif
+
+ return bytes;
+}
+
+static uint32_t _tx_ump_write(midi2d_interface_t* p_midi, const uint32_t* words, uint32_t count) {
+ uint32_t written = 0;
+ while (written < count) {
+ uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t) pkt_words * 4;
+
+ if (written + pkt_words > count) break;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break;
+
+ if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break;
+ written += pkt_words;
+ }
+
+ (void) _tx_start_xfer(p_midi);
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// Protocol Negotiation
+//--------------------------------------------------------------------+
+static void _nego_send_ump(midi2d_interface_t* p_midi, const uint32_t* words, uint8_t count) {
+ if (!_tx_opened(p_midi)) return;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < (uint32_t) count * 4) return;
+ (void) _tx_ump_write(p_midi, words, count);
+}
+
+static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_ENDPOINT_INFO << 16)
+ | ((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_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) {
+ if (!str || str[0] == '\0') return;
+
+ uint16_t total_len = (uint16_t) strlen(str);
+ uint16_t offset = 0;
+
+ while (offset < total_len) {
+ uint16_t remaining = total_len - offset;
+ uint8_t n = (uint8_t)((remaining > 14) ? 14 : remaining);
+ bool is_first = (offset == 0);
+ bool is_last = (remaining <= 14);
+
+ uint8_t form;
+ if (is_first && is_last) form = 0;
+ else if (is_first) form = 1;
+ else if (is_last) form = 3;
+ else form = 2;
+
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) form << 26)
+ | ((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);
+ msg[word_idx] |= ((uint32_t)(uint8_t) p[i] << shift);
+ }
+
+ _nego_send_ump(p_midi, msg, 4);
+ offset += n;
+ }
+}
+
+static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protocol) {
+ uint32_t msg[4] = {0};
+ msg[0] = ((uint32_t) MT_STREAM << 28)
+ | ((uint32_t) STREAM_CONFIG_NOTIFY << 16)
+ | ((uint32_t) protocol << 8);
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) {
+ 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);
+ _nego_send_ump(p_midi, msg, 4);
+}
+
+static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) {
+ 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)));
+ break;
+
+ case STREAM_CONFIG_REQUEST: {
+ uint8_t req_proto = (words[0] >> 8) & 0xFF;
+ if (req_proto == MIDI_PROTOCOL_MIDI1 || req_proto == MIDI_PROTOCOL_MIDI2) {
+ p_midi->protocol = req_proto;
+ }
+ _nego_send_config_notify(p_midi, p_midi->protocol);
+ p_midi->negotiated = true;
+ break;
+ }
+
+ 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);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void _nego_process_rx(midi2d_interface_t* p_midi) {
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+ uint8_t word_bytes[4];
+
+ while (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) == 4) {
+ // UMP words travel LSB-first on the wire and in LE memory, so MT is in
+ // the high nibble of byte 3, not byte 0.
+ uint8_t mt = (word_bytes[3] >> 4) & 0x0F;
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint32_t pkt_bytes = (uint32_t)pkt_words * 4;
+
+ if (mt != MT_STREAM) break;
+ if (tu_edpt_stream_read_available(ep_rx) < pkt_bytes) break;
+
+ uint32_t buf[4] = {0};
+ tu_edpt_stream_read(ep_rx, buf, pkt_bytes);
+ _nego_handle_stream_msg(p_midi, buf);
+ }
+}
+
+//--------------------------------------------------------------------+
+// READ API
+//--------------------------------------------------------------------+
+bool tud_midi2_n_mounted(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return _tx_opened(p_midi) &&
+ tu_edpt_stream_is_opened(&p_midi->ep_stream.rx);
+}
+
+uint32_t tud_midi2_n_available(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return tu_edpt_stream_read_available(&p_midi->ep_stream.rx) / 4;
+}
+
+uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && max_words > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+
+ // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0).
+ // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words.
+ if (p_midi->alt_setting != 1) { return 0; }
+
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+
+ uint32_t total_read = 0;
+ while (total_read < max_words) {
+ uint8_t word_bytes[4];
+ if (tu_fifo_peek_n(&ep_rx->ff, word_bytes, 4) < 4) break;
+
+ // UMP words travel LSB-first; MT is the high nibble of byte 3, not byte 0.
+ uint8_t mt = (word_bytes[3] >> 4) & 0x0F;
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+
+ if (total_read + pkt_words > max_words) break;
+ if (tu_edpt_stream_read_available(ep_rx) < (uint32_t)pkt_words * 4) break;
+
+ tu_edpt_stream_read(ep_rx, &words[total_read], pkt_words * 4);
+ total_read += pkt_words;
+ }
+
+ return total_read;
+}
+
+uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && max_packets > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ return tu_edpt_stream_read(&p_midi->ep_stream.rx, packets, max_packets * 4u) >> 2u;
+}
+
+//--------------------------------------------------------------------+
+// WRITE API
+//--------------------------------------------------------------------+
+uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && words != NULL && count > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+
+ // UMP API is only valid on Alt Setting 1 (USB-MIDI 2.0).
+ // Alt 0 carries USB-MIDI 1.0 32-bit Event Packets, not UMP words.
+ if (p_midi->alt_setting != 1) { return 0; }
+ TU_VERIFY(_tx_opened(p_midi), 0);
+
+ return _tx_ump_write(p_midi, words, count);
+}
+
+uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2 && packets != NULL && count > 0, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[itf];
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+
+ // Packet API is for Alt Setting 0 (USB-MIDI 1.0) event packets.
+ TU_VERIFY(p_midi->alt_setting == 0, 0);
+ TU_VERIFY(_tx_opened(p_midi), 0);
+
+ uint32_t written = 0;
+ while (written < count) {
+ if (tu_fifo_remaining(&tx->ff) < 4) break;
+
+ if (tu_fifo_write_n(&tx->ff, packets + written * 4u, 4) != 4) break;
+ written++;
+ }
+
+ (void) _tx_start_xfer(p_midi);
+
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// STATE GETTERS
+//--------------------------------------------------------------------+
+uint8_t tud_midi2_n_alt_setting(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ return _midi2d_itf[itf].alt_setting;
+}
+
+bool tud_midi2_n_negotiated(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, false);
+ return _midi2d_itf[itf].negotiated;
+}
+
+uint8_t tud_midi2_n_protocol(uint8_t itf) {
+ TU_VERIFY(itf < CFG_TUD_MIDI2, 0);
+ return _midi2d_itf[itf].protocol;
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+void midi2d_init(void) {
+ tu_memclr(_midi2d_itf, sizeof(_midi2d_itf));
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ uint8_t *epout_buf = NULL;
+ uint8_t *epin_buf = NULL;
+ #else
+ uint8_t *epout_buf = _midi2d_epbuf[i].epout;
+ uint8_t *epin_buf = _midi2d_epbuf[i].epin;
+ #endif
+
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false,
+ p_midi->ep_stream.rx_ff_buf, CFG_TUD_MIDI2_RX_BUFSIZE, epout_buf);
+
+ midi2d_tx_t* tx = &p_midi->ep_stream.tx;
+ (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI2_TX_BUFSIZE, false);
+#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+ tx->ep_buf = epin_buf;
+#else
+ (void) epin_buf;
+#endif
+ }
+}
+
+bool midi2d_deinit(void) {
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ }
+ return true;
+}
+
+void midi2d_reset(uint8_t rhport) {
+ (void) rhport;
+ for (uint8_t i = 0; i < CFG_TUD_MIDI2; i++) {
+ midi2d_interface_t* p_midi = &_midi2d_itf[i];
+ tu_memclr(p_midi, ITF_MEM_RESET_SIZE);
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
+ }
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t find_midi2_itf(uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) {
+ const midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static uint8_t find_midi2_itf_by_num(uint8_t itf_num) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI2; idx++) {
+ if (_midi2d_itf[idx].itf_num == itf_num) return idx;
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) {
+ const uint8_t* p_desc = (const uint8_t*) desc_itf;
+ const uint8_t* desc_end = p_desc + max_len;
+
+ // 1st Interface: Audio Control v1 (optional)
+ if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass &&
+ AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) {
+ p_desc = tu_desc_next(desc_itf);
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ // 2nd Interface: MIDI Streaming
+ TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
+ const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass &&
+ AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol,
+ 0);
+
+ uint8_t idx = find_midi2_itf(0);
+ TU_ASSERT(idx < CFG_TUD_MIDI2, 0);
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+
+ p_midi->rhport = rhport;
+ p_midi->itf_num = desc_midi->bInterfaceNumber;
+ p_midi->alt_setting = 0;
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+ p_midi->negotiated = false;
+
+ p_desc = tu_desc_next(p_desc);
+
+ // Skip class-specific descriptors
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Find and open endpoint descriptors
+ uint8_t found_ep = 0;
+ while ((found_ep < desc_midi->bNumEndpoints) && tu_desc_in_bounds(p_desc, desc_end)) {
+ if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
+ const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc;
+ TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+ const uint8_t ep_addr = desc_ep->bEndpointAddress;
+
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ p_midi->ep_stream.tx.ep_addr = ep_addr;
+ p_midi->ep_stream.tx.mps = tu_edpt_packet_size(desc_ep);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ } else {
+ tu_edpt_stream_open(&p_midi->ep_stream.rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep));
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ TU_ASSERT(tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx) > 0, 0);
+ }
+
+ found_ep++;
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Skip remaining descriptors (alt setting 1, CS endpoints, GTB)
+ // Stop at any interface descriptor that is not our MIDI Streaming alt setting
+ while (tu_desc_in_bounds(p_desc, desc_end)) {
+ uint8_t dtype = tu_desc_type(p_desc);
+
+ if (dtype == TUSB_DESC_INTERFACE) {
+ const tusb_desc_interface_t* next_itf = (const tusb_desc_interface_t*) p_desc;
+ // Continue only if this is an alternate setting of our own interface
+ if (next_itf->bInterfaceNumber != desc_midi->bInterfaceNumber) break;
+ } else if (dtype != TUSB_DESC_CS_INTERFACE && dtype != TUSB_DESC_CS_ENDPOINT &&
+ dtype != TUSB_DESC_ENDPOINT) {
+ break;
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ return (uint16_t)(p_desc - (const uint8_t*) desc_itf);
+}
+
+bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) {
+ TU_LOG2("MIDI2 ctrl: stage=%u bRequest=0x%02X wValue=0x%04X wIndex=0x%04X wLength=%u\r\n",
+ stage, request->bRequest, request->wValue, request->wIndex, request->wLength);
+
+ if (stage != CONTROL_STAGE_SETUP) return true;
+
+ switch (request->bRequest) {
+ case TUSB_REQ_SET_INTERFACE: {
+ uint8_t itf_num = tu_u16_low(request->wIndex);
+ uint8_t alt = tu_u16_low(request->wValue);
+
+ // Only Alt Setting 0 (MIDI 1.0) and 1 (UMP) are valid
+ if (alt > 1) return false;
+
+ uint8_t idx = find_midi2_itf_by_num(itf_num);
+ if (idx >= CFG_TUD_MIDI2) return false;
+
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+ p_midi->alt_setting = alt;
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+
+ if (alt == 1) {
+ p_midi->negotiated = false;
+ p_midi->protocol = MIDI_PROTOCOL_MIDI2;
+ }
+
+ // Re-arm RX endpoint for receiving data after alt setting change
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+
+ tud_midi2_set_itf_cb(idx, alt);
+ tud_control_status(rhport, request);
+ return true;
+ }
+
+ case TUSB_REQ_GET_DESCRIPTOR: {
+ // USB-MIDI 2.0 Section 6: GTB descriptor retrieval
+ // bmRequestType = 0x81 (Device-to-Host, Standard, Interface)
+ // wValue = CS_GR_TRM_BLOCK (0x26) in high byte, alt setting in low byte
+ // wIndex = interface number
+ if (request->bmRequestType_bit.direction != TUSB_DIR_IN) return false;
+ if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_STANDARD) return false;
+ if (request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_INTERFACE) return false;
+ if (tu_u16_high(request->wValue) != MIDI2_CS_GRP_TRM_BLOCK) return false;
+
+ uint8_t itf_num = tu_u16_low(request->wIndex);
+ uint8_t idx = find_midi2_itf_by_num(itf_num);
+ if (idx >= CFG_TUD_MIDI2) return false;
+
+ // Only Alt Setting 1 exposes Group Terminal Block descriptors.
+ if (tu_u16_low(request->wValue) != 0x01) return false;
+
+ if (tud_midi2_get_req_itf_cb(rhport, request)) return true;
+
+ uint16_t len = request->wLength;
+ if (len > sizeof(_default_gtb_desc)) {
+ len = sizeof(_default_gtb_desc);
+ }
+ tud_control_xfer(rhport, request, (void*)(uintptr_t) _default_gtb_desc, len);
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) rhport;
+
+ uint8_t idx = find_midi2_itf(ep_addr);
+ TU_ASSERT(idx < CFG_TUD_MIDI2);
+ midi2d_interface_t* p_midi = &_midi2d_itf[idx];
+
+ tu_edpt_stream_t* ep_rx = &p_midi->ep_stream.rx;
+ midi2d_tx_t* ep_tx = &p_midi->ep_stream.tx;
+
+ if (ep_addr == ep_rx->ep_addr) {
+ if (result == XFER_RESULT_SUCCESS) {
+ tu_edpt_stream_read_xfer_complete(ep_rx, xferred_bytes);
+ if (p_midi->alt_setting == 1) {
+ _nego_process_rx(p_midi);
+ }
+ tud_midi2_rx_cb(idx);
+ }
+ tu_edpt_stream_read_xfer(ep_rx);
+ } else if (ep_addr == ep_tx->ep_addr && result == XFER_RESULT_SUCCESS) {
+ uint16_t queued = _tx_start_xfer(p_midi);
+ // Send ZLP if no more data is queued but the last transfer was exactly mps
+ if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 &&
+ (0 == (xferred_bytes & (ep_tx->mps - 1)))) {
+ if (usbd_edpt_claim(rhport, ep_tx->ep_addr)) {
+ usbd_edpt_xfer(rhport, ep_tx->ep_addr, NULL, 0, false);
+ }
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+#endif
diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h
new file mode 100644
index 000000000..e53535693
--- /dev/null
+++ b/src/class/midi/midi2_device.h
@@ -0,0 +1,193 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MIDI2_DEVICE_H_
+#define TUSB_MIDI2_DEVICE_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+// 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_EP_NAME, CFG_TUD_MIDI2_PRODUCT_ID
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+
+#ifndef CFG_TUD_MIDI2_TX_EPSIZE
+ #define CFG_TUD_MIDI2_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_MIDI2_RX_EPSIZE
+ #define CFG_TUD_MIDI2_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+#endif
+
+#ifndef CFG_TUD_MIDI2_TX_BUFSIZE
+ #define CFG_TUD_MIDI2_TX_BUFSIZE CFG_TUD_MIDI2_TX_EPSIZE
+#endif
+
+#ifndef CFG_TUD_MIDI2_RX_BUFSIZE
+ #define CFG_TUD_MIDI2_RX_BUFSIZE CFG_TUD_MIDI2_RX_EPSIZE
+#endif
+
+#ifndef CFG_TUD_MIDI2_NUM_GROUPS
+ #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
+
+#ifndef CFG_TUD_MIDI2_PRODUCT_ID
+ #define CFG_TUD_MIDI2_PRODUCT_ID "TinyUSB-MIDI2"
+#endif
+
+// String descriptor index for the Group Terminal Block (iBlockItem, Table 5-6).
+// 0 = no string descriptor (default, spec-allowed).
+#ifndef CFG_TUD_MIDI2_BLOCK_STRIDX
+ #define CFG_TUD_MIDI2_BLOCK_STRIDX 0
+#endif
+
+//--------------------------------------------------------------------+
+// MIDI Protocol Values (returned by tud_midi2_n_protocol)
+//--------------------------------------------------------------------+
+
+// Per USB-MIDI 2.0 spec, UMP Stream Configuration messages.
+enum {
+ MIDI_PROTOCOL_MIDI1 = 0x01,
+ MIDI_PROTOCOL_MIDI2 = 0x02,
+};
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak, optional)
+//--------------------------------------------------------------------+
+void tud_midi2_rx_cb(uint8_t itf);
+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);
+
+//--------------------------------------------------------------------+
+// Application API (Multiple Interfaces)
+//--------------------------------------------------------------------+
+
+bool tud_midi2_n_mounted(uint8_t itf);
+uint32_t tud_midi2_n_available(uint8_t itf);
+uint8_t tud_midi2_n_alt_setting(uint8_t itf);
+bool tud_midi2_n_negotiated(uint8_t itf);
+uint8_t tud_midi2_n_protocol(uint8_t itf);
+
+// Read up to max_words UMP words from the RX FIFO. Returns the number of
+// words actually read (0 if FIFO is empty).
+//
+// NOTE: this function returns when max_words is reached or when the FIFO is
+// empty, whichever comes first. Applications should invoke it in a loop
+// until it returns 0 to guarantee the RX FIFO is fully drained per
+// tud_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can
+// prevent subsequent bulk OUT transfers from landing.
+uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words);
+uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count);
+
+uint32_t tud_midi2_n_packet_read(uint8_t itf, uint8_t packets[], uint32_t max_packets);
+uint32_t tud_midi2_n_packet_write(uint8_t itf, const uint8_t packets[], uint32_t count);
+
+//--------------------------------------------------------------------+
+// Application API (Single Interface)
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_mounted(void) {
+ return tud_midi2_n_mounted(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi2_available(void) {
+ return tud_midi2_n_available(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_alt_setting(void) {
+ return tud_midi2_n_alt_setting(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi2_negotiated(void) {
+ return tud_midi2_n_negotiated(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_midi2_protocol(void) {
+ return tud_midi2_n_protocol(0);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_ump_read(uint32_t* words, uint32_t max_words) {
+ return tud_midi2_n_ump_read(0, words, max_words);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_ump_write(const uint32_t* words, uint32_t count) {
+ return tud_midi2_n_ump_write(0, words, count);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_packet_read(uint8_t packets[], uint32_t max_packets) {
+ return tud_midi2_n_packet_read(0, packets, max_packets);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi2_packet_write(const uint8_t packets[], uint32_t count) {
+ return tud_midi2_n_packet_write(0, packets, count);
+}
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+void midi2d_init(void);
+bool midi2d_deinit(void);
+void midi2d_reset(uint8_t rhport);
+uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len);
+bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request);
+bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/midi/midi2_host.c b/src/class/midi/midi2_host.c
new file mode 100644
index 000000000..6d8861f4b
--- /dev/null
+++ b/src/class/midi/midi2_host.c
@@ -0,0 +1,635 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (CFG_TUH_ENABLED && CFG_TUH_MIDI2)
+
+#include "host/usbh.h"
+#include "host/usbh_pvt.h"
+#include "midi2_host.h"
+
+#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI2_LOG_LEVEL, __VA_ARGS__)
+
+//--------------------------------------------------------------------+
+// Weak stubs for application callbacks
+//--------------------------------------------------------------------+
+
+TU_ATTR_WEAK void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data) {
+ (void) idx; (void) desc_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data) {
+ (void) idx; (void) mount_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void) idx; (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) {
+ (void) idx; (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_midi2_umount_cb(uint8_t idx) {
+ (void) idx;
+}
+
+//--------------------------------------------------------------------+
+// Internal structure and state
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t ep_addr;
+ uint16_t mps;
+ tu_fifo_t ff;
+ uint8_t* ep_buf;
+} midih2_tx_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+
+ uint8_t alt_setting_current;
+
+ uint8_t protocol_version;
+ uint8_t bcdMSC_hi, bcdMSC_lo;
+ uint8_t rx_cable_count_alt0;
+ uint8_t tx_cable_count_alt0;
+ uint8_t rx_cable_count_alt1;
+ uint8_t tx_cable_count_alt1;
+
+ struct {
+ midih2_tx_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUH_MIDI2_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUH_MIDI2_TX_BUFSIZE];
+ } ep_stream;
+
+ bool mounted;
+} midih2_interface_t;
+
+static midih2_interface_t _midi2_host[CFG_TUH_MIDI2];
+
+typedef struct {
+ TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX);
+ TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX);
+} midih2_epbuf_t;
+
+CFG_TUH_MEM_SECTION static midih2_epbuf_t _midi2_epbuf[CFG_TUH_MIDI2];
+
+//--------------------------------------------------------------------+
+// Helper functions
+//--------------------------------------------------------------------+
+
+static inline uint8_t find_new_midi2_index(void) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == 0) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ const midih2_interface_t *p_midi = &_midi2_host[idx];
+ if ((p_midi->daddr == daddr) &&
+ (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline bool _tuh_tx_opened(const midih2_interface_t* p_midi) {
+ return p_midi->ep_stream.tx.ep_addr != 0;
+}
+
+static uint8_t _tuh_tx_byte_at(const tu_fifo_buffer_info_t* info, uint16_t offset) {
+ if (offset < info->linear.len) {
+ return info->linear.ptr[offset];
+ }
+ offset = (uint16_t)(offset - info->linear.len);
+ if (offset < info->wrapped.len) {
+ return info->wrapped.ptr[offset];
+ }
+ return 0;
+}
+
+// Largest byte count containing only whole UMP packets and fitting one xfer.
+static uint16_t _tuh_tx_nonseg_len_to_mps(midih2_tx_t* tx) {
+ tu_fifo_buffer_info_t info;
+ tu_fifo_get_read_info(&tx->ff, &info);
+
+ const uint16_t available = (uint16_t)(info.linear.len + info.wrapped.len);
+ uint16_t bytes = 0;
+
+ while (bytes < tx->mps) {
+ if ((uint16_t)(available - bytes) < 4) break;
+
+ uint8_t mt = (uint8_t)((_tuh_tx_byte_at(&info, (uint16_t)(bytes + 3)) >> 4) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
+
+ if (pkt_bytes == 0) break;
+ if ((uint16_t)(available - bytes) < pkt_bytes) break;
+ if ((uint16_t)(bytes + pkt_bytes) > tx->mps) break;
+
+ bytes = (uint16_t)(bytes + pkt_bytes);
+ }
+
+ return bytes;
+}
+
+// Start one OUT transfer capped at mps. Returns bytes queued, or 0 if nothing.
+static uint16_t _tuh_tx_start_xfer(midih2_interface_t* p_midi) {
+ midih2_tx_t* tx = &p_midi->ep_stream.tx;
+ uint16_t ff_count = tu_fifo_count(&tx->ff);
+ if (ff_count == 0) return 0;
+ if (!usbh_edpt_claim(p_midi->daddr, tx->ep_addr)) return 0;
+
+ uint16_t bytes;
+ if (p_midi->alt_setting_current == 1) {
+ bytes = _tuh_tx_nonseg_len_to_mps(tx);
+ } else {
+ bytes = tu_min16(tu_fifo_count(&tx->ff), tx->mps);
+ }
+ if (bytes == 0) {
+ usbh_edpt_release(p_midi->daddr, tx->ep_addr);
+ return 0;
+ }
+
+ tu_fifo_read_n(&tx->ff, tx->ep_buf, bytes);
+ TU_ASSERT(usbh_edpt_xfer(p_midi->daddr, tx->ep_addr, tx->ep_buf, bytes), 0);
+ return bytes;
+}
+
+static uint32_t _tuh_tx_ump_write(midih2_interface_t* p_midi, const uint32_t* words, uint32_t count) {
+ uint32_t written = 0;
+ while (written < count) {
+ uint8_t mt = (uint8_t)((words[written] >> 28) & 0x0F);
+ uint8_t pkt_words = midi2_ump_word_count(mt);
+ uint16_t pkt_bytes = (uint16_t)(pkt_words * 4);
+
+ if (written + pkt_words > count) break;
+ if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < pkt_bytes) break;
+ if (tu_fifo_write_n(&p_midi->ep_stream.tx.ff, &words[written], pkt_bytes) != pkt_bytes) break;
+ written += pkt_words;
+ }
+
+ (void) _tuh_tx_start_xfer(p_midi);
+ return written;
+}
+
+//--------------------------------------------------------------------+
+// Descriptor parsing
+//--------------------------------------------------------------------+
+
+// Parse Alt Setting 0 (MIDI 1.0) descriptors. Returns pointer past last consumed descriptor.
+static const uint8_t* midih2_parse_descriptors_alt0(midih2_interface_t *p_midi,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) {
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL);
+
+ p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber;
+
+ const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ p_desc = tu_desc_next(p_desc);
+
+ uint8_t rx_cable_count = 0;
+ uint8_t tx_cable_count = 0;
+ bool found_new_interface = false;
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+
+ TU_ASSERT(tuh_edpt_open(p_midi->daddr, p_ep), NULL);
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ tu_edpt_stream_open(&p_midi->ep_stream.rx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep));
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ } else {
+ p_midi->ep_stream.tx.ep_addr = p_ep->bEndpointAddress;
+ p_midi->ep_stream.tx.mps = tu_edpt_packet_size(p_ep);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ }
+
+ p_desc = tu_desc_next(p_desc);
+ if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) {
+ const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc;
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
+ tx_cable_count = p_csep->bNumEmbMIDIJack;
+ } else {
+ rx_cable_count = p_csep->bNumEmbMIDIJack;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!found_new_interface) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ p_midi->rx_cable_count_alt0 = rx_cable_count;
+ p_midi->tx_cable_count_alt0 = tx_cable_count;
+ return p_desc;
+}
+
+// Parse Alt Setting 1 (MIDI 2.0 UMP) descriptors. Returns pointer past last consumed descriptor.
+static const uint8_t* midih2_parse_descriptors_alt1(midih2_interface_t *p_midi,
+ const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) {
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL);
+ TU_VERIFY(desc_itf->bAlternateSetting == 1, NULL);
+
+ const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ p_desc = tu_desc_next(p_desc);
+
+ uint8_t rx_cable_count = 0;
+ uint8_t tx_cable_count = 0;
+ bool found_new_interface = false;
+
+ while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_CS_INTERFACE:
+ if (tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_HEADER) {
+ // bcdMSC at offset 3-4 in CS Interface Header
+ const uint8_t *bcd_ptr = p_desc + 3;
+ p_midi->bcdMSC_lo = bcd_ptr[0];
+ p_midi->bcdMSC_hi = bcd_ptr[1];
+ if (p_midi->bcdMSC_hi == 0x02) { // bcdMSC 0x0200 = USB-MIDI 2.0
+ p_midi->protocol_version = 1;
+ }
+ }
+ break;
+
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+ p_desc = tu_desc_next(p_desc);
+
+ if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) {
+ // MIDI 2.0 CS Endpoint General 2.0: bNumGrpTrmBlk at offset 3
+ if (p_desc[0] >= 4 && p_desc[2] == MIDI_CS_ENDPOINT_GENERAL_2_0) {
+ uint8_t num_grp_trm_blk = p_desc[3];
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
+ tx_cable_count = num_grp_trm_blk;
+ } else {
+ rx_cable_count = num_grp_trm_blk;
+ }
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (!found_new_interface) {
+ p_desc = tu_desc_next(p_desc);
+ }
+ }
+
+ p_midi->rx_cable_count_alt1 = rx_cable_count;
+ p_midi->tx_cable_count_alt1 = tx_cable_count;
+ return p_desc;
+}
+
+//--------------------------------------------------------------------+
+// Auto-selection logic
+//--------------------------------------------------------------------+
+
+static void midih2_auto_select_alt_setting(midih2_interface_t *p_midi) {
+ p_midi->alt_setting_current = 0;
+ if (p_midi->protocol_version == 1) {
+ p_midi->alt_setting_current = 1;
+ }
+}
+
+//--------------------------------------------------------------------+
+// Init/Deinit
+//--------------------------------------------------------------------+
+
+bool midih2_init(void) {
+ tu_memclr(&_midi2_host, sizeof(_midi2_host));
+ for (int inst = 0; inst < CFG_TUH_MIDI2; inst++) {
+ midih2_interface_t *p_midi = &_midi2_host[inst];
+
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, true, false, false,
+ p_midi->ep_stream.rx_ff_buf, CFG_TUH_MIDI2_RX_BUFSIZE, _midi2_epbuf[inst].rx);
+
+ // TX uses raw tu_fifo + direct usbh_edpt_xfer (no FIFO wrapper) to preserve
+ // UMP packet boundaries across USB transfers.
+ midih2_tx_t* tx = &p_midi->ep_stream.tx;
+ (void) tu_fifo_config(&tx->ff, p_midi->ep_stream.tx_ff_buf, CFG_TUH_MIDI2_TX_BUFSIZE, false);
+ tx->ep_buf = _midi2_epbuf[inst].tx;
+ }
+ return true;
+}
+
+bool midih2_deinit(void) {
+ for (size_t i = 0; i < CFG_TUH_MIDI2; i++) {
+ midih2_interface_t* p_midi = &_midi2_host[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ }
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Class driver callbacks
+//--------------------------------------------------------------------+
+
+uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
+ (void) rhport;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+
+ // For Alt Setting 1, reuse existing slot for same device+interface
+ uint8_t idx = TUSB_INDEX_INVALID_8;
+ if (desc_itf->bAlternateSetting > 0) {
+ for (uint8_t i = 0; i < CFG_TUH_MIDI2; i++) {
+ if (_midi2_host[i].daddr == dev_addr &&
+ _midi2_host[i].bInterfaceNumber == desc_itf->bInterfaceNumber) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ if (idx == TUSB_INDEX_INVALID_8) {
+ idx = find_new_midi2_index();
+ }
+ TU_VERIFY(idx < CFG_TUH_MIDI2, 0);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ p_midi->daddr = dev_addr;
+
+ const uint8_t *desc_start = (const uint8_t *) desc_itf;
+ const uint8_t *desc_end = desc_start + max_len;
+
+ // Skip Audio Control interface and any non-MIDI-Streaming descriptors
+ // (following midi_host.c pattern from Ha Thach)
+ if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) {
+ const uint8_t *p_desc = tu_desc_next((const uint8_t *)desc_itf);
+ // Skip CS_INTERFACE header
+ TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE, 0);
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *) p_desc;
+ // Skip until we find MIDI Streaming interface
+ while (tu_desc_in_bounds(p_desc, desc_end) &&
+ (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE ||
+ (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO &&
+ desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) {
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *) p_desc;
+ }
+ TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0);
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+ }
+
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, 0);
+
+ TU_LOG_DRV("MIDI2 opening Interface %u Alt %u (addr = %u)\r\n",
+ desc_itf->bInterfaceNumber, desc_itf->bAlternateSetting, dev_addr);
+
+ // Dispatch to appropriate parser based on Alt Setting
+ const uint8_t *p_end = NULL;
+ if (desc_itf->bAlternateSetting == 0) {
+ p_end = midih2_parse_descriptors_alt0(p_midi, desc_itf, desc_end);
+ } else if (desc_itf->bAlternateSetting == 1) {
+ p_end = midih2_parse_descriptors_alt1(p_midi, desc_itf, desc_end);
+ }
+
+ // Return number of bytes consumed (following midi_host.c pattern)
+ uint16_t const parsed_len = (p_end != NULL) ? (uint16_t)(p_end - desc_start) : 0;
+ return parsed_len;
+}
+
+static void midih2_set_config_complete(midih2_interface_t *p_midi, uint8_t idx) {
+ uint8_t dev_addr = p_midi->daddr;
+
+ // Invoke descriptor_cb
+ tuh_midi2_descriptor_cb_t desc_cb = {
+ .protocol_version = p_midi->protocol_version,
+ .bcdMSC_hi = p_midi->bcdMSC_hi,
+ .bcdMSC_lo = p_midi->bcdMSC_lo,
+ .rx_cable_count = (p_midi->alt_setting_current == 0) ?
+ p_midi->rx_cable_count_alt0 : p_midi->rx_cable_count_alt1,
+ .tx_cable_count = (p_midi->alt_setting_current == 0) ?
+ p_midi->tx_cable_count_alt0 : p_midi->tx_cable_count_alt1,
+ };
+ tuh_midi2_descriptor_cb(idx, &desc_cb);
+
+ // Mark as mounted
+ TU_LOG_DRV("MIDI2 mounted addr = %u, alt = %u, protocol = %u\r\n",
+ dev_addr, p_midi->alt_setting_current, p_midi->protocol_version);
+ p_midi->mounted = true;
+
+ // Invoke mount_cb
+ tuh_midi2_mount_cb_t mount_cb = {
+ .daddr = dev_addr,
+ .bInterfaceNumber = p_midi->bInterfaceNumber,
+ .protocol_version = p_midi->protocol_version,
+ .alt_setting_active = p_midi->alt_setting_current,
+ .rx_cable_count = desc_cb.rx_cable_count,
+ .tx_cable_count = desc_cb.tx_cable_count,
+ };
+ tuh_midi2_mount_cb(idx, &mount_cb);
+
+ // Prepare RX transfer
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+
+ // Signal USBH that configuration is complete
+ usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber);
+}
+
+static void midih2_set_interface_cb(tuh_xfer_t *xfer) {
+ uint8_t const dev_addr = xfer->daddr;
+ uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex);
+
+ // Find our interface
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) {
+ if (xfer->result == XFER_RESULT_SUCCESS) {
+ midih2_set_config_complete(&_midi2_host[idx], idx);
+ } else {
+ // SET_INTERFACE failed, fall back to alt 0
+ TU_LOG_DRV("MIDI2 SET_INTERFACE failed, falling back to alt 0\r\n");
+ _midi2_host[idx].alt_setting_current = 0;
+ midih2_set_config_complete(&_midi2_host[idx], idx);
+ }
+ return;
+ }
+ }
+}
+
+bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) {
+ uint8_t idx = 0;
+ for (idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) {
+ break;
+ }
+ }
+
+ if (idx >= CFG_TUH_MIDI2) {
+ // Not our interface (e.g. Audio Control) - pass through to next
+ usbh_driver_set_config_complete(dev_addr, itf_num);
+ return true;
+ }
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+
+ // Auto-select alt setting
+ midih2_auto_select_alt_setting(p_midi);
+
+ // If MIDI 2.0 detected, issue SET_INTERFACE to activate Alt Setting 1
+ if (p_midi->alt_setting_current == 1) {
+ TU_LOG_DRV("MIDI2 requesting SET_INTERFACE alt 1 for itf %u\r\n", itf_num);
+ TU_ASSERT(tuh_interface_set(dev_addr, itf_num, 1, midih2_set_interface_cb, 0));
+ } else {
+ // MIDI 1.0 only, complete immediately
+ midih2_set_config_complete(p_midi, idx);
+ }
+
+ return true;
+}
+
+void midih2_close(uint8_t dev_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) {
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ if (p_midi->daddr == dev_addr) {
+ TU_LOG_DRV(" MIDI2 close addr = %u index = %u\r\n", dev_addr, idx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+ tu_fifo_clear(&p_midi->ep_stream.tx.ff);
+ p_midi->ep_stream.tx.ep_addr = 0;
+ tuh_midi2_umount_cb(idx);
+ tu_memclr(p_midi, sizeof(midih2_interface_t));
+ }
+ }
+}
+
+bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr);
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ midih2_tx_t* ep_tx = &p_midi->ep_stream.tx;
+
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr) {
+ if (result == XFER_RESULT_SUCCESS && xferred_bytes > 0) {
+ tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes);
+ tuh_midi2_rx_cb(idx, xferred_bytes);
+ }
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx);
+ } else if (ep_addr == ep_tx->ep_addr) {
+ tuh_midi2_tx_cb(idx, xferred_bytes);
+ if (result == XFER_RESULT_SUCCESS) {
+ uint16_t queued = _tuh_tx_start_xfer(p_midi);
+ // Send ZLP if no more data is queued but the last transfer was exactly mps
+ if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 &&
+ (0 == (xferred_bytes & (ep_tx->mps - 1)))) {
+ if (usbh_edpt_claim(dev_addr, ep_tx->ep_addr)) {
+ usbh_edpt_xfer(dev_addr, ep_tx->ep_addr, NULL, 0);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Public API
+//--------------------------------------------------------------------+
+
+bool tuh_midi2_mounted(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].mounted;
+}
+
+uint8_t tuh_midi2_get_protocol_version(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].protocol_version;
+}
+
+uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _midi2_host[idx].alt_setting_current;
+}
+
+uint8_t tuh_midi2_get_cable_count(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return (_midi2_host[idx].alt_setting_current == 0) ?
+ _midi2_host[idx].rx_cable_count_alt0 : _midi2_host[idx].rx_cable_count_alt1;
+}
+
+uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2 && words && max_words);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ tu_edpt_stream_t *ep_rx = &p_midi->ep_stream.rx;
+
+ uint32_t n_words = 0;
+ for (uint32_t i = 0; i < max_words; i++) {
+ if (tu_edpt_stream_read_available(ep_rx) >= 4) {
+ tu_edpt_stream_read(ep_rx, (uint8_t *) &words[i], 4);
+ n_words++;
+ } else {
+ break;
+ }
+ }
+
+ return n_words;
+}
+
+uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2 && words && count);
+
+ midih2_interface_t *p_midi = &_midi2_host[idx];
+ TU_VERIFY(_tuh_tx_opened(p_midi), 0);
+
+ return _tuh_tx_ump_write(p_midi, words, count);
+}
+
+uint32_t tuh_midi2_write_flush(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI2);
+ return _tuh_tx_start_xfer(&_midi2_host[idx]);
+}
+
+#endif // CFG_TUH_ENABLED && CFG_TUH_MIDI2
diff --git a/src/class/midi/midi2_host.h b/src/class/midi/midi2_host.h
new file mode 100644
index 000000000..47039eb85
--- /dev/null
+++ b/src/class/midi/midi2_host.h
@@ -0,0 +1,107 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2026 Saulo Verissimo
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_MIDI2_HOST_H_
+#define TUSB_MIDI2_HOST_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Callback Type Definitions
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t protocol_version; // 0 = MIDI 1.0 only, 1 = MIDI 2.0
+ uint8_t bcdMSC_hi, bcdMSC_lo; // MIDI version from descriptor
+ uint8_t rx_cable_count; // For both alt settings (same for Alt 0 and Alt 1)
+ uint8_t tx_cable_count;
+} tuh_midi2_descriptor_cb_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+ uint8_t protocol_version; // 0 = MIDI 1.0, 1 = MIDI 2.0
+ uint8_t alt_setting_active; // 0 or 1
+ uint8_t rx_cable_count;
+ uint8_t tx_cable_count;
+} tuh_midi2_mount_cb_t;
+
+//--------------------------------------------------------------------+
+// Application Callback API (weak, optional)
+//--------------------------------------------------------------------+
+
+void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t *desc_cb_data);
+void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t *mount_cb_data);
+void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes);
+void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes);
+void tuh_midi2_umount_cb(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Application API - Query
+//--------------------------------------------------------------------+
+
+bool tuh_midi2_mounted(uint8_t idx);
+uint8_t tuh_midi2_get_protocol_version(uint8_t idx);
+uint8_t tuh_midi2_get_alt_setting_active(uint8_t idx);
+uint8_t tuh_midi2_get_cable_count(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Application API - I/O
+//--------------------------------------------------------------------+
+
+// Read up to max_words UMP words from the RX FIFO. Returns the number of
+// words actually read (0 if FIFO is empty).
+//
+// NOTE: this function returns when max_words is reached or when the FIFO is
+// empty, whichever comes first. Applications should invoke it in a loop
+// until it returns 0 to guarantee the RX FIFO is fully drained per
+// tuh_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can
+// prevent subsequent bulk IN transfers from landing.
+uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words);
+uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count);
+uint32_t tuh_midi2_write_flush(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+
+bool midih2_init(void);
+bool midih2_deinit(void);
+bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num);
+void midih2_close(uint8_t dev_addr);
+uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len);
+bool midih2_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c
index 7dac7c4a5..de4ff5dd8 100644
--- a/src/class/midi/midi_device.c
+++ b/src/class/midi/midi_device.c
@@ -26,7 +26,7 @@
#include "tusb_option.h"
-#if (CFG_TUD_ENABLED && CFG_TUD_MIDI)
+#if CFG_TUD_ENABLED && CFG_TUD_MIDI
//--------------------------------------------------------------------+
// INCLUDE
@@ -37,12 +37,18 @@
#include "midi_device.h"
//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tud_midi_rx_cb(uint8_t itf) {
+ (void)itf;
+}
+
+//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF
//--------------------------------------------------------------------+
typedef struct {
+ uint8_t rhport;
uint8_t itf_num;
- uint8_t ep_in;
- uint8_t ep_out;
// For Stream read()/write() API
// Messages are always 4 bytes long, queue them for reading and writing so the
@@ -51,134 +57,98 @@ typedef struct {
midi_driver_stream_t stream_read;
/*------------- From this point, data is not cleared by bus reset -------------*/
- // FIFO
- tu_fifo_t rx_ff;
- tu_fifo_t tx_ff;
- uint8_t rx_ff_buf[CFG_TUD_MIDI_RX_BUFSIZE];
- uint8_t tx_ff_buf[CFG_TUD_MIDI_TX_BUFSIZE];
+ // Endpoint stream
+ struct {
+ tu_edpt_stream_t tx;
+ tu_edpt_stream_t rx;
- #if CFG_FIFO_MUTEX
- osal_mutex_def_t rx_ff_mutex;
- osal_mutex_def_t tx_ff_mutex;
- #endif
+ uint8_t rx_ff_buf[CFG_TUD_MIDI_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUD_MIDI_TX_BUFSIZE];
+ } ep_stream;
} midid_interface_t;
-#define ITF_MEM_RESET_SIZE offsetof(midid_interface_t, rx_ff)
+#define ITF_MEM_RESET_SIZE offsetof(midid_interface_t, ep_stream)
-// Endpoint Transfer buffer
-CFG_TUD_MEM_SECTION static struct {
- TUD_EPBUF_DEF(epin, CFG_TUD_MIDI_EP_BUFSIZE);
- TUD_EPBUF_DEF(epout, CFG_TUD_MIDI_EP_BUFSIZE);
-} _midid_epbuf[CFG_TUD_MIDI];
-
-//--------------------------------------------------------------------+
-// INTERNAL OBJECT & FUNCTION DECLARATION
-//--------------------------------------------------------------------+
static midid_interface_t _midid_itf[CFG_TUD_MIDI];
-bool tud_midi_n_mounted (uint8_t itf) {
- midid_interface_t* midi = &_midid_itf[itf];
- return midi->ep_in && midi->ep_out;
-}
-
-static void _prep_out_transaction(uint8_t idx) {
- const uint8_t rhport = 0;
- midid_interface_t* p_midi = &_midid_itf[idx];
- uint16_t available = tu_fifo_remaining(&p_midi->rx_ff);
-
- // Prepare for incoming data but only allow what we can store in the ring buffer.
- // TODO Actually we can still carry out the transfer, keeping count of received bytes
- // and slowly move it to the FIFO when read().
- // This pre-check reduces endpoint claiming
- TU_VERIFY(available >= CFG_TUD_MIDI_EP_BUFSIZE, );
-
- // claim endpoint
- TU_VERIFY(usbd_edpt_claim(rhport, p_midi->ep_out), );
-
- // fifo can be changed before endpoint is claimed
- available = tu_fifo_remaining(&p_midi->rx_ff);
-
- if ( available >= CFG_TUD_MIDI_EP_BUFSIZE ) {
- usbd_edpt_xfer(rhport, p_midi->ep_out, _midid_epbuf[idx].epout, CFG_TUD_MIDI_EP_BUFSIZE);
- }else
- {
- // Release endpoint since we don't make any transfer
- usbd_edpt_release(rhport, p_midi->ep_out);
- }
-}
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0
+// Endpoint Transfer buffer: not used if dedicated hw FIFO is available
+typedef struct {
+ TUD_EPBUF_DEF(epin, CFG_TUD_MIDI_TX_EPSIZE);
+ TUD_EPBUF_DEF(epout, CFG_TUD_MIDI_RX_EPSIZE);
+} midid_epbuf_t;
+CFG_TUD_MEM_SECTION static midid_epbuf_t _midid_epbuf[CFG_TUD_MIDI];
+ #endif
//--------------------------------------------------------------------+
-// Weak stubs: invoked if no strong implementation is available
+// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
-TU_ATTR_WEAK void tud_midi_rx_cb(uint8_t itf) {
- (void) itf;
+bool tud_midi_n_mounted (uint8_t itf) {
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ const bool tx_opened = tu_edpt_stream_is_opened(&p_midi->ep_stream.tx);
+ const bool rx_opened = tu_edpt_stream_is_opened(&p_midi->ep_stream.rx);
+ return tx_opened && rx_opened;
}
//--------------------------------------------------------------------+
// READ API
//--------------------------------------------------------------------+
-uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num)
-{
+uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num) {
(void) cable_num;
-
- midid_interface_t* midi = &_midid_itf[itf];
- const midi_driver_stream_t* stream = &midi->stream_read;
+ const midid_interface_t *p_midi = &_midid_itf[itf];
+ const midi_driver_stream_t *stream = &p_midi->stream_read;
+ const tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
// when using with packet API stream total & index are both zero
- return tu_fifo_count(&midi->rx_ff) + (uint8_t) (stream->total - stream->index);
+ return tu_edpt_stream_read_available(ep_str) + (uint8_t)(stream->total - stream->index);
}
-uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, uint32_t bufsize)
-{
+uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize) {
(void) cable_num;
- TU_VERIFY(bufsize, 0);
-
- uint8_t* buf8 = (uint8_t*) buffer;
+ TU_VERIFY(buffer != NULL && bufsize > 0, 0);
- midid_interface_t* midi = &_midid_itf[itf];
- midi_driver_stream_t* stream = &midi->stream_read;
+ uint8_t *buf8 = (uint8_t *)buffer;
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ midi_driver_stream_t *stream = &p_midi->stream_read;
uint32_t total_read = 0;
- while( bufsize )
- {
+ while (bufsize > 0) {
// Get new packet from fifo, then set packet expected bytes
- if ( stream->total == 0 )
- {
- // return if there is no more data from fifo
- if ( !tud_midi_n_packet_read(itf, stream->buffer) ) return total_read;
+ if (stream->total == 0) {
+ if (!tud_midi_n_packet_read(itf, stream->buffer)) {
+ return total_read; // return if there is no more data from fifo
+ }
- uint8_t const code_index = stream->buffer[0] & 0x0f;
+ const uint8_t code_index = stream->buffer[0] & 0x0f;
// MIDI 1.0 Table 4-1: Code Index Number Classifications
- switch(code_index)
- {
+ switch (code_index) {
case MIDI_CIN_MISC:
case MIDI_CIN_CABLE_EVENT:
// These are reserved and unused, possibly issue somewhere, skip this packet
return 0;
- break;
case MIDI_CIN_SYSEX_END_1BYTE:
case MIDI_CIN_1BYTE_DATA:
stream->total = 1;
- break;
+ break;
case MIDI_CIN_SYSCOM_2BYTE :
case MIDI_CIN_SYSEX_END_2BYTE :
case MIDI_CIN_PROGRAM_CHANGE :
case MIDI_CIN_CHANNEL_PRESSURE :
stream->total = 2;
- break;
+ break;
default:
stream->total = 3;
- break;
+ break;
}
}
// Copy data up to bufsize
- uint8_t const count = (uint8_t) tu_min32(stream->total - stream->index, bufsize);
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize);
// Skip the header (1st byte) in the buffer
TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count));
@@ -189,8 +159,7 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, ui
bufsize -= count;
// complete current event packet, reset stream
- if ( stream->total == stream->index )
- {
+ if (stream->total == stream->index) {
stream->index = 0;
stream->total = 0;
}
@@ -199,150 +168,206 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, ui
return total_read;
}
-bool tud_midi_n_packet_read (uint8_t itf, uint8_t packet[4])
-{
- midid_interface_t* midi = &_midid_itf[itf];
- TU_VERIFY(midi->ep_out);
+// Note: this function shares stream->buffer with tud_midi_n_stream_read().
+// Do not mix calls to both functions on the same interface.
+uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize) {
+ TU_VERIFY(p_cable_num != NULL && buffer != NULL && bufsize > 0, 0);
- const uint32_t num_read = tu_fifo_read_n(&midi->rx_ff, packet, 4);
- _prep_out_transaction(itf);
- return (num_read == 4);
-}
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ midi_driver_stream_t *stream = &p_midi->stream_read;
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
-//--------------------------------------------------------------------+
-// WRITE API
-//--------------------------------------------------------------------+
+ uint8_t *buf8 = (uint8_t *)buffer;
+ uint32_t total_read = 0;
-static uint32_t write_flush(uint8_t idx) {
- midid_interface_t* midi = &_midid_itf[idx];
+ // Initialize to invalid cable so callers can detect "no data" even when
+ // the return value is 0.
+ *p_cable_num = 0xff;
- if (!tu_fifo_count(&midi->tx_ff)) {
- return 0; // No data to send
+ // If there are leftover bytes from a previous partial read, return them first
+ if (stream->total > 0) {
+ *p_cable_num = (stream->buffer[0] >> 4) & 0x0f;
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize);
+ TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count));
+
+ total_read += count;
+ stream->index += count;
+ buf8 += count;
+ bufsize -= count;
+
+ if (stream->total == stream->index) {
+ stream->index = 0;
+ stream->total = 0;
+ }
+
+ if (bufsize == 0) {
+ return total_read;
+ }
}
- const uint8_t rhport = 0;
+ while (bufsize > 0) {
+ // Peek at next packet header to get cable number without consuming
+ uint8_t one_byte;
+ if (!tu_edpt_stream_peek(ep_str, &one_byte)) {
+ break;
+ }
+
+ const uint8_t next_cable = (one_byte >> 4) & 0x0f;
+
+ // Stop if cable changed (covers both leftover-originated reads and
+ // freshly consumed packets — total_read > 0 in either case)
+ if (total_read > 0 && next_cable != *p_cable_num) {
+ break;
+ }
+ *p_cable_num = next_cable;
+
+ // Consume the packet
+ if (!tud_midi_n_packet_read(itf, stream->buffer)) {
+ break;
+ }
+
+ const uint8_t code_index = stream->buffer[0] & 0x0f;
+ uint8_t msg_bytes;
+
+ // MIDI 1.0 Table 4-1: Code Index Number Classifications
+ switch (code_index) {
+ case MIDI_CIN_MISC:
+ case MIDI_CIN_CABLE_EVENT:
+ // Reserved and unused, skip this packet
+ continue;
+
+ case MIDI_CIN_SYSEX_END_1BYTE:
+ case MIDI_CIN_1BYTE_DATA:
+ msg_bytes = 1;
+ break;
+
+ case MIDI_CIN_SYSCOM_2BYTE:
+ case MIDI_CIN_SYSEX_END_2BYTE:
+ case MIDI_CIN_PROGRAM_CHANGE:
+ case MIDI_CIN_CHANNEL_PRESSURE:
+ msg_bytes = 2;
+ break;
+
+ default:
+ msg_bytes = 3;
+ break;
+ }
- // skip if previous transfer not complete
- TU_VERIFY( usbd_edpt_claim(rhport, midi->ep_in), 0 );
+ const uint8_t count = (uint8_t)tu_min32((uint32_t)msg_bytes, bufsize);
+ TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1, count));
- uint16_t count = tu_fifo_read_n(&midi->tx_ff, _midid_epbuf[idx].epin, CFG_TUD_MIDI_EP_BUFSIZE);
+ total_read += count;
+ buf8 += count;
+ bufsize -= count;
- if (count) {
- TU_ASSERT( usbd_edpt_xfer(rhport, midi->ep_in, _midid_epbuf[idx].epin, count), 0 );
- return count;
- }else {
- // Release endpoint since we don't make any transfer
- usbd_edpt_release(rhport, midi->ep_in);
- return 0;
+ if (count < msg_bytes) {
+ // Output buffer full, save remaining for next call
+ stream->total = msg_bytes;
+ stream->index = count;
+ }
}
+
+ return total_read;
}
-uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t* buffer, uint32_t bufsize)
-{
- midid_interface_t* midi = &_midid_itf[itf];
- TU_VERIFY(midi->ep_in, 0);
+bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]) {
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
+ return 4 == tu_edpt_stream_read(ep_str, packet, 4);
+}
- midi_driver_stream_t* stream = &midi->stream_write;
+uint32_t tud_midi_n_packet_read_n(uint8_t itf, uint8_t packets[], uint32_t max_packets) {
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx;
+ const uint32_t num_read = tu_edpt_stream_read(ep_str, packets, 4u * max_packets);
+ return num_read >> 2u;
+}
+
+//--------------------------------------------------------------------+
+// WRITE API
+//--------------------------------------------------------------------+
+uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) {
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ midi_driver_stream_t *stream = &p_midi->stream_write;
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.tx;
+ TU_VERIFY(tu_edpt_stream_is_opened(ep_str), 0);
uint32_t i = 0;
- while ( (i < bufsize) && (tu_fifo_remaining(&midi->tx_ff) >= 4) )
- {
+ while (i < bufsize) {
+ if (tu_edpt_stream_write_available(ep_str) < 4) {
+ break;
+ }
+
const uint8_t data = buffer[i];
i++;
- if ( stream->index == 0 )
- {
+ if (stream->index == 0) {
//------------- New event packet -------------//
const uint8_t msg = data >> 4;
- stream->index = 2;
+ stream->index = 2;
stream->buffer[1] = data;
// Check to see if we're still in a SysEx transmit.
- if ( ((stream->buffer[0]) & 0xF) == MIDI_CIN_SYSEX_START )
- {
- if ( data == MIDI_STATUS_SYSEX_END )
- {
- stream->buffer[0] = (uint8_t) ((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE);
- stream->total = 2;
- }
- else
- {
+ if (((stream->buffer[0]) & 0xF) == MIDI_CIN_SYSEX_START) {
+ if (data == MIDI_STATUS_SYSEX_END) {
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE);
+ stream->total = 2;
+ } else {
stream->total = 4;
}
- }
- else if ( (msg >= 0x8 && msg <= 0xB) || msg == 0xE )
- {
+ } else if ((msg >= 0x8 && msg <= 0xB) || msg == 0xE) {
// Channel Voice Messages
- stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg);
- stream->total = 4;
- }
- else if ( msg == 0xC || msg == 0xD)
- {
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | msg);
+ stream->total = 4;
+ } else if (msg == 0xC || msg == 0xD) {
// Channel Voice Messages, two-byte variants (Program Change and Channel Pressure)
- stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg);
- stream->total = 3;
- }
- else if ( msg == 0xf )
- {
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | msg);
+ stream->total = 3;
+ } else if (msg == 0xf) {
// System message
- if ( data == MIDI_STATUS_SYSEX_START )
- {
+ if (data == MIDI_STATUS_SYSEX_START) {
stream->buffer[0] = MIDI_CIN_SYSEX_START;
- stream->total = 4;
- }
- else if ( data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT )
- {
+ stream->total = 4;
+ } else if (data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT) {
stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE;
- stream->total = 3;
- }
- else if ( data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER )
- {
+ stream->total = 3;
+ } else if (data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER) {
stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE;
- stream->total = 4;
- }
- else
- {
+ stream->total = 4;
+ } else {
stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
- stream->total = 2;
+ stream->total = 2;
}
stream->buffer[0] |= (uint8_t)(cable_num << 4);
- }
- else
- {
+ } else {
// Pack individual bytes if we don't support packing them into words.
- stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf);
+ stream->buffer[0] = (uint8_t)(cable_num << 4 | 0xf);
stream->buffer[2] = 0;
stream->buffer[3] = 0;
- stream->index = 2;
- stream->total = 2;
+ stream->total = 2; // index already set to 2
}
- }
- else
- {
+ } else {
//------------- On-going (buffering) packet -------------//
-
TU_ASSERT(stream->index < 4, i);
stream->buffer[stream->index] = data;
stream->index++;
// See if this byte ends a SysEx.
- if ( (stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END )
- {
- stream->buffer[0] = (uint8_t) ((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1)));
- stream->total = stream->index;
+ if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) {
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1)));
+ stream->total = stream->index;
}
}
// Send out packet
- if ( stream->index == stream->total )
- {
+ if (stream->index == stream->total) {
// zeroes unused bytes
for (uint8_t idx = stream->total; idx < 4; idx++) {
stream->buffer[idx] = 0;
}
- const uint16_t count = tu_fifo_write_n(&midi->tx_ff, stream->buffer, 4);
+ const uint32_t count = tu_edpt_stream_write(ep_str, stream->buffer, 4);
// complete current event packet, reset stream
stream->index = stream->total = 0;
@@ -352,98 +377,107 @@ uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t*
}
}
- write_flush(itf);
+ (void)tu_edpt_stream_write_xfer(ep_str);
return i;
}
bool tud_midi_n_packet_write (uint8_t itf, const uint8_t packet[4]) {
- midid_interface_t* midi = &_midid_itf[itf];
- TU_VERIFY(midi->ep_in);
-
- if (tu_fifo_remaining(&midi->tx_ff) < 4) {
- return false;
- }
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.tx;
+ TU_VERIFY(tu_edpt_stream_is_opened(ep_str));
- tu_fifo_write_n(&midi->tx_ff, packet, 4);
- write_flush(itf);
+ TU_VERIFY(tu_edpt_stream_write_available(ep_str) >= 4);
+ TU_VERIFY(tu_edpt_stream_write(ep_str, packet, 4) > 0);
+ (void)tu_edpt_stream_write_xfer(ep_str);
return true;
}
+uint32_t tud_midi_n_packet_write_n(uint8_t itf, const uint8_t packets[], uint32_t n_packets) {
+ midid_interface_t *p_midi = &_midid_itf[itf];
+ tu_edpt_stream_t *ep_str = &p_midi->ep_stream.tx;
+ TU_VERIFY(tu_edpt_stream_is_opened(ep_str), 0);
+
+ uint32_t n_bytes = tu_edpt_stream_write_available(ep_str);
+ n_bytes = tu_min32(tu_align4(n_bytes), n_packets << 2u);
+
+ const uint32_t n_write = tu_edpt_stream_write(ep_str, packets, n_bytes);
+ (void)tu_edpt_stream_write_xfer(ep_str);
+
+ return n_write >> 2u;
+}
+
//--------------------------------------------------------------------+
// USBD Driver API
//--------------------------------------------------------------------+
void midid_init(void) {
tu_memclr(_midid_itf, sizeof(_midid_itf));
-
for (uint8_t i = 0; i < CFG_TUD_MIDI; i++) {
- midid_interface_t* midi = &_midid_itf[i];
+ midid_interface_t *p_midi = &_midid_itf[i];
- // config fifo
- tu_fifo_config(&midi->rx_ff, midi->rx_ff_buf, CFG_TUD_MIDI_RX_BUFSIZE, 1, false); // true, true
- tu_fifo_config(&midi->tx_ff, midi->tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE, 1, false); // OBVS.
+ #if CFG_TUD_EDPT_DEDICATED_HWFIFO
+ uint8_t *epout_buf = NULL;
+ uint8_t *epin_buf = NULL;
+ #else
+ midid_epbuf_t *p_epbuf = &_midid_epbuf[i];
+ uint8_t *epout_buf = p_epbuf->epout;
+ uint8_t *epin_buf = p_epbuf->epin;
+ #endif
- #if CFG_FIFO_MUTEX
- osal_mutex_t mutex_rd = osal_mutex_create(&midi->rx_ff_mutex);
- osal_mutex_t mutex_wr = osal_mutex_create(&midi->tx_ff_mutex);
- TU_ASSERT(mutex_wr != NULL && mutex_wr != NULL, );
+ tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false, p_midi->ep_stream.rx_ff_buf,
+ CFG_TUD_MIDI_RX_BUFSIZE, epout_buf);
- tu_fifo_config_mutex(&midi->rx_ff, NULL, mutex_rd);
- tu_fifo_config_mutex(&midi->tx_ff, mutex_wr, NULL);
- #endif
+ tu_edpt_stream_init(&p_midi->ep_stream.tx, false, true, false, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE,
+ epin_buf);
}
}
bool midid_deinit(void) {
- #if CFG_FIFO_MUTEX
- for(uint8_t i=0; i<CFG_TUD_MIDI; i++) {
- midid_interface_t* midi = &_midid_itf[i];
- osal_mutex_t mutex_rd = midi->rx_ff.mutex_rd;
- osal_mutex_t mutex_wr = midi->tx_ff.mutex_wr;
-
- if (mutex_rd) {
- osal_mutex_delete(mutex_rd);
- tu_fifo_config_mutex(&midi->rx_ff, NULL, NULL);
- }
-
- if (mutex_wr) {
- osal_mutex_delete(mutex_wr);
- tu_fifo_config_mutex(&midi->tx_ff, NULL, NULL);
- }
+ for (uint8_t i = 0; i < CFG_TUD_MIDI; i++) {
+ midid_interface_t *p_midi = &_midid_itf[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ tu_edpt_stream_deinit(&p_midi->ep_stream.tx);
}
- #endif
-
return true;
}
-void midid_reset(uint8_t rhport)
-{
- (void) rhport;
+void midid_reset(uint8_t rhport) {
+ (void)rhport;
+ for (uint8_t i = 0; i < CFG_TUD_MIDI; i++) {
+ midid_interface_t *p_midi = &_midid_itf[i];
+ tu_memclr(p_midi, ITF_MEM_RESET_SIZE);
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.rx);
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+
+ tu_edpt_stream_clear(&p_midi->ep_stream.tx);
+ tu_edpt_stream_close(&p_midi->ep_stream.tx);
+ }
+}
- for(uint8_t i=0; i<CFG_TUD_MIDI; i++)
- {
- midid_interface_t* midi = &_midid_itf[i];
- tu_memclr(midi, ITF_MEM_RESET_SIZE);
- tu_fifo_clear(&midi->rx_ff);
- tu_fifo_clear(&midi->tx_ff);
+TU_ATTR_ALWAYS_INLINE static inline uint8_t find_midi_itf(uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUD_MIDI; idx++) {
+ const midid_interface_t *p_midi = &_midid_itf[idx];
+ if (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr) {
+ return idx;
+ }
}
+ return TUSB_INDEX_INVALID_8;
}
-uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) {
- uint16_t drv_len = 0;
- uint8_t const * p_desc = (uint8_t const *)desc_itf;
+uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
+ const uint8_t *p_desc = (const uint8_t *)desc_itf;
+ const uint8_t *desc_end = p_desc + max_len;
// 1st Interface is Audio Control v1 (optional)
if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass &&
AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass &&
AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) {
- drv_len = tu_desc_len(desc_itf);
p_desc = tu_desc_next(desc_itf);
// Skip Class Specific descriptors
- while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) {
- drv_len += tu_desc_len(p_desc);
- p_desc = tu_desc_next(p_desc);
+ while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) {
+ p_desc = tu_desc_next(p_desc);
}
}
@@ -451,59 +485,48 @@ uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint1
TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc;
- TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass &&
- AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass &&
- AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol, 0);
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass &&
+ AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass &&
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol,
+ 0);
- // Find available interface
- midid_interface_t * p_midi = NULL;
- uint8_t idx;
- for(idx=0; idx<CFG_TUD_MIDI; idx++) {
- if ( _midid_itf[idx].ep_in == 0 && _midid_itf[idx].ep_out == 0 ) {
- p_midi = &_midid_itf[idx];
- break;
- }
- }
- TU_ASSERT(p_midi);
+ uint8_t idx = find_midi_itf(0); // find unused interface
+ TU_ASSERT(idx < CFG_TUD_MIDI, 0);
+ midid_interface_t *p_midi = &_midid_itf[idx];
+ p_midi->rhport = rhport;
p_midi->itf_num = desc_midi->bInterfaceNumber;
(void) p_midi->itf_num;
- // next descriptor
- drv_len += tu_desc_len(p_desc);
- p_desc = tu_desc_next(p_desc);
+ p_desc = tu_desc_next(p_desc);
// Find and open endpoint descriptors
- uint8_t found_endpoints = 0;
- while ( (found_endpoints < desc_midi->bNumEndpoints) && (drv_len <= max_len) )
- {
- if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
- {
- TU_ASSERT(usbd_edpt_open(rhport, (const tusb_desc_endpoint_t*) p_desc), 0);
- uint8_t ep_addr = ((const tusb_desc_endpoint_t*) p_desc)->bEndpointAddress;
+ uint8_t found_ep = 0;
+ while ((found_ep < desc_midi->bNumEndpoints) && tu_desc_in_bounds(p_desc, desc_end)) {
+ if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) {
+ const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc;
+ TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0);
+ const uint8_t ep_addr = ((const tusb_desc_endpoint_t *)p_desc)->bEndpointAddress;
- if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN)
- {
- p_midi->ep_in = ep_addr;
+ if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) {
+ tu_edpt_stream_t *stream_tx = &p_midi->ep_stream.tx;
+ tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_MIDI_TX_EPSIZE);
+ tu_edpt_stream_clear(stream_tx);
} else {
- p_midi->ep_out = ep_addr;
+ tu_edpt_stream_t *stream_rx = &p_midi->ep_stream.rx;
+ tu_edpt_stream_open(stream_rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep));
+ tu_edpt_stream_clear(stream_rx);
+ TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare to receive data
}
- // Class Specific MIDI Stream endpoint descriptor
- drv_len += tu_desc_len(p_desc);
- p_desc = tu_desc_next(p_desc);
-
- found_endpoints += 1;
+ p_desc = tu_desc_next(p_desc); // skip CS Endpoint descriptor
+ found_ep++;
}
- drv_len += tu_desc_len(p_desc);
- p_desc = tu_desc_next(p_desc);
+ p_desc = tu_desc_next(p_desc);
}
- // Prepare for incoming data
- _prep_out_transaction(idx);
-
- return drv_len;
+ return (uint16_t)(p_desc - (const uint8_t *)desc_itf);
}
// Invoked when a control transfer occurred on an interface of this class
@@ -514,44 +537,32 @@ bool midid_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_req
return false; // driver doesn't support any request yet
}
-bool midid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
-{
- (void) result;
- (void) rhport;
-
- uint8_t idx;
- midid_interface_t* p_midi;
+bool midid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void)rhport;
+ (void)result;
- // Identify which interface to use
- for (idx = 0; idx < CFG_TUD_MIDI; idx++) {
- p_midi = &_midid_itf[idx];
- if ((ep_addr == p_midi->ep_out) || (ep_addr == p_midi->ep_in)) {
- break;
- }
- }
+ uint8_t idx = find_midi_itf(ep_addr);
TU_ASSERT(idx < CFG_TUD_MIDI);
+ midid_interface_t *p_midi = &_midid_itf[idx];
- // receive new data
- if (ep_addr == p_midi->ep_out) {
- tu_fifo_write_n(&p_midi->rx_ff, _midid_epbuf[idx].epout, (uint16_t)xferred_bytes);
-
- // invoke receive callback if available
- tud_midi_rx_cb(idx);
+ tu_edpt_stream_t *ep_st_rx = &p_midi->ep_stream.rx;
+ tu_edpt_stream_t *ep_st_tx = &p_midi->ep_stream.tx;
- // prepare for next
- // TODO for now ep_out is not used by public API therefore there is no race condition,
- // and does not need to claim like ep_in
- _prep_out_transaction(idx);
- } else if (ep_addr == p_midi->ep_in) {
- if (0 == write_flush(idx)) {
- // If there is no data left, a ZLP should be sent if
- // xferred_bytes is multiple of EP size and not zero
- if (!tu_fifo_count(&p_midi->tx_ff) && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_MIDI_EP_BUFSIZE))) {
- if (usbd_edpt_claim(rhport, p_midi->ep_in)) {
- usbd_edpt_xfer(rhport, p_midi->ep_in, NULL, 0);
- }
- }
+ if (ep_addr == ep_st_rx->ep_addr) {
+ // Received new data: put into stream's fifo
+ if (result == XFER_RESULT_SUCCESS) {
+ tu_edpt_stream_read_xfer_complete(ep_st_rx, xferred_bytes);
+ tud_midi_rx_cb(idx); // invoke callback
}
+ tu_edpt_stream_read_xfer(ep_st_rx); // prepare for next data
+ } else if (ep_addr == ep_st_tx->ep_addr && result == XFER_RESULT_SUCCESS) {
+ // sent complete: try to send more if possible
+ if (0 == tu_edpt_stream_write_xfer(ep_st_tx)) {
+ // If there is no data left, a ZLP should be sent if needed
+ (void)tu_edpt_stream_write_zlp_if_needed(ep_st_tx, xferred_bytes);
+ }
+ } else {
+ return false;
}
return true;
diff --git a/src/class/midi/midi_device.h b/src/class/midi/midi_device.h
index c2c6e9859..57eabec1f 100644
--- a/src/class/midi/midi_device.h
+++ b/src/class/midi/midi_device.h
@@ -24,8 +24,8 @@
* This file is part of the TinyUSB stack.
*/
-#ifndef _TUSB_MIDI_DEVICE_H_
-#define _TUSB_MIDI_DEVICE_H_
+#ifndef TUSB_MIDI_DEVICE_H_
+#define TUSB_MIDI_DEVICE_H_
#include "class/audio/audio.h"
#include "midi.h"
@@ -34,23 +34,30 @@
// Class Driver Configuration
//--------------------------------------------------------------------+
-#if !defined(CFG_TUD_MIDI_EP_BUFSIZE) && defined(CFG_TUD_MIDI_EPSIZE)
- #warning CFG_TUD_MIDI_EPSIZE is renamed to CFG_TUD_MIDI_EP_BUFSIZE, please update to use the new name
- #define CFG_TUD_MIDI_EP_BUFSIZE CFG_TUD_MIDI_EPSIZE
+#ifndef CFG_TUD_MIDI_RX_EPSIZE
+ #ifdef CFG_TUD_MIDI_EP_BUFSIZE
+ #define CFG_TUD_MIDI_RX_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
+ #else
+ #define CFG_TUD_MIDI_RX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
-#ifndef CFG_TUD_MIDI_EP_BUFSIZE
- #define CFG_TUD_MIDI_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#ifndef CFG_TUD_MIDI_TX_EPSIZE
+ #ifdef CFG_TUD_MIDI_EP_BUFSIZE
+ #define CFG_TUD_MIDI_TX_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
+ #else
+ #define CFG_TUD_MIDI_TX_EPSIZE TUD_EPSIZE_BULK_MAX
+ #endif
#endif
#ifdef __cplusplus
- extern "C" {
+extern "C" {
#endif
-/** \addtogroup MIDI_Serial Serial
- * @{
- * \defgroup MIDI_Serial_Device Device
- * @{ */
+//--------------------------------------------------------------------+
+// Application Callback API (optional)
+//--------------------------------------------------------------------+
+void tud_midi_rx_cb(uint8_t itf);
//--------------------------------------------------------------------+
// Application API (Multiple Interfaces)
@@ -58,117 +65,89 @@
//--------------------------------------------------------------------+
// Check if midi interface is mounted
-bool tud_midi_n_mounted (uint8_t itf);
+bool tud_midi_n_mounted(uint8_t itf);
// Get the number of bytes available for reading
-uint32_t tud_midi_n_available (uint8_t itf, uint8_t cable_num);
-
-// Read byte stream (legacy)
-uint32_t tud_midi_n_stream_read (uint8_t itf, uint8_t cable_num, void* buffer, uint32_t bufsize);
+uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num);
-// Write byte Stream (legacy)
-uint32_t tud_midi_n_stream_write (uint8_t itf, uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize);
+// Read byte stream (legacy)
+uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize);
-// Read event packet (4 bytes)
-bool tud_midi_n_packet_read (uint8_t itf, uint8_t packet[4]);
+// Read byte stream with cable demultiplexing: returns the cable number of the
+// data that was read. Reads from a single cable per call; stops when the next
+// packet belongs to a different cable so the caller can dispatch per-cable.
+// Note: shares internal state with tud_midi_n_stream_read(); do not mix both
+// on the same interface.
+uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize);
-// Write event packet (4 bytes)
-bool tud_midi_n_packet_write (uint8_t itf, uint8_t const packet[4]);
-
-//--------------------------------------------------------------------+
-// Application API (Single Interface)
-//--------------------------------------------------------------------+
-static inline bool tud_midi_mounted (void);
-static inline uint32_t tud_midi_available (void);
+// Write byte Stream (legacy)
+uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize);
-static inline uint32_t tud_midi_stream_read (void* buffer, uint32_t bufsize);
-static inline uint32_t tud_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize);
+// Read an event 4-byte packet
+bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]);
-static inline bool tud_midi_packet_read (uint8_t packet[4]);
-static inline bool tud_midi_packet_write (uint8_t const packet[4]);
+// Read multiple event packets, return number of read packets
+uint32_t tud_midi_n_packet_read_n(uint8_t itf, uint8_t packets[], uint32_t max_packets);
-//------------- Deprecated API name -------------//
-// TODO remove after 0.10.0 release
+// Write an event 4-byte packet
+bool tud_midi_n_packet_write(uint8_t itf, const uint8_t packet[4]);
-TU_ATTR_DEPRECATED("tud_midi_read() is renamed to tud_midi_stream_read()")
-static inline uint32_t tud_midi_read (void* buffer, uint32_t bufsize)
-{
- return tud_midi_stream_read(buffer, bufsize);
-}
-
-TU_ATTR_DEPRECATED("tud_midi_write() is renamed to tud_midi_stream_write()")
-static inline uint32_t tud_midi_write(uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize)
-{
- return tud_midi_stream_write(cable_num, buffer, bufsize);
-}
-
-
-TU_ATTR_DEPRECATED("tud_midi_send() is renamed to tud_midi_packet_write()")
-static inline bool tud_midi_send(uint8_t packet[4])
-{
- return tud_midi_packet_write(packet);
-}
-
-TU_ATTR_DEPRECATED("tud_midi_receive() is renamed to tud_midi_packet_read()")
-static inline bool tud_midi_receive(uint8_t packet[4])
-{
- return tud_midi_packet_read(packet);
-}
+// Write multiple event packets, return number of written packets
+uint32_t tud_midi_n_packet_write_n(uint8_t itf, const uint8_t packets[], uint32_t n_packets);
//--------------------------------------------------------------------+
-// Application Callback API (optional)
-//--------------------------------------------------------------------+
-void tud_midi_rx_cb(uint8_t itf);
-
-//--------------------------------------------------------------------+
-// Inline Functions
+// Application API (Single Interface)
//--------------------------------------------------------------------+
-
-static inline bool tud_midi_mounted (void)
-{
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi_mounted(void) {
return tud_midi_n_mounted(0);
}
-static inline uint32_t tud_midi_available (void)
-{
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_available(void) {
return tud_midi_n_available(0, 0);
}
-static inline uint32_t tud_midi_stream_read (void* buffer, uint32_t bufsize)
-{
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_read(void *buffer, uint32_t bufsize) {
return tud_midi_n_stream_read(0, 0, buffer, bufsize);
}
-static inline uint32_t tud_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize)
-{
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi_demux_stream_read(uint8_t *p_cable_num, void *buffer, uint32_t bufsize) {
+ return tud_midi_n_demux_stream_read(0, p_cable_num, buffer, bufsize);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline uint32_t
+tud_midi_stream_write(uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) {
return tud_midi_n_stream_write(0, cable_num, buffer, bufsize);
}
-static inline bool tud_midi_packet_read (uint8_t packet[4])
-{
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi_packet_read(uint8_t packet[4]) {
return tud_midi_n_packet_read(0, packet);
}
-static inline bool tud_midi_packet_write (uint8_t const packet[4])
-{
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_packet_read_n(uint8_t packets[], uint32_t max_packets) {
+ return tud_midi_n_packet_read_n(0, packets, max_packets);
+}
+
+TU_ATTR_ALWAYS_INLINE static inline bool tud_midi_packet_write(const uint8_t packet[4]) {
return tud_midi_n_packet_write(0, packet);
}
+TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_packet_write_n(const uint8_t packets[], uint32_t n_packets) {
+ return tud_midi_n_packet_write_n(0, packets, n_packets);
+}
+
//--------------------------------------------------------------------+
// Internal Class Driver API
//--------------------------------------------------------------------+
-void midid_init (void);
-bool midid_deinit (void);
-void midid_reset (uint8_t rhport);
-uint16_t midid_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
-bool midid_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
-bool midid_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes);
+void midid_init(void);
+bool midid_deinit(void);
+void midid_reset(uint8_t rhport);
+uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len);
+bool midid_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request);
+bool midid_xfer_cb(uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes);
#ifdef __cplusplus
- }
+}
#endif
-#endif /* _TUSB_MIDI_DEVICE_H_ */
-
-/** @} */
-/** @} */
+#endif
diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c
index e6ace316c..a53546404 100644
--- a/src/class/midi/midi_host.c
+++ b/src/class/midi/midi_host.c
@@ -57,10 +57,7 @@ typedef struct {
uint8_t daddr;
uint8_t bInterfaceNumber; // interface number of MIDI streaming
uint8_t iInterface;
- uint8_t itf_count; // number of interface including Audio Control + MIDI streaming
-
- uint8_t ep_in; // IN endpoint address
- uint8_t ep_out; // OUT endpoint address
+ uint8_t itf_count; // number of interfaces including Audio Control + MIDI streaming
uint8_t rx_cable_count; // IN endpoint CS descriptor bNumEmbMIDIJack value
uint8_t tx_cable_count; // OUT endpoint CS descriptor bNumEmbMIDIJack value
@@ -86,8 +83,8 @@ typedef struct {
}midih_interface_t;
typedef struct {
- TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MPS);
- TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MPS);
+ TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX);
+ TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX);
} midih_epbuf_t;
static midih_interface_t _midi_host[CFG_TUH_MIDI];
@@ -124,9 +121,9 @@ bool midih_init(void) {
for (int inst = 0; inst < CFG_TUH_MIDI; inst++) {
midih_interface_t *p_midi_host = &_midi_host[inst];
tu_edpt_stream_init(&p_midi_host->ep_stream.rx, true, false, false,
- p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf->rx, TUH_EPSIZE_BULK_MPS);
+ p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf[inst].rx);
tu_edpt_stream_init(&p_midi_host->ep_stream.tx, true, true, false,
- p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf->tx, TUH_EPSIZE_BULK_MPS);
+ p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf[inst].tx);
}
return true;
}
@@ -147,8 +144,6 @@ void midih_close(uint8_t daddr) {
TU_LOG_DRV(" MIDI close addr = %u index = %u\r\n", daddr, idx);
tuh_midi_umount_cb(idx);
- p_midi->ep_in = 0;
- p_midi->ep_out = 0;
p_midi->bInterfaceNumber = 0;
p_midi->rx_cable_count = 0;
p_midi->tx_cable_count = 0;
@@ -169,23 +164,25 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint
const uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr);
TU_VERIFY(idx < CFG_TUH_MIDI);
midih_interface_t *p_midi = &_midi_host[idx];
+ tu_edpt_stream_t *ep_str_rx = &p_midi->ep_stream.rx;
+ tu_edpt_stream_t *ep_str_tx = &p_midi->ep_stream.tx;
- if (ep_addr == p_midi->ep_stream.rx.ep_addr) {
+ if (ep_addr == ep_str_rx->ep_addr) {
// receive new data, put it into FIFO and invoke callback if available
// Note: some devices send back all zero packets even if there is no data ready
- if (xferred_bytes && !tu_mem_is_zero(p_midi->ep_stream.rx.ep_buf, xferred_bytes)) {
- tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes);
+ if (xferred_bytes && !tu_mem_is_zero(ep_str_rx->ep_buf, xferred_bytes)) {
+ tu_edpt_stream_read_xfer_complete(ep_str_rx, xferred_bytes);
tuh_midi_rx_cb(idx, xferred_bytes);
}
- tu_edpt_stream_read_xfer(dev_addr, &p_midi->ep_stream.rx); // prepare for next transfer
- } else if (ep_addr == p_midi->ep_stream.tx.ep_addr) {
+ tu_edpt_stream_read_xfer(ep_str_rx); // prepare for next transfer
+ } else if (ep_addr == ep_str_tx->ep_addr) {
tuh_midi_tx_cb(idx, xferred_bytes);
- if (0 == tu_edpt_stream_write_xfer(dev_addr, &p_midi->ep_stream.tx)) {
+ if (0 == tu_edpt_stream_write_xfer(ep_str_tx)) {
// If there is no data left, a ZLP should be sent if
// xferred_bytes is multiple of EP size and not zero
- tu_edpt_stream_write_zlp_if_needed(dev_addr, &p_midi->ep_stream.tx, xferred_bytes);
+ tu_edpt_stream_write_zlp_if_needed(ep_str_tx, xferred_bytes);
}
}
@@ -195,15 +192,16 @@ bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint
//--------------------------------------------------------------------+
// Enumeration
//--------------------------------------------------------------------+
-bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) {
+uint16_t midih_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
(void) rhport;
- TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass);
- const uint8_t *p_end = ((const uint8_t *) desc_itf) + max_len;
- const uint8_t *p_desc = (const uint8_t *) desc_itf;
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+ const uint8_t *desc_start = (const uint8_t *)desc_itf;
+ const uint8_t *p_desc = desc_start;
+ const uint8_t *desc_end = desc_start + max_len;
const uint8_t idx = find_new_midi_index();
- TU_VERIFY(idx < CFG_TUH_MIDI);
+ TU_VERIFY(idx < CFG_TUH_MIDI, 0);
midih_interface_t *p_midi = &_midi_host[idx];
p_midi->itf_count = 0;
@@ -220,29 +218,30 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d
// driver after parsing the audio control interface and then resume parsing
// the streaming audio interface.
if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) {
- TU_VERIFY(max_len > 2*sizeof(tusb_desc_interface_t) + sizeof(audio_desc_cs_ac_interface_t));
-
+ TU_VERIFY(max_len > 2 * sizeof(tusb_desc_interface_t) + sizeof(midi10_desc_cs_ac_interface_t), 0);
p_desc = tu_desc_next(p_desc);
TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE &&
- tu_desc_subtype(p_desc) == AUDIO_CS_AC_INTERFACE_HEADER);
+ tu_desc_subtype(p_desc) == AUDIO10_CS_AC_INTERFACE_HEADER,
+ 0);
desc_cb.desc_audio_control = desc_itf;
p_desc = tu_desc_next(p_desc);
desc_itf = (const tusb_desc_interface_t *)p_desc;
p_midi->itf_count = 1;
// skip non-interface and non-midi streaming descriptors
- while (tu_desc_in_bounds(p_desc, p_end) &&
- (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE || (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO && desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) {
+ while (tu_desc_in_bounds(p_desc, desc_end) && (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE ||
+ (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO &&
+ desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) {
if (desc_itf->bDescriptorType == TUSB_DESC_INTERFACE && desc_itf->bAlternateSetting == 0) {
p_midi->itf_count++;
}
p_desc = tu_desc_next(p_desc);
- desc_itf = (tusb_desc_interface_t const *)p_desc;
+ desc_itf = (const tusb_desc_interface_t *)p_desc;
}
- TU_VERIFY(p_desc < p_end); // TODO: If MIDI interface comes after Audio Streaming, then max_len did not include the MIDI interface descriptor
- TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass);
+ TU_VERIFY(p_desc < desc_end, 0);
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
}
- TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass);
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, 0);
TU_LOG_DRV("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber;
@@ -250,10 +249,12 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d
p_midi->itf_count++;
desc_cb.desc_midi = desc_itf;
- p_desc = tu_desc_next(p_desc); // next to CS Header
-
bool found_new_interface = false;
- while (tu_desc_in_bounds(p_desc, p_end) && !found_new_interface) {
+ do {
+ p_desc = tu_desc_next(p_desc);
+ if (!tu_desc_in_bounds(p_desc, desc_end)) {
+ break;
+ }
switch (tu_desc_type(p_desc)) {
case TUSB_DESC_INTERFACE:
found_new_interface = true;
@@ -290,39 +291,39 @@ bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *d
case TUSB_DESC_ENDPOINT: {
const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc;
+
p_desc = tu_desc_next(p_desc); // next to CS endpoint
- TU_VERIFY(p_desc < p_end && tu_desc_next(p_desc) <= p_end);
+ TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0);
const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc;
TU_LOG_DRV(" Endpoint and CS_Endpoint descriptor %02x\r\n", p_ep->bEndpointAddress);
+ tu_edpt_stream_t *ep_stream;
if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) {
- p_midi->ep_out = p_ep->bEndpointAddress;
p_midi->tx_cable_count = p_csep->bNumEmbMIDIJack;
desc_cb.desc_epout = p_ep;
-
- TU_ASSERT(tuh_edpt_open(dev_addr, p_ep));
- tu_edpt_stream_open(&p_midi->ep_stream.tx, p_ep);
+ ep_stream = &p_midi->ep_stream.tx;
} else {
- p_midi->ep_in = p_ep->bEndpointAddress;
p_midi->rx_cable_count = p_csep->bNumEmbMIDIJack;
- desc_cb.desc_epin = p_ep;
-
- TU_ASSERT(tuh_edpt_open(dev_addr, p_ep));
- tu_edpt_stream_open(&p_midi->ep_stream.rx, p_ep);
+ desc_cb.desc_epin = p_ep;
+ ep_stream = &p_midi->ep_stream.rx;
}
+ TU_ASSERT(tuh_edpt_open(dev_addr, p_ep), 0);
+ tu_edpt_stream_open(ep_stream, dev_addr, p_ep, tu_edpt_packet_size(p_ep));
+ tu_edpt_stream_clear(ep_stream);
+
break;
}
default: break; // skip unknown descriptor
}
- p_desc = tu_desc_next(p_desc);
- }
- desc_cb.desc_midi_total_len = (uint16_t) ((uintptr_t)p_desc - (uintptr_t) desc_itf);
+ } while (!found_new_interface);
+
+ desc_cb.desc_midi_total_len = (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_start);
p_midi->daddr = dev_addr;
tuh_midi_descriptor_cb(idx, &desc_cb);
- return true;
+ return desc_cb.desc_midi_total_len;
}
bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) {
@@ -333,13 +334,13 @@ bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) {
const tuh_midi_mount_cb_t mount_cb_data = {
.daddr = dev_addr,
- .bInterfaceNumber = itf_num,
+ .bInterfaceNumber = p_midi->bInterfaceNumber,
.rx_cable_count = p_midi->rx_cable_count,
.tx_cable_count = p_midi->tx_cable_count,
};
tuh_midi_mount_cb(idx, &mount_cb_data);
- tu_edpt_stream_read_xfer(dev_addr, &p_midi->ep_stream.rx); // prepare for incoming data
+ tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx); // prepare for incoming data
// No special config things to do for MIDI
usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber);
@@ -379,8 +380,14 @@ bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info) {
desc->bDescriptorType = TUSB_DESC_INTERFACE;
desc->bInterfaceNumber = p_midi->bInterfaceNumber;
- desc->bAlternateSetting = 0;
- desc->bNumEndpoints = (uint8_t)((p_midi->ep_in != 0 ? 1:0) + (p_midi->ep_out != 0 ? 1:0));
+ desc->bAlternateSetting = 0;
+ desc->bNumEndpoints = 0;
+ if (tu_edpt_stream_is_opened(&p_midi->ep_stream.tx)) {
+ desc->bNumEndpoints++;
+ }
+ if (tu_edpt_stream_is_opened(&p_midi->ep_stream.rx)) {
+ desc->bNumEndpoints++;
+ }
desc->bInterfaceClass = TUSB_CLASS_AUDIO;
desc->bInterfaceSubClass = AUDIO_SUBCLASS_MIDI_STREAMING;
desc->bInterfaceProtocol = 0;
@@ -412,7 +419,7 @@ uint32_t tuh_midi_read_available(uint8_t idx) {
uint32_t tuh_midi_write_flush(uint8_t idx) {
TU_VERIFY(idx < CFG_TUH_MIDI);
midih_interface_t *p_midi = &_midi_host[idx];
- return tu_edpt_stream_write_xfer(p_midi->daddr, &p_midi->ep_stream.tx);
+ return tu_edpt_stream_write_xfer(&p_midi->ep_stream.tx);
}
//--------------------------------------------------------------------+
@@ -425,7 +432,7 @@ uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize)
uint32_t count4 = tu_min32(bufsize, tu_edpt_stream_read_available(&p_midi->ep_stream.rx));
count4 = tu_align4(count4); // round down to multiple of 4
TU_VERIFY(count4 > 0, 0);
- return tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, buffer, count4);
+ return tu_edpt_stream_read(&p_midi->ep_stream.rx, buffer, count4);
}
uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) {
@@ -434,7 +441,7 @@ uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bu
const uint32_t bufsize4 = tu_align4(bufsize);
TU_VERIFY(bufsize4 > 0, 0);
- return tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, buffer, bufsize4);
+ return tu_edpt_stream_write(&p_midi->ep_stream.tx, buffer, bufsize4);
}
//--------------------------------------------------------------------+
@@ -448,17 +455,17 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
midi_driver_stream_t *stream = &p_midi->stream_write;
uint32_t byte_count = 0;
- while ((byte_count < bufsize) && (tu_edpt_stream_write_available(p_midi->daddr, &p_midi->ep_stream.tx) >= 4)) {
- uint8_t const data = buffer[byte_count];
+ while ((byte_count < bufsize) && (tu_edpt_stream_write_available(&p_midi->ep_stream.tx) >= 4)) {
+ const uint8_t data = buffer[byte_count];
byte_count++;
if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) {
// real-time messages need to be sent right away
midi_driver_stream_t streamrt;
- streamrt.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
+ streamrt.buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE);
streamrt.buffer[1] = data;
streamrt.index = 2;
streamrt.total = 2;
- uint32_t const count = tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, streamrt.buffer, 4);
+ const uint32_t count = tu_edpt_stream_write(&p_midi->ep_stream.tx, streamrt.buffer, 4);
TU_ASSERT(count == 4, byte_count); // Check FIFO overflown, since we already check fifo remaining. It is probably race condition
} else if (stream->index == 0) {
//------------- New event packet -------------//
@@ -469,9 +476,9 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
stream->buffer[1] = data;
// Check to see if we're still in a SysEx transmit.
- if (stream->buffer[0] == MIDI_CIN_SYSEX_START) {
+ if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START) {
if (data == MIDI_STATUS_SYSEX_END) {
- stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE);
stream->total = 2;
} else {
stream->total = 4;
@@ -499,6 +506,7 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
stream->total = 2;
}
+ stream->buffer[0] |= (uint8_t)(cable_num << 4);
} else {
// Pack individual bytes if we don't support packing them into words.
stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf);
@@ -513,8 +521,8 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
stream->buffer[stream->index] = data;
stream->index++;
// See if this byte ends a SysEx.
- if (stream->buffer[0] == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) {
- stream->buffer[0] = MIDI_CIN_SYSEX_START + (stream->index - 1);
+ if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) {
+ stream->buffer[0] = (uint8_t)((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1)));
stream->total = stream->index;
}
}
@@ -527,7 +535,7 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *bu
}
TU_LOG3_MEM(stream->buffer, 4, 2);
- const uint32_t count = tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, stream->buffer, 4);
+ const uint32_t count = tu_edpt_stream_write(&p_midi->ep_stream.tx, stream->buffer, 4);
// complete current event packet, reset stream
stream->index = 0;
@@ -549,7 +557,7 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff
return 0;
}
*p_cable_num = (one_byte >> 4) & 0xf;
- uint32_t nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
+ uint32_t nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
static uint16_t cable_sysex_in_progress;// bit i is set if received MIDI_STATUS_SYSEX_START but not MIDI_STATUS_SYSEX_END
while (nread == 4 && bytes_buffered < bufsize) {
*p_cable_num = (p_midi->stream_read.buffer[0] >> 4) & 0x0f;
@@ -577,7 +585,7 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff
}
else {
// bad packet discard
- nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
+ nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
continue;
}
} else if (status < MIDI_STATUS_SYSEX_START) {
@@ -624,7 +632,7 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff
}
else {
// bad packet discard
- nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
+ nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
continue;
}
@@ -637,7 +645,7 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff
uint8_t new_cable = (one_byte >> 4) & 0xf;
if (new_cable == *p_cable_num) {
// still on the same cable. Continue reading the stream
- nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
+ nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4);
}
}
}
diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h
index 06554a03d..8fdfd8966 100644
--- a/src/class/midi/midi_host.h
+++ b/src/class/midi/midi_host.h
@@ -31,45 +31,45 @@
#include "midi.h"
#ifdef __cplusplus
- extern "C" {
+extern "C" {
#endif
//--------------------------------------------------------------------+
// Class Driver Configuration
//--------------------------------------------------------------------+
#ifndef CFG_TUH_MIDI_RX_BUFSIZE
-#define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
#ifndef CFG_TUH_MIDI_TX_BUFSIZE
-#define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
#ifndef CFG_TUH_MIDI_EP_BUFSIZE
-#define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MPS
+ #define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MAX
#endif
// Enable the MIDI stream read/write API. Some library can work with raw USB MIDI packet
// Disable this can save driver footprint.
#ifndef CFG_TUH_MIDI_STREAM_API
-#define CFG_TUH_MIDI_STREAM_API 1
+ #define CFG_TUH_MIDI_STREAM_API 1
#endif
//--------------------------------------------------------------------+
// Application Types
//--------------------------------------------------------------------+
typedef struct {
- const tusb_desc_interface_t* desc_audio_control;
- const tusb_desc_interface_t* desc_midi; // start of whole midi interface descriptor
- uint16_t desc_midi_total_len;
+ const tusb_desc_interface_t *desc_audio_control;
+ const tusb_desc_interface_t *desc_midi; // start of whole midi interface descriptor
+ uint16_t desc_midi_total_len;
- const uint8_t* desc_header;
- const uint8_t* desc_element;
- const tusb_desc_endpoint_t* desc_epin; // endpoint IN descriptor, CS_ENDPOINT is right after
- const tusb_desc_endpoint_t* desc_epout; // endpoint OUT descriptor, CS_ENDPOINT is right after
+ const uint8_t *desc_header;
+ const uint8_t *desc_element;
+ const tusb_desc_endpoint_t *desc_epin; // endpoint IN descriptor, CS_ENDPOINT is right after
+ const tusb_desc_endpoint_t *desc_epout; // endpoint OUT descriptor, CS_ENDPOINT is right after
- uint8_t jack_num;
- const uint8_t* desc_jack[32]; // list of jack descriptors (embedded + external)
+ uint8_t jack_num;
+ const uint8_t *desc_jack[32]; // list of jack descriptors (embedded + external)
} tuh_midi_descriptor_cb_t;
typedef struct {
@@ -92,7 +92,7 @@ uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num);
// Get Interface information
// return true if index is correct and interface is currently mounted
-bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info);
+bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t *info);
// return the number of virtual midi cables on the device's IN endpoint
uint8_t tuh_midi_get_rx_cable_count(uint8_t idx);
@@ -115,24 +115,22 @@ uint32_t tuh_midi_write_flush(uint8_t idx);
// Read all available MIDI packets from the connected device
// Return number of bytes read (always multiple of 4)
-uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize);
+uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t *buffer, uint32_t bufsize);
// Read a raw MIDI packet from the connected device
// Return true if a packet was returned
-TU_ATTR_ALWAYS_INLINE static inline
-bool tuh_midi_packet_read (uint8_t idx, uint8_t packet[4]) {
- return 4 == tuh_midi_packet_read_n(idx, packet, 4);
+TU_ATTR_ALWAYS_INLINE static inline bool tuh_midi_packet_read(uint8_t idx, uint8_t packet[4]) {
+ return 4 == tuh_midi_packet_read_n(idx, packet, 4);
}
// Write all 4-byte packets, data is locally buffered and only transferred when buffered bytes
// reach the endpoint packet size or tuh_midi_write_flush() is called
-uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize);
+uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t *buffer, uint32_t bufsize);
// Write a 4-bytes packet to the device.
// Returns true if the packet was successfully queued.
-TU_ATTR_ALWAYS_INLINE static inline
-bool tuh_midi_packet_write (uint8_t idx, uint8_t const packet[4]) {
- return 4 == tuh_midi_packet_write_n(idx, packet, 4);
+TU_ATTR_ALWAYS_INLINE static inline bool tuh_midi_packet_write(uint8_t idx, const uint8_t packet[4]) {
+ return 4 == tuh_midi_packet_write_n(idx, packet, 4);
}
//--------------------------------------------------------------------+
@@ -143,7 +141,7 @@ bool tuh_midi_packet_write (uint8_t idx, uint8_t const packet[4]) {
// Queue a message to the device using stream API. data is locally buffered and only transferred when buffered bytes
// reach the endpoint packet size or tuh_midi_write_flush() is called
// Returns number of bytes was successfully queued.
-uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *p_buffer, uint32_t bufsize);
+uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, const uint8_t *p_buffer, uint32_t bufsize);
// Get the MIDI stream from the device. Set the value pointed
// to by p_cable_num to the MIDI cable number intended to receive it.
@@ -152,6 +150,13 @@ uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *p_
// Note that this function ignores the CIN field of the MIDI packet
// because a number of commercial devices out there do not encode
// it properly.
+//
+// NOTE: this function terminates when it encounters an event whose cable
+// number differs from the one being returned. Applications should invoke
+// it in a loop until it returns 0 (or until tuh_midi_read_available()
+// returns 0) to guarantee the stream FIFO is fully drained per callback.
+// Leaving bytes in the FIFO across callbacks can prevent subsequent bulk
+// IN transfers from landing.
uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize);
#endif
@@ -162,10 +167,10 @@ uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buff
// Invoked when MIDI interface is detected in enumeration. Application can copy/parse descriptor if needed.
// Note: may be fired before tuh_midi_mount_cb(), therefore midi interface is not mounted/ready.
-void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data);
+void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t *desc_cb_data);
// Invoked when device with MIDI interface is mounted.
-void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data);
+void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t *mount_cb_data);
// Invoked when device with MIDI interface is un-mounted
void tuh_midi_umount_cb(uint8_t idx);
@@ -179,12 +184,12 @@ void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes);
//--------------------------------------------------------------------+
// Internal Class Driver API
//--------------------------------------------------------------------+
-bool midih_init (void);
-bool midih_deinit (void);
-bool midih_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len);
-bool midih_set_config (uint8_t dev_addr, uint8_t itf_num);
-bool midih_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
-void midih_close (uint8_t daddr);
+bool midih_init(void);
+bool midih_deinit(void);
+uint16_t midih_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len);
+bool midih_set_config(uint8_t dev_addr, uint8_t itf_num);
+bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+void midih_close(uint8_t daddr);
#ifdef __cplusplus
}