summaryrefslogtreecommitdiff
path: root/src/class
diff options
context:
space:
mode:
authorHa Thach <[email protected]>2025-03-09 19:01:10 +0700
committerGitHub <[email protected]>2025-03-09 19:01:10 +0700
commit02a630b3da54f68df87a1605661013e177e58067 (patch)
treebf36afa4414efd0911a1cb5b33a3c627f3641623 /src/class
parent29ffd57237554b1f2339af543e3789ae04d3b29b (diff)
parentee234a84caf59d23395ed70f5e1c9d772e3f958b (diff)
Merge pull request #1627 from atoktoto/midihost
RP2040 MIDI Host
Diffstat (limited to 'src/class')
-rw-r--r--src/class/audio/audio.h1
-rw-r--r--src/class/audio/audio_device.c4
-rw-r--r--src/class/cdc/cdc_host.c4
-rw-r--r--src/class/cdc/cdc_host.h8
-rw-r--r--src/class/midi/README_midi_host.md111
-rw-r--r--src/class/midi/midi.h163
-rw-r--r--src/class/midi/midi_device.c45
-rw-r--r--src/class/midi/midi_host.c595
-rw-r--r--src/class/midi/midi_host.h189
9 files changed, 997 insertions, 123 deletions
diff --git a/src/class/audio/audio.h b/src/class/audio/audio.h
index 2f97c0f23..0d1acadcc 100644
--- a/src/class/audio/audio.h
+++ b/src/class/audio/audio.h
@@ -661,6 +661,7 @@ typedef struct TU_ATTR_PACKED
uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors.
uint8_t bmControls ; ///< See: audio_cs_ac_interface_control_pos_t.
} audio_desc_cs_ac_interface_t;
+TU_VERIFY_STATIC(sizeof(audio_desc_cs_ac_interface_t) == 9, "size is not correct");
/// AUDIO Clock Source Descriptor (4.7.2.1)
typedef struct TU_ATTR_PACKED
diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c
index 8a130b7e2..388168424 100644
--- a/src/class/audio/audio_device.c
+++ b/src/class/audio/audio_device.c
@@ -628,10 +628,6 @@ static uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio);
#if (CFG_TUD_AUDIO_ENABLE_EP_IN && (CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL || CFG_TUD_AUDIO_ENABLE_ENCODING)) || (CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING)
static void audiod_parse_for_AS_params(audiod_function_t *audio, uint8_t const *p_desc, uint8_t const *p_desc_end, uint8_t const as_itf);
-
-static inline uint8_t tu_desc_subtype(void const *desc) {
- return ((uint8_t const *) desc)[2];
-}
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL
diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c
index e817ebc7e..4058857c5 100644
--- a/src/class/cdc/cdc_host.c
+++ b/src/class/cdc/cdc_host.c
@@ -691,10 +691,10 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t
}
} else if ( ep_addr == p_cdc->stream.rx.ep_addr ) {
#if CFG_TUH_CDC_FTDI
- if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI) {
+ if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI && xferred_bytes > 2) {
// FTDI reserve 2 bytes for status
// uint8_t status[2] = {p_cdc->stream.rx.ep_buf[0], p_cdc->stream.rx.ep_buf[1]};
- tu_edpt_stream_read_xfer_complete_offset(&p_cdc->stream.rx, xferred_bytes, 2);
+ tu_edpt_stream_read_xfer_complete_with_buf(&p_cdc->stream.rx, p_cdc->stream.rx.ep_buf+2, xferred_bytes-2);
}else
#endif
{
diff --git a/src/class/cdc/cdc_host.h b/src/class/cdc/cdc_host.h
index df975b2f0..8c5399534 100644
--- a/src/class/cdc/cdc_host.h
+++ b/src/class/cdc/cdc_host.h
@@ -49,22 +49,22 @@
// RX FIFO size
#ifndef CFG_TUH_CDC_RX_BUFSIZE
-#define CFG_TUH_CDC_RX_BUFSIZE USBH_EPSIZE_BULK_MAX
+#define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MPS
#endif
// RX Endpoint size
#ifndef CFG_TUH_CDC_RX_EPSIZE
-#define CFG_TUH_CDC_RX_EPSIZE USBH_EPSIZE_BULK_MAX
+#define CFG_TUH_CDC_RX_EPSIZE TUH_EPSIZE_BULK_MPS
#endif
// TX FIFO size
#ifndef CFG_TUH_CDC_TX_BUFSIZE
-#define CFG_TUH_CDC_TX_BUFSIZE USBH_EPSIZE_BULK_MAX
+#define CFG_TUH_CDC_TX_BUFSIZE TUH_EPSIZE_BULK_MPS
#endif
// TX Endpoint size
#ifndef CFG_TUH_CDC_TX_EPSIZE
-#define CFG_TUH_CDC_TX_EPSIZE USBH_EPSIZE_BULK_MAX
+#define CFG_TUH_CDC_TX_EPSIZE TUH_EPSIZE_BULK_MPS
#endif
//--------------------------------------------------------------------+
diff --git a/src/class/midi/README_midi_host.md b/src/class/midi/README_midi_host.md
new file mode 100644
index 000000000..efaf7a13d
--- /dev/null
+++ b/src/class/midi/README_midi_host.md
@@ -0,0 +1,111 @@
+# MIDI HOST DRIVER
+This README file contains the design notes and limitations of the
+MIDI host driver.
+
+# MAXIMUM NUMBER OF MIDI DEVICES ATTACHED TO HOST
+In this version of the driver, only one MIDI device is supported. This
+constraint may change in the future.
+
+# MAXIMUM NUMBER OF ENDPOINTS
+Although the USB MIDI 1.0 Class specification allows an arbitrary number
+of endpoints, this driver supports at most one USB BULK DATA IN endpoint
+and one USB BULK DATA OUT endpoint. Each endpoint can support up to 16
+virtual cables. If a device has multiple IN endpoints or multiple OUT
+endpoints, it will fail to enumerate.
+
+Most USB MIDI devices contain both an IN endpoint and an OUT endpoint,
+but not all do. For example, some USB pedals only support an OUT endpoint.
+This driver allows that.
+
+# PUBLIC API
+Applications interact with this driver via 8-bit buffers of MIDI messages
+formed using the rules for sending bytes on a 5-pin DIN cable per the
+original MIDI 1.0 specification.
+
+To send a message to a device, the Host application composes a sequence
+of status and data bytes in a byte array and calls the API function.
+The arguments of the function are a pointer to the byte array, the number
+of bytes in the array, and the target virtual cable number 0-15.
+
+When the host driver receives a message from the device, the host driver
+will call a callback function that the host application registers. This
+callback function contains a pointer to a message buffer, a message length,
+and the virtual cable number of the message buffer. One complete bulk IN
+endpoint transfer might contain multiple messages targeted to different
+virtual cables.
+
+# SUBCLASS AUDIO CONTROL
+A MIDI device does not absolutely need to have an Audio Control Interface,
+unless it adheres to the USB Audio Class 2 spec, but many devices
+have them even if the devices do not have an audio streaming interface.
+Because this driver does not support audio streaming, the descriptor parser
+will skip past any audio control interface and audio streaming interface
+and open only the MIDI interface.
+
+An audio streaming host driver can use this driver by passing a pointer
+to the MIDI interface descriptor that is found after the audio streaming
+interface to the midih_open() function. That is, an audio streaming host
+driver would parse the audio control interface descriptor and then the
+audio streaming interface and endpoint descriptors. When the next descriptor
+pointer points to a MIDI interface descriptor, call midih_open() with that
+descriptor pointer.
+
+# CLASS SPECIFIC INTERFACE AND REQUESTS
+The host driver does not make use of the information in the class specific
+interface descriptors. In the future, a public API could be created to
+retrieve the string descriptors for the names of each ELEMENT,
+IN JACK and OUT JACK, and how the device describes the connections.
+
+This driver also does not support class specific requests to control
+ELEMENT items, nor does it support non-MIDI Streaming bulk endpoints.
+
+# MIDI CLASS SPECIFIC DESCRIPTOR TOTAL LENGTH FIELD IGNORED
+I have observed at least one keyboard by a leading manufacturer that
+sets the wTotalLength field of the Class-Specific MS Interface Header
+Descriptor to include the length of the MIDIStreaming Endpoint
+Descriptors. This is wrong per my reading of the specification.
+
+# MESSAGE BUFFER DETAILS
+Messages buffers composed from USB data received on the IN endpoint will never contain
+running status because USB MIDI 1.0 class does not support that. Messages
+buffers to be sent to the device on the OUT endpoint may contain running status
+(the message might come from a UART data stream from a 5-pin DIN MIDI IN
+cable on the host, for example). The driver may in the future correctly compose
+4-byte USB MIDI Class packets using the running status if need be. However,
+it does not currently do that. Also, use of running status is not a good idea
+overall because a single byte error can really mess up the data stream with no
+way to recover until the next non-real time status byte is in the message buffer.
+
+Message buffers to be sent to the device may contain Real time messages
+such as MIDI clock. Real time messages may be inserted in the message
+byte stream between status and data bytes of another message without disrupting
+the running status. However, because MIDI 1.0 class messages are sent
+as four byte packets, a real-time message so inserted will be re-ordered
+to be sent to the device in a new 4-byte packet immediately before the
+interrupted data stream.
+
+Real time messages the device sends to the host can only appear between
+the status byte and data bytes of the message in System Exclusive messages
+that are longer than 3 bytes.
+
+# POORLY FORMED USB MIDI DATA PACKETS FROM THE DEVICE
+Some devices do not properly encode the code index number (CIN) for the
+MIDI message status byte even though the 3-byte data payload correctly encodes
+the MIDI message. This driver looks to the byte after the CIN byte to decide
+how many bytes to place in the message buffer.
+
+Some devices do not properly encode the virtual cable number. If the virtual
+cable number in the CIN data byte of the packet is not less than bNumEmbMIDIJack
+for that endpoint, then the host driver assumes virtual cable 0 and does not
+report an error.
+
+Some MIDI devices will always send back exactly wMaxPacketSize bytes on
+every endpoint even if only one 4-byte packet is required (e.g., NOTE ON).
+These devices send packets with 4 packet bytes 0. This driver ignores all
+zero packets without reporting an error.
+
+# ENUMERATION FAILURES
+The host may fail to enumerate a device if it has too many endpoints, if it has
+if it has a Standard MS Transfer Bulk Data Endpoint Descriptor (not supported),
+if it has a poorly formed descriptor, or if the descriptor is too long for
+the host to read the whole thing.
diff --git a/src/class/midi/midi.h b/src/class/midi/midi.h
index 8ddcdfda2..cd67640e4 100644
--- a/src/class/midi/midi.h
+++ b/src/class/midi/midi.h
@@ -24,13 +24,8 @@
* This file is part of the TinyUSB stack.
*/
-/** \ingroup group_class
- * \defgroup ClassDriver_CDC Communication Device Class (CDC)
- * Currently only Abstract Control Model subclass is supported
- * @{ */
-
-#ifndef _TUSB_MIDI_H__
-#define _TUSB_MIDI_H__
+#ifndef TUSB_MIDI_H_
+#define TUSB_MIDI_H_
#include "common/tusb_common.h"
@@ -39,30 +34,31 @@
#endif
//--------------------------------------------------------------------+
-// Class Specific Descriptor
+// Constants
//--------------------------------------------------------------------+
+enum {
+ MIDI_VERSION_1_0 = 0x0100,
+ MIDI_VERSION_2_0 = 0x0200,
+};
-typedef enum
-{
+typedef enum {
MIDI_CS_INTERFACE_HEADER = 0x01,
MIDI_CS_INTERFACE_IN_JACK = 0x02,
MIDI_CS_INTERFACE_OUT_JACK = 0x03,
MIDI_CS_INTERFACE_ELEMENT = 0x04,
} midi_cs_interface_subtype_t;
-typedef enum
-{
- MIDI_CS_ENDPOINT_GENERAL = 0x01
+typedef enum {
+ MIDI_CS_ENDPOINT_GENERAL = 0x01,
+ MIDI_CS_ENDPOINT_GENERAL_2_0 = 0x02,
} midi_cs_endpoint_subtype_t;
-typedef enum
-{
+typedef enum {
MIDI_JACK_EMBEDDED = 0x01,
MIDI_JACK_EXTERNAL = 0x02
} midi_jack_type_t;
-typedef enum
-{
+typedef enum {
MIDI_CIN_MISC = 0,
MIDI_CIN_CABLE_EVENT = 1,
MIDI_CIN_SYSCOM_2BYTE = 2, // 2 byte system common message e.g MTC, SongSelect
@@ -82,8 +78,7 @@ typedef enum
} midi_code_index_number_t;
// MIDI 1.0 status byte
-enum
-{
+enum {
//------------- System Exclusive -------------//
MIDI_STATUS_SYSEX_START = 0xF0,
MIDI_STATUS_SYSEX_END = 0xF7,
@@ -106,80 +101,54 @@ enum
MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF,
};
+enum {
+ MIDI_MAX_DATA_VAL = 0x7F,
+};
+
+//--------------------------------------------------------------------+
+// Class Specific Descriptor
+//--------------------------------------------------------------------+
+
/// MIDI Interface Header Descriptor
-typedef struct TU_ATTR_PACKED
-{
- uint8_t bLength ; ///< Size of this descriptor in bytes.
- uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific
- uint8_t bDescriptorSubType ; ///< Descriptor SubType
- uint16_t bcdMSC ; ///< MidiStreaming SubClass release number in Binary-Coded Decimal
- uint16_t wTotalLength ;
+typedef struct TU_ATTR_PACKED {
+ uint8_t bLength; ///< Size of this descriptor in bytes.
+ uint8_t bDescriptorType; ///< must be TUSB_DESC_CS_INTERFACE
+ uint8_t bDescriptorSubType;///< Descriptor SubType
+ uint16_t bcdMSC; ///< MidiStreaming SubClass release number in Binary-Coded Decimal
+ uint16_t wTotalLength;
} midi_desc_header_t;
+TU_VERIFY_STATIC(sizeof(midi_desc_header_t) == 7, "size is not correct");
/// MIDI In Jack Descriptor
-typedef struct TU_ATTR_PACKED
-{
- uint8_t bLength ; ///< Size of this descriptor in bytes.
- uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific
- uint8_t bDescriptorSubType ; ///< Descriptor SubType
- uint8_t bJackType ; ///< Embedded or External
- uint8_t bJackID ; ///< Unique ID for MIDI IN Jack
- uint8_t iJack ; ///< string descriptor
+typedef struct TU_ATTR_PACKED {
+ uint8_t bLength; ///< Size of this descriptor in bytes.
+ uint8_t bDescriptorType; ///< Descriptor Type, must be Class-Specific
+ uint8_t bDescriptorSubType;///< Descriptor SubType
+ uint8_t bJackType; ///< Embedded or External
+ uint8_t bJackID; ///< Unique ID for MIDI IN Jack
+ uint8_t iJack; ///< string descriptor
} midi_desc_in_jack_t;
+TU_VERIFY_STATIC(sizeof(midi_desc_in_jack_t) == 6, "size is not correct");
-
-/// MIDI Out Jack Descriptor with single pin
-typedef struct TU_ATTR_PACKED
-{
- uint8_t bLength ; ///< Size of this descriptor in bytes.
- uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific
- uint8_t bDescriptorSubType ; ///< Descriptor SubType
- uint8_t bJackType ; ///< Embedded or External
- uint8_t bJackID ; ///< Unique ID for MIDI IN Jack
- uint8_t bNrInputPins;
-
- uint8_t baSourceID;
- uint8_t baSourcePin;
-
- uint8_t iJack ; ///< string descriptor
-} midi_desc_out_jack_t ;
-
-/// MIDI Out Jack Descriptor with multiple pins
+/// MIDI Out Jack Descriptor with multiple input pins
#define midi_desc_out_jack_n_t(input_num) \
- struct TU_ATTR_PACKED { \
- uint8_t bLength ; \
- uint8_t bDescriptorType ; \
- uint8_t bDescriptorSubType ; \
- uint8_t bJackType ; \
- uint8_t bJackID ; \
- uint8_t bNrInputPins ; \
- struct TU_ATTR_PACKED { \
- uint8_t baSourceID; \
- uint8_t baSourcePin; \
- } pins[input_num]; \
- uint8_t iJack ; \
+ struct TU_ATTR_PACKED { \
+ uint8_t bLength; \
+ uint8_t bDescriptorType; \
+ uint8_t bDescriptorSubType; \
+ uint8_t bJackType; \
+ uint8_t bJackID; \
+ uint8_t bNrInputPins; \
+ struct TU_ATTR_PACKED { \
+ uint8_t baSourceID; \
+ uint8_t baSourcePin; \
+ } input[input_num]; \
+ uint8_t iJack; \
}
-/// MIDI Element Descriptor
-typedef struct TU_ATTR_PACKED
-{
- uint8_t bLength ; ///< Size of this descriptor in bytes.
- uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific
- uint8_t bDescriptorSubType ; ///< Descriptor SubType
- uint8_t bElementID;
-
- uint8_t bNrInputPins;
- uint8_t baSourceID;
- uint8_t baSourcePin;
-
- uint8_t bNrOutputPins;
- uint8_t bInTerminalLink;
- uint8_t bOutTerminalLink;
- uint8_t bElCapsSize;
-
- uint16_t bmElementCaps;
- uint8_t iElement;
-} midi_desc_element_t;
+typedef midi_desc_out_jack_n_t(1) midi_desc_out_jack_1in_t; // 1 input
+typedef midi_desc_out_jack_1in_t midi_desc_out_jack_t; // backward compatible
+TU_VERIFY_STATIC(sizeof(midi_desc_out_jack_1in_t) == 7 + 2 * 1, "size is not correct");
/// MIDI Element Descriptor with multiple pins
#define midi_desc_element_n_t(input_num) \
@@ -201,12 +170,32 @@ typedef struct TU_ATTR_PACKED
uint8_t iElement; \
}
-/** @} */
+// This descriptor follows the standard bulk data endpoint descriptor
+#define midi_desc_cs_endpoint_n_t(jack_num) \
+ struct TU_ATTR_PACKED { \
+ uint8_t bLength; \
+ uint8_t bDescriptorType; \
+ uint8_t bDescriptorSubType; \
+ uint8_t bNumEmbMIDIJack; \
+ uint8_t baAssocJackID[jack_num]; \
+ }
+
+typedef midi_desc_cs_endpoint_n_t() midi_desc_cs_endpoint_t; // empty/flexible jack list
+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");
+
+//--------------------------------------------------------------------+
+// For Internal Driver Use
+//--------------------------------------------------------------------+
+typedef struct {
+ uint8_t buffer[4];
+ uint8_t index;
+ uint8_t total;
+} midi_driver_stream_t;
#ifdef __cplusplus
}
#endif
#endif
-
-/** @} */
diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c
index c545f8287..0bbb3caf4 100644
--- a/src/class/midi/midi_device.c
+++ b/src/class/midi/midi_device.c
@@ -39,13 +39,6 @@
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF
//--------------------------------------------------------------------+
-
-typedef struct {
- uint8_t buffer[4];
- uint8_t index;
- uint8_t total;
-} midid_stream_t;
-
typedef struct {
uint8_t itf_num;
uint8_t ep_in;
@@ -54,8 +47,8 @@ typedef struct {
// For Stream read()/write() API
// Messages are always 4 bytes long, queue them for reading and writing so the
// callers can use the Stream interface with single-byte read/write calls.
- midid_stream_t stream_write;
- midid_stream_t stream_read;
+ midi_driver_stream_t stream_write;
+ midi_driver_stream_t stream_read;
/*------------- From this point, data is not cleared by bus reset -------------*/
// FIFO
@@ -122,7 +115,7 @@ uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num)
(void) cable_num;
midid_interface_t* midi = &_midid_itf[itf];
- const midid_stream_t* stream = &midi->stream_read;
+ const midi_driver_stream_t* stream = &midi->stream_read;
// when using with packet API stream total & index are both zero
return tu_fifo_count(&midi->rx_ff) + (uint8_t) (stream->total - stream->index);
@@ -136,7 +129,7 @@ uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, ui
uint8_t* buf8 = (uint8_t*) buffer;
midid_interface_t* midi = &_midid_itf[itf];
- midid_stream_t* stream = &midi->stream_read;
+ midi_driver_stream_t* stream = &midi->stream_read;
uint32_t total_read = 0;
while( bufsize )
@@ -241,7 +234,7 @@ uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t*
midid_interface_t* midi = &_midid_itf[itf];
TU_VERIFY(midi->ep_in, 0);
- midid_stream_t* stream = &midi->stream_write;
+ midi_driver_stream_t* stream = &midi->stream_write;
uint32_t i = 0;
while ( (i < bufsize) && (tu_fifo_remaining(&midi->tx_ff) >= 4) )
@@ -429,21 +422,21 @@ void midid_reset(uint8_t rhport)
}
}
-uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len)
-{
- // 1st Interface is Audio Control v1
- TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass &&
- AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass &&
- AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol, 0);
-
- uint16_t drv_len = tu_desc_len(desc_itf);
- const uint8_t* p_desc = tu_desc_next(desc_itf);
+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;
- // 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);
+ // 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);
+ }
}
// 2nd Interface is MIDI Streaming
diff --git a/src/class/midi/midi_host.c b/src/class/midi/midi_host.c
new file mode 100644
index 000000000..03144eb45
--- /dev/null
+++ b/src/class/midi/midi_host.c
@@ -0,0 +1,595 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * 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_MIDI)
+
+#include "host/usbh.h"
+#include "host/usbh_pvt.h"
+
+#include "midi_host.h"
+
+// Level where CFG_TUSB_DEBUG must be at least for this driver is logged
+#ifndef CFG_TUH_MIDI_LOG_LEVEL
+ #define CFG_TUH_MIDI_LOG_LEVEL CFG_TUH_LOG_LEVEL
+#endif
+
+#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI_LOG_LEVEL, __VA_ARGS__)
+
+//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data) { (void) idx; (void) desc_cb_data; }
+TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) { (void) idx; (void) mount_cb_data; }
+TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t idx) { (void) idx; }
+TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; }
+TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; }
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber; // interface number of MIDI streaming
+ 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 rx_cable_count; // IN endpoint CS descriptor bNumEmbMIDIJack value
+ uint8_t tx_cable_count; // OUT endpoint CS descriptor bNumEmbMIDIJack value
+
+ #if CFG_TUH_MIDI_STREAM_API
+ // For Stream read()/write() API
+ // Messages are always 4 bytes long, queue them for reading and writing so the
+ // callers can use the Stream interface with single-byte read/write calls.
+ midi_driver_stream_t stream_write;
+ midi_driver_stream_t stream_read;
+ #endif
+
+ // Endpoint stream
+ struct {
+ tu_edpt_stream_t tx;
+ tu_edpt_stream_t rx;
+
+ uint8_t rx_ff_buf[CFG_TUH_MIDI_RX_BUFSIZE];
+ uint8_t tx_ff_buf[CFG_TUH_MIDI_TX_BUFSIZE];
+ } ep_stream;
+
+ bool mounted;
+}midih_interface_t;
+
+typedef struct {
+ TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MPS);
+ TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MPS);
+} midih_epbuf_t;
+
+static midih_interface_t _midi_host[CFG_TUH_MIDI];
+CFG_TUH_MEM_SECTION static midih_epbuf_t _midi_epbuf[CFG_TUH_MIDI];
+
+//--------------------------------------------------------------------+
+// Helper
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_midi_index(void) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) {
+ if (_midi_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_MIDI; idx++) {
+ const midih_interface_t *p_midi = &_midi_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;
+}
+
+//--------------------------------------------------------------------+
+// USBH API
+//--------------------------------------------------------------------+
+bool midih_init(void) {
+ tu_memclr(&_midi_host, sizeof(_midi_host));
+ 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);
+ 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);
+ }
+ return true;
+}
+
+bool midih_deinit(void) {
+ for (size_t i = 0; i < CFG_TUH_MIDI; i++) {
+ midih_interface_t* p_midi = &_midi_host[i];
+ tu_edpt_stream_deinit(&p_midi->ep_stream.rx);
+ tu_edpt_stream_deinit(&p_midi->ep_stream.tx);
+ }
+ return true;
+}
+
+void midih_close(uint8_t daddr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) {
+ midih_interface_t* p_midi = &_midi_host[idx];
+ if (p_midi->daddr == 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;
+ p_midi->daddr = 0;
+ p_midi->mounted = false;
+ tu_memclr(&p_midi->stream_read, sizeof(p_midi->stream_read));
+ tu_memclr(&p_midi->stream_write, sizeof(p_midi->stream_write));
+
+ tu_edpt_stream_close(&p_midi->ep_stream.rx);
+ tu_edpt_stream_close(&p_midi->ep_stream.tx);
+ }
+ }
+}
+
+bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) result;
+ 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];
+
+ if (ep_addr == p_midi->ep_stream.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);
+ 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) {
+ tuh_midi_tx_cb(idx, xferred_bytes);
+
+ if (0 == tu_edpt_stream_write_xfer(dev_addr, &p_midi->ep_stream.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);
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Enumeration
+//--------------------------------------------------------------------+
+bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *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;
+
+ const uint8_t idx = find_new_midi_index();
+ TU_VERIFY(idx < CFG_TUH_MIDI);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ p_midi->itf_count = 0;
+
+ tuh_midi_descriptor_cb_t desc_cb = { 0 };
+ desc_cb.jack_num = 0;
+
+ // There can be just a MIDI or an Audio + MIDI interface
+ // If there is Audio Control Interface + Audio Header descriptor, skip it
+ if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) {
+ TU_VERIFY(max_len > 2*sizeof(tusb_desc_interface_t) + sizeof(audio_desc_cs_ac_interface_t));
+
+ 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);
+ desc_cb.desc_audio_control = desc_itf;
+
+ p_desc = tu_desc_next(p_desc);
+ desc_itf = (const tusb_desc_interface_t *)p_desc;
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass);
+ p_midi->itf_count = 1;
+ }
+ TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass);
+
+ TU_LOG_DRV("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
+ p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber;
+ 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 ((p_desc < p_end) && (tu_desc_next(p_desc) <= p_end) && !found_new_interface) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_INTERFACE:
+ found_new_interface = true;
+ break;
+
+ case TUSB_DESC_CS_INTERFACE:
+ switch (tu_desc_subtype(p_desc)) {
+ case MIDI_CS_INTERFACE_HEADER:
+ TU_LOG_DRV(" Interface Header descriptor\r\n");
+ desc_cb.desc_header = p_desc;
+ break;
+
+ case MIDI_CS_INTERFACE_IN_JACK:
+ case MIDI_CS_INTERFACE_OUT_JACK: {
+ TU_LOG_DRV(" Jack %s %s descriptor \r\n",
+ tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_IN_JACK ? "IN" : "OUT",
+ p_desc[3] == MIDI_JACK_EXTERNAL ? "External" : "Embedded");
+ desc_cb.desc_jack[desc_cb.jack_num++] = p_desc;
+ break;
+ }
+
+ case MIDI_CS_INTERFACE_ELEMENT:
+ TU_LOG_DRV(" Element descriptor\r\n");
+ desc_cb.desc_element = p_desc;
+ break;
+
+ default:
+ TU_LOG_DRV(" Unknown CS Interface sub-type %u\r\n", tu_desc_subtype(p_desc));
+ break;
+ }
+ 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); // next to CS endpoint
+ TU_VERIFY(p_desc < p_end && tu_desc_next(p_desc) <= p_end);
+ 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);
+ 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);
+ } 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);
+ }
+ 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);
+
+ p_midi->daddr = dev_addr;
+ tuh_midi_descriptor_cb(idx, &desc_cb);
+
+ return true;
+}
+
+bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) {
+ uint8_t idx = tuh_midi_itf_get_index(dev_addr, itf_num);
+ TU_ASSERT(idx < CFG_TUH_MIDI);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ p_midi->mounted = true;
+
+ const tuh_midi_mount_cb_t mount_cb_data = {
+ .daddr = dev_addr,
+ .bInterfaceNumber = itf_num,
+ .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
+
+ // No special config things to do for MIDI
+ usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber);
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// API
+//--------------------------------------------------------------------+
+bool tuh_midi_mounted(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ return p_midi->mounted;
+}
+
+uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num) {
+ for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) {
+ const midih_interface_t *p_midi = &_midi_host[idx];
+ if (p_midi->daddr == daddr &&
+ (p_midi->bInterfaceNumber == itf_num ||
+ p_midi->bInterfaceNumber == (uint8_t) (itf_num + p_midi->itf_count - 1))) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+uint8_t tuh_midi_get_tx_cable_count (uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ TU_VERIFY(p_midi->ep_stream.tx.ep_addr != 0, 0);
+ return p_midi->tx_cable_count;
+}
+
+uint8_t tuh_midi_get_rx_cable_count (uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ TU_VERIFY(p_midi->ep_stream.rx.ep_addr != 0, 0);
+ return p_midi->rx_cable_count;
+}
+
+uint32_t tuh_midi_read_available(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_MIDI);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ return tu_edpt_stream_read_available(&p_midi->ep_stream.rx);
+}
+
+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);
+}
+
+//--------------------------------------------------------------------+
+// Packet API
+//--------------------------------------------------------------------+
+uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize) {
+ TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0);
+ midih_interface_t *p_midi = &_midi_host[idx];
+
+ 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);
+}
+
+uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) {
+ TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0);
+ midih_interface_t *p_midi = &_midi_host[idx];
+
+ 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);
+}
+
+//--------------------------------------------------------------------+
+// Stream API
+//--------------------------------------------------------------------+
+#if CFG_TUH_MIDI_STREAM_API
+uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *buffer, uint32_t bufsize) {
+ TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ TU_VERIFY(cable_num < p_midi->tx_cable_count);
+ 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];
+ 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[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);
+ 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 -------------//
+
+ uint8_t const msg = data >> 4;
+
+ stream->index = 2;
+ stream->buffer[1] = data;
+
+ // Check to see if we're still in a SysEx transmit.
+ if (stream->buffer[0] == MIDI_CIN_SYSEX_START) {
+ if (data == MIDI_STATUS_SYSEX_END) {
+ stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
+ stream->total = 2;
+ } else {
+ stream->total = 4;
+ }
+ } 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) {
+ // 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) {
+ // System message
+ 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->buffer[0] = MIDI_CIN_SYSCOM_2BYTE;
+ stream->total = 3;
+ } else if (data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER) {
+ stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE;
+ stream->total = 4;
+ } else {
+ stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE;
+ stream->total = 2;
+ }
+ } else {
+ // Pack individual bytes if we don't support packing them into words.
+ stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf);
+ stream->buffer[2] = 0;
+ stream->buffer[3] = 0;
+ stream->index = 2;
+ stream->total = 2;
+ }
+ } else {
+ //------------- On-going (buffering) packet -------------//
+ TU_ASSERT(stream->index < 4, byte_count);
+ 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);
+ stream->total = stream->index;
+ }
+ }
+
+ // Send out packet
+ if (stream->index >= 2 && stream->index == stream->total) {
+ // zeroes unused bytes
+ for (uint8_t i = stream->total; i < 4; i++) {
+ stream->buffer[i] = 0;
+ }
+ 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);
+
+ // complete current event packet, reset stream
+ stream->index = 0;
+ stream->total = 0;
+
+ // FIFO overflown, since we already check fifo remaining. It is probably race condition
+ TU_ASSERT(count == 4, byte_count);
+ }
+ }
+ return byte_count;
+}
+
+uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) {
+ TU_VERIFY(idx < CFG_TUH_MIDI && p_cable_num && p_buffer && bufsize > 0);
+ midih_interface_t *p_midi = &_midi_host[idx];
+ uint32_t bytes_buffered = 0;
+ uint8_t one_byte;
+ if (!tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) {
+ 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);
+ 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;
+ uint8_t bytes_to_add_to_stream = 0;
+ if (*p_cable_num < p_midi->rx_cable_count) {
+ // ignore the CIN field; too many devices out there encode this wrong
+ uint8_t status = p_midi->stream_read.buffer[1];
+ uint16_t cable_mask = (uint16_t) (1 << *p_cable_num);
+ if (status <= MIDI_MAX_DATA_VAL || status == MIDI_STATUS_SYSEX_START) {
+ if (status == MIDI_STATUS_SYSEX_START) {
+ cable_sysex_in_progress |= cable_mask;
+ }
+ // only add the packet if a sysex message is in progress
+ if (cable_sysex_in_progress & cable_mask) {
+ ++bytes_to_add_to_stream;
+ for (uint8_t i = 2; i < 4; i++) {
+ if (p_midi->stream_read.buffer[i] <= MIDI_MAX_DATA_VAL) {
+ ++bytes_to_add_to_stream;
+ } else if (p_midi->stream_read.buffer[i] == MIDI_STATUS_SYSEX_END) {
+ ++bytes_to_add_to_stream;
+ cable_sysex_in_progress &= (uint16_t) ~cable_mask;
+ i = 4;// force the loop to exit; I hate break statements in loops
+ }
+ }
+ }
+ } else if (status < MIDI_STATUS_SYSEX_START) {
+ // then it is a channel message either three bytes or two
+ uint8_t fake_cin = (status & 0xf0) >> 4;
+ switch (fake_cin) {
+ case MIDI_CIN_NOTE_OFF:
+ case MIDI_CIN_NOTE_ON:
+ case MIDI_CIN_POLY_KEYPRESS:
+ case MIDI_CIN_CONTROL_CHANGE:
+ case MIDI_CIN_PITCH_BEND_CHANGE:
+ bytes_to_add_to_stream = 3;
+ break;
+ case MIDI_CIN_PROGRAM_CHANGE:
+ case MIDI_CIN_CHANNEL_PRESSURE:
+ bytes_to_add_to_stream = 2;
+ break;
+ default:
+ break;// Should not get this
+ }
+ cable_sysex_in_progress &= (uint16_t) ~cable_mask;
+ } else if (status < MIDI_STATUS_SYSREAL_TIMING_CLOCK) {
+ switch (status) {
+ case MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME:
+ case MIDI_STATUS_SYSCOM_SONG_SELECT:
+ bytes_to_add_to_stream = 2;
+ break;
+ case MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER:
+ bytes_to_add_to_stream = 3;
+ break;
+ case MIDI_STATUS_SYSCOM_TUNE_REQUEST:
+ case MIDI_STATUS_SYSEX_END:
+ bytes_to_add_to_stream = 1;
+ break;
+ default:
+ break;
+ cable_sysex_in_progress &= (uint16_t) ~cable_mask;
+ }
+ } else {
+ // Real-time message: can be inserted into a sysex message,
+ // so do don't clear cable_sysex_in_progress bit
+ bytes_to_add_to_stream = 1;
+ }
+ }
+
+ for (uint8_t i = 1; i <= bytes_to_add_to_stream; i++) {
+ *p_buffer++ = p_midi->stream_read.buffer[i];
+ }
+ bytes_buffered += bytes_to_add_to_stream;
+ nread = 0;
+ if (tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) {
+ 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);
+ }
+ }
+ }
+
+ return bytes_buffered;
+}
+#endif
+
+#endif
diff --git a/src/class/midi/midi_host.h b/src/class/midi/midi_host.h
new file mode 100644
index 000000000..b06bb43d7
--- /dev/null
+++ b/src/class/midi/midi_host.h
@@ -0,0 +1,189 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * 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_MIDI_HOST_H_
+#define TUSB_MIDI_HOST_H_
+
+#include "class/audio/audio.h"
+#include "midi.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+#ifndef CFG_TUH_MIDI_RX_BUFSIZE
+#define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MPS
+#endif
+
+#ifndef CFG_TUH_MIDI_TX_BUFSIZE
+#define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MPS
+#endif
+
+#ifndef CFG_TUH_MIDI_EP_BUFSIZE
+#define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MPS
+#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
+#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 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[16]; // list of jack descriptors (embedded + external)
+} tuh_midi_descriptor_cb_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber; // interface number of MIDI streaming
+ uint8_t rx_cable_count;
+ uint8_t tx_cable_count;
+} tuh_midi_mount_cb_t;
+
+//--------------------------------------------------------------------+
+// Application API
+//--------------------------------------------------------------------+
+
+// Check if MIDI interface is mounted
+bool tuh_midi_mounted(uint8_t idx);
+
+// Get Interface index from device address + interface number
+// return TUSB_INDEX_INVALID_8 (0xFF) if not found
+uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num);
+
+// return the number of virtual midi cables on the device's IN endpoint
+uint8_t tuh_midi_get_rx_cable_count(uint8_t idx);
+
+// return the number of virtual midi cables on the device's OUT endpoint
+uint8_t tuh_midi_get_tx_cable_count(uint8_t idx);
+
+// return the raw number of bytes available.
+// Note: this is related but not the same as number of stream bytes available.
+uint32_t tuh_midi_read_available(uint8_t idx);
+
+// Send any queued packets to the device if the host hardware is able to do it
+// Returns the number of bytes flushed to the host hardware or 0 if
+// the host hardware is busy or there is nothing in queue to send.
+uint32_t tuh_midi_write_flush(uint8_t idx);
+
+//--------------------------------------------------------------------+
+// Packet API
+//--------------------------------------------------------------------+
+
+// 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);
+
+// 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);
+}
+
+// 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);
+
+// 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);
+}
+
+//--------------------------------------------------------------------+
+// Stream API
+//--------------------------------------------------------------------+
+#if CFG_TUH_MIDI_STREAM_API
+
+// 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);
+
+// 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.
+// The MIDI stream will be stored in the buffer pointed to by p_buffer.
+// Return the number of bytes added to the buffer.
+// 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.
+uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize);
+
+#endif
+
+//--------------------------------------------------------------------+
+// Callbacks (Weak is optional)
+//--------------------------------------------------------------------+
+
+// 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);
+
+// 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);
+
+// Invoked when device with MIDI interface is un-mounted
+void tuh_midi_umount_cb(uint8_t idx);
+
+// Invoked when received new data
+void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes);
+
+// Invoked when a TX is complete and therefore space becomes available in TX buffer
+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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif