Add MIDI host support to tinyusb
This commit is contained in:
		
							
								
								
									
										111
									
								
								src/class/midi/README_midi_host.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/class/midi/README_midi_host.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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 informaton 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 endpont 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. | ||||
| @@ -71,8 +71,8 @@ typedef enum | ||||
|   MIDI_CIN_SYSEX_END_1BYTE   = 5, // SysEx ends with 1 data, or 1 byte system common message | ||||
|   MIDI_CIN_SYSEX_END_2BYTE   = 6, // SysEx ends with 2 data | ||||
|   MIDI_CIN_SYSEX_END_3BYTE   = 7, // SysEx ends with 3 data | ||||
|   MIDI_CIN_NOTE_ON           = 8, | ||||
|   MIDI_CIN_NOTE_OFF          = 9, | ||||
|   MIDI_CIN_NOTE_ON           = 9, | ||||
|   MIDI_CIN_NOTE_OFF          = 8, | ||||
|   MIDI_CIN_POLY_KEYPRESS     = 10, | ||||
|   MIDI_CIN_CONTROL_CHANGE    = 11, | ||||
|   MIDI_CIN_PROGRAM_CHANGE    = 12, | ||||
| @@ -106,6 +106,11 @@ enum | ||||
|   MIDI_STATUS_SYSREAL_SYSTEM_RESET           = 0xFF, | ||||
| }; | ||||
|  | ||||
| enum | ||||
| { | ||||
|   MIDI_MAX_DATA_VAL = 0x7F, | ||||
| }; | ||||
|  | ||||
| /// MIDI Interface Header Descriptor | ||||
| typedef struct TU_ATTR_PACKED | ||||
| { | ||||
| @@ -201,6 +206,16 @@ typedef struct TU_ATTR_PACKED | ||||
|     uint8_t  iElement;          \ | ||||
|  } | ||||
|  | ||||
| // This descriptor follows the standard bulk data endpoint descriptor | ||||
| typedef struct | ||||
| { | ||||
|   uint8_t bLength            ; ///< Size of this descriptor in bytes (4+bNumEmbMIDIJack) | ||||
|   uint8_t bDescriptorType    ; ///< Descriptor Type, must be CS_ENDPOINT | ||||
|   uint8_t bDescriptorSubType ; ///< Descriptor SubType, must be MS_GENERAL | ||||
|   uint8_t bNumEmbMIDIJack;   ; ///< Number of embedded MIDI jacks associated with this endpoint | ||||
|   uint8_t baAssocJackID[];   ; ///< A list of associated jacks | ||||
| } midi_cs_desc_endpoint_t; | ||||
|  | ||||
| /** @} */ | ||||
|  | ||||
| #ifdef __cplusplus | ||||
|   | ||||
							
								
								
									
										944
									
								
								src/class/midi/midi_host.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										944
									
								
								src/class/midi/midi_host.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,944 @@ | ||||
| /*  | ||||
|  * 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 (TUSB_OPT_HOST_ENABLED && CFG_TUH_MIDI) | ||||
|  | ||||
| #include "host/usbh.h" | ||||
| #include "host/usbh_classdriver.h" | ||||
|  | ||||
| #include "midi_host.h" | ||||
|  | ||||
| //--------------------------------------------------------------------+ | ||||
| // MACRO CONSTANT TYPEDEF | ||||
| //--------------------------------------------------------------------+ | ||||
| #ifndef CFG_TUH_MAX_CABLES | ||||
|   #define CFG_TUH_MAX_CABLES 16 | ||||
| #endif | ||||
| #define CFG_TUH_MIDI_RX_BUFSIZE 64 | ||||
| #define CFG_TUH_MIDI_TX_BUFSIZE 64 | ||||
| #ifndef CFG_TUH_MIDI_EP_BUFSIZE | ||||
|   #define CFG_TUH_MIDI_EP_BUFSIZE 64 | ||||
| #endif | ||||
|  | ||||
| // TODO: refactor to share code with the MIDI Device driver | ||||
| typedef struct | ||||
| { | ||||
|   uint8_t buffer[4]; | ||||
|   uint8_t index; | ||||
|   uint8_t total; | ||||
| }midi_stream_t; | ||||
|  | ||||
| typedef struct | ||||
| { | ||||
|   uint8_t dev_addr; | ||||
|   uint8_t itf_num; | ||||
|  | ||||
|   uint8_t ep_in;          // IN endpoint address | ||||
|   uint8_t ep_out;         // OUT endpoint address | ||||
|   uint16_t ep_in_max;     // min( CFG_TUH_MIDI_RX_BUFSIZE, wMaxPacketSize of the IN endpoint) | ||||
|   uint16_t ep_out_max;    //  min( CFG_TUH_MIDI_TX_BUFSIZE, wMaxPacketSize of the OUT endpoint) | ||||
|  | ||||
|   uint8_t num_cables_rx;  // IN endpoint CS descriptor bNumEmbMIDIJack value | ||||
|   uint8_t num_cables_tx;  // OUT endpoint CS descriptor bNumEmbMIDIJack value | ||||
|  | ||||
|   // 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_stream_t stream_write; | ||||
|   midi_stream_t stream_read; | ||||
|  | ||||
|   /*------------- From this point, data is not cleared by bus reset -------------*/ | ||||
|   // Endpoint FIFOs | ||||
|   tu_fifo_t rx_ff; | ||||
|   tu_fifo_t tx_ff; | ||||
|   | ||||
|  | ||||
|   uint8_t rx_ff_buf[CFG_TUH_MIDI_RX_BUFSIZE]; | ||||
|   uint8_t tx_ff_buf[CFG_TUH_MIDI_TX_BUFSIZE]; | ||||
|  | ||||
|   #if CFG_FIFO_MUTEX | ||||
|   osal_mutex_def_t rx_ff_mutex; | ||||
|   osal_mutex_def_t tx_ff_mutex; | ||||
|   #endif | ||||
|  | ||||
|   // Endpoint Transfer buffer | ||||
|   CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUH_MIDI_EP_BUFSIZE]; | ||||
|   CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUH_MIDI_EP_BUFSIZE]; | ||||
|  | ||||
|   bool configured; | ||||
|  | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
| #define MAX_STRING_INDICES 32 | ||||
|   uint8_t all_string_indices[MAX_STRING_INDICES]; | ||||
|   uint8_t num_string_indices; | ||||
| #define MAX_IN_JACKS 8 | ||||
| #define MAX_OUT_JACKS 8 | ||||
|   struct { | ||||
|     uint8_t jack_id; | ||||
|     int8_t jack_type; | ||||
|     uint8_t string_index; | ||||
|   } in_jack_info[MAX_IN_JACKS]; | ||||
|   uint8_t next_in_jack; | ||||
|   struct { | ||||
|     uint8_t jack_id; | ||||
|     uint8_t jack_type; | ||||
|     uint8_t num_source_ids; | ||||
|     uint8_t source_ids[MAX_IN_JACKS/4]; | ||||
|     uint8_t string_index; | ||||
|   } out_jack_info[MAX_OUT_JACKS]; | ||||
|   uint8_t next_out_jack; | ||||
|   uint8_t ep_in_associated_jacks[MAX_OUT_JACKS/2]; | ||||
|   uint8_t ep_out_associated_jacks[MAX_IN_JACKS/2]; | ||||
| #endif | ||||
| }midih_interface_t; | ||||
|  | ||||
| static midih_interface_t _midi_host[CFG_TUH_DEVICE_MAX]; | ||||
|  | ||||
| static midih_interface_t *get_midi_host(uint8_t dev_addr) | ||||
| { | ||||
|   TU_VERIFY(dev_addr >0 && dev_addr <= CFG_TUH_DEVICE_MAX); | ||||
|   return (_midi_host + dev_addr - 1); | ||||
| } | ||||
|  | ||||
| //------------- Internal prototypes -------------// | ||||
| static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi); | ||||
|  | ||||
| //--------------------------------------------------------------------+ | ||||
| // USBH API | ||||
| //--------------------------------------------------------------------+ | ||||
| void midih_init(void) | ||||
| { | ||||
|   tu_memclr(&_midi_host, sizeof(_midi_host)); | ||||
|   // config fifos | ||||
|   for (int inst = 0; inst < CFG_TUH_DEVICE_MAX; inst++) | ||||
|   { | ||||
|     midih_interface_t *p_midi_host = &_midi_host[inst]; | ||||
|     tu_fifo_config(&p_midi_host->rx_ff, p_midi_host->rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, 1, false); // true, true | ||||
|     tu_fifo_config(&p_midi_host->tx_ff, p_midi_host->tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, 1, false); // OBVS. | ||||
|  | ||||
|   #if CFG_FIFO_MUTEX | ||||
|     tu_fifo_config_mutex(&p_midi_host->rx_ff, NULL, osal_mutex_create(&p_midi_host->rx_ff_mutex)); | ||||
|     tu_fifo_config_mutex(&p_midi_host->tx_ff, osal_mutex_create(&p_midi_host->tx_ff_mutex), NULL); | ||||
|   #endif | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) | ||||
| { | ||||
|   (void)result; | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   if ( ep_addr == p_midi_host->ep_in) | ||||
|   { | ||||
|     if (0 == xferred_bytes) | ||||
|     { | ||||
|       return true; // No data to handle | ||||
|     } | ||||
|  | ||||
|     // receive new data if available | ||||
|     uint32_t packets_queued = 0; | ||||
|     if (xferred_bytes) | ||||
|     { | ||||
|       // put in the RX FIFO only non-zero MIDI IN 4-byte packets | ||||
|       uint8_t* buf = p_midi_host->epin_buf; | ||||
|       uint32_t npackets = xferred_bytes / 4; | ||||
|       uint32_t packet_num; | ||||
|       for (packet_num = 0; packet_num < npackets; packet_num++) | ||||
|       { | ||||
|         // some devices send back all zero packets even if there is no data ready | ||||
|         uint32_t packet = (uint32_t)((*buf)<<24) | ((uint32_t)(*(buf+1))<<16) | ((uint32_t)(*(buf+2))<<8) | ((uint32_t)(*(buf+3))); | ||||
|         if (packet != 0) | ||||
|         { | ||||
|           tu_fifo_write_n(&p_midi_host->rx_ff, buf, 4); | ||||
|           ++packets_queued; | ||||
|           TU_LOG3("MIDI RX=%08x\r\n", packet); | ||||
|         } | ||||
|         buf += 4; | ||||
|       } | ||||
|     } | ||||
|     // invoke receive callback if available | ||||
|     if (tuh_midi_rx_cb) | ||||
|     { | ||||
|       tuh_midi_rx_cb(dev_addr, packets_queued); | ||||
|     } | ||||
|   } | ||||
|   else if ( ep_addr == p_midi_host->ep_out ) | ||||
|   { | ||||
|     if (0 == write_flush(dev_addr, p_midi_host)) | ||||
|     { | ||||
|       // 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_host->tx_ff) && xferred_bytes && (0 == (xferred_bytes % p_midi_host->ep_out_max)) ) | ||||
|       { | ||||
|         if ( usbh_edpt_claim(dev_addr, p_midi_host->ep_out) ) | ||||
|         { | ||||
|           TU_ASSERT(usbh_edpt_xfer(dev_addr, p_midi_host->ep_out, XFER_RESULT_SUCCESS, 0)); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (tuh_midi_tx_cb) | ||||
|     { | ||||
|       tuh_midi_tx_cb(dev_addr); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void midih_close(uint8_t dev_addr) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   if (p_midi_host == NULL) | ||||
|     return; | ||||
|   if (tuh_midi_umount_cb) | ||||
|     tuh_midi_umount_cb(dev_addr, 0); | ||||
|   tu_fifo_clear(&p_midi_host->rx_ff); | ||||
|   tu_fifo_clear(&p_midi_host->tx_ff); | ||||
|   p_midi_host->ep_in = 0; | ||||
|   p_midi_host->ep_in_max = 0; | ||||
|   p_midi_host->ep_out = 0; | ||||
|   p_midi_host->ep_out_max = 0; | ||||
|   p_midi_host->itf_num = 0; | ||||
|   p_midi_host->num_cables_rx = 0; | ||||
|   p_midi_host->num_cables_tx = 0; | ||||
|   p_midi_host->dev_addr = 255; // invalid | ||||
|   p_midi_host->configured = false; | ||||
|   tu_memclr(&p_midi_host->stream_read, sizeof(p_midi_host->stream_read)); | ||||
|   tu_memclr(&p_midi_host->stream_write, sizeof(p_midi_host->stream_write)); | ||||
| } | ||||
|  | ||||
| //--------------------------------------------------------------------+ | ||||
| // Enumeration | ||||
| //--------------------------------------------------------------------+ | ||||
| bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) | ||||
| { | ||||
|   (void) rhport; | ||||
|  | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|  | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   p_midi_host->num_string_indices = 0; | ||||
|   TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); | ||||
|   // There can be just a MIDI interface or an audio and a MIDI interface. Only open the MIDI interface | ||||
|   uint8_t const *p_desc = (uint8_t const *) desc_itf; | ||||
|   uint16_t len_parsed = 0; | ||||
|   if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) | ||||
|   { | ||||
|     // Keep track of any string descriptor that might be here | ||||
|     if (desc_itf->iInterface != 0) | ||||
|         p_midi_host->all_string_indices[p_midi_host->num_string_indices++] = desc_itf->iInterface; | ||||
|     // This driver does not support audio streaming. However, if this is the audio control interface | ||||
|     // there might be a MIDI interface following it. Search through every descriptor until a MIDI | ||||
|     // interface is found or the end of the descriptor is found | ||||
|     while (len_parsed < max_len && (desc_itf->bInterfaceClass != TUSB_CLASS_AUDIO || desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING)) | ||||
|     { | ||||
|       len_parsed += desc_itf->bLength; | ||||
|       p_desc = tu_desc_next(p_desc); | ||||
|       desc_itf = (tusb_desc_interface_t const *)p_desc; | ||||
|     } | ||||
|  | ||||
|     TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); | ||||
|   } | ||||
|   TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass); | ||||
|   len_parsed += desc_itf->bLength; | ||||
|  | ||||
|   // Keep track of any string descriptor that might be here | ||||
|   if (desc_itf->iInterface != 0) | ||||
|       p_midi_host->all_string_indices[p_midi_host->num_string_indices++] = desc_itf->iInterface; | ||||
|  | ||||
|   p_desc = tu_desc_next(p_desc); | ||||
|   TU_LOG1("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); | ||||
|   // Find out if getting the MIDI class specific interface header or an endpoint descriptor | ||||
|   // or a class-specific endpoint descriptor | ||||
|   // Jack descriptors or element descriptors must follow the cs interface header, | ||||
|   // but this driver does not support devices that contain element descriptors | ||||
|  | ||||
|   // assume it is an interface header | ||||
|   midi_desc_header_t const *p_mdh = (midi_desc_header_t const *)p_desc; | ||||
|   TU_VERIFY((p_mdh->bDescriptorType == TUSB_DESC_CS_INTERFACE && p_mdh->bDescriptorSubType == MIDI_CS_INTERFACE_HEADER) ||  | ||||
|     (p_mdh->bDescriptorType == TUSB_DESC_CS_ENDPOINT && p_mdh->bDescriptorSubType == MIDI_CS_ENDPOINT_GENERAL) || | ||||
|     p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT); | ||||
|  | ||||
|   uint8_t prev_ep_addr = 0; // the CS endpoint descriptor is associated with the previous endpoint descrptor | ||||
|   p_midi_host->itf_num = desc_itf->bInterfaceNumber; | ||||
|   tusb_desc_endpoint_t const* in_desc = NULL; | ||||
|   tusb_desc_endpoint_t const* out_desc = NULL; | ||||
|   while (len_parsed < max_len) | ||||
|   { | ||||
|     TU_VERIFY((p_mdh->bDescriptorType == TUSB_DESC_CS_INTERFACE) ||  | ||||
|       (p_mdh->bDescriptorType == TUSB_DESC_CS_ENDPOINT && p_mdh->bDescriptorSubType == MIDI_CS_ENDPOINT_GENERAL) || | ||||
|       p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT); | ||||
|  | ||||
|     if (p_mdh->bDescriptorType == TUSB_DESC_CS_INTERFACE) { | ||||
|       // The USB host doesn't really need this information unless it uses | ||||
|       // the string descriptor for a jack or Element | ||||
|  | ||||
|       // assume it is an input jack | ||||
|       midi_desc_in_jack_t const *p_mdij = (midi_desc_in_jack_t const *)p_desc; | ||||
|       if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_HEADER) | ||||
|       { | ||||
|         TU_LOG2("Found MIDI Interface Header\r\b"); | ||||
|       } | ||||
|       else if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_IN_JACK) | ||||
|       { | ||||
|         // Then it is an in jack.  | ||||
|         TU_LOG2("Found in jack\r\n"); | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
|         if (p_midi_host->next_in_jack < MAX_IN_JACKS) | ||||
|         { | ||||
|           p_midi_host->in_jack_info[p_midi_host->next_in_jack].jack_id = p_mdij->bJackID; | ||||
|           p_midi_host->in_jack_info[p_midi_host->next_in_jack].jack_type = p_mdij->bJackType; | ||||
|           p_midi_host->in_jack_info[p_midi_host->next_in_jack].string_index = p_mdij->iJack; | ||||
|           ++p_midi_host->next_in_jack; | ||||
|           // Keep track of any string descriptor that might be here | ||||
|           if (p_mdij->iJack != 0) | ||||
|             p_midi_host->all_string_indices[p_midi_host->num_string_indices++] = p_mdij->iJack; | ||||
|  | ||||
|         } | ||||
| #endif | ||||
|       } | ||||
|       else if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_OUT_JACK) | ||||
|       { | ||||
|         // then it is an out jack | ||||
|         TU_LOG2("Found out jack\r\n"); | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
|         if (p_midi_host->next_out_jack < MAX_OUT_JACKS) | ||||
|         { | ||||
|           midi_desc_out_jack_t const *p_mdoj = (midi_desc_out_jack_t const *)p_desc; | ||||
|           p_midi_host->out_jack_info[p_midi_host->next_out_jack].jack_id = p_mdoj->bJackID; | ||||
|           p_midi_host->out_jack_info[p_midi_host->next_out_jack].jack_type = p_mdoj->bJackType; | ||||
|           p_midi_host->out_jack_info[p_midi_host->next_out_jack].num_source_ids = p_mdoj->bNrInputPins; | ||||
|           struct associated_jack_s { | ||||
|               uint8_t id; | ||||
|               uint8_t pin; | ||||
|           } *associated_jack = (struct associated_jack_s *)(p_desc+6); | ||||
|           int jack; | ||||
|           for (jack = 0; jack < p_mdoj->bNrInputPins; jack++) | ||||
|           { | ||||
|             p_midi_host->out_jack_info[p_midi_host->next_out_jack].source_ids[jack] = associated_jack->id; | ||||
|           } | ||||
|           p_midi_host->out_jack_info[p_midi_host->next_out_jack].string_index = *(p_desc+6+p_mdoj->bNrInputPins*2); | ||||
|           ++p_midi_host->next_out_jack; | ||||
|           if (p_mdoj->iJack != 0) | ||||
|             p_midi_host->all_string_indices[p_midi_host->num_string_indices++] = p_mdoj->iJack; | ||||
|         } | ||||
| #endif | ||||
|       } | ||||
|       else if (p_mdij->bDescriptorSubType == MIDI_CS_INTERFACE_ELEMENT) | ||||
|       { | ||||
|         // the it is an element; | ||||
|     #if CFG_MIDI_HOST_DEVSTRINGS | ||||
|         TU_LOG1("Found element; strings not supported\r\n"); | ||||
|     #else | ||||
|         TU_LOG2("Found element\r\n"); | ||||
|     #endif | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         TU_LOG2("Unknown CS Interface sub-type %u\r\n", p_mdij->bDescriptorSubType); | ||||
|         TU_VERIFY(false); // unknown CS Interface sub-type | ||||
|       } | ||||
|       len_parsed += p_mdij->bLength; | ||||
|     } | ||||
|     else if (p_mdh->bDescriptorType == TUSB_DESC_CS_ENDPOINT) | ||||
|     { | ||||
|       TU_LOG2("found CS_ENDPOINT Descriptor for %u\r\n", prev_ep_addr); | ||||
|       TU_VERIFY(prev_ep_addr != 0); | ||||
|       // parse out the mapping between the device's embedded jacks and the endpoints | ||||
|       // Each embedded IN jack is assocated with an OUT endpoint | ||||
|       midi_cs_desc_endpoint_t const* p_csep = (midi_cs_desc_endpoint_t const*)p_mdh; | ||||
|       if (tu_edpt_dir(prev_ep_addr) == TUSB_DIR_OUT) | ||||
|       { | ||||
|         TU_VERIFY(p_midi_host->ep_out == prev_ep_addr); | ||||
|         TU_VERIFY(p_midi_host->num_cables_tx == 0); | ||||
|         p_midi_host->num_cables_tx = p_csep->bNumEmbMIDIJack; | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
|         uint8_t jack; | ||||
|         uint8_t max_jack = p_midi_host->num_cables_tx; | ||||
|         if (max_jack > sizeof(p_midi_host->ep_out_associated_jacks)) | ||||
|         { | ||||
|             max_jack = sizeof(p_midi_host->ep_out_associated_jacks); | ||||
|         } | ||||
|         for (jack = 0; jack < max_jack; jack++) | ||||
|         { | ||||
|           p_midi_host->ep_out_associated_jacks[jack] = p_csep->baAssocJackID[jack]; | ||||
|         } | ||||
| #endif | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         TU_VERIFY(p_midi_host->ep_in == prev_ep_addr); | ||||
|         TU_VERIFY(p_midi_host->num_cables_rx == 0); | ||||
|         p_midi_host->num_cables_rx = p_csep->bNumEmbMIDIJack; | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
|         uint8_t jack; | ||||
|         uint8_t max_jack = p_midi_host->num_cables_rx; | ||||
|         if (max_jack > sizeof(p_midi_host->ep_in_associated_jacks)) | ||||
|         { | ||||
|             max_jack = sizeof(p_midi_host->ep_in_associated_jacks); | ||||
|         } | ||||
|         for (jack = 0; jack < max_jack; jack++) | ||||
|         { | ||||
|           p_midi_host->ep_in_associated_jacks[jack] = p_csep->baAssocJackID[jack]; | ||||
|         } | ||||
| #endif | ||||
|       } | ||||
|       len_parsed += p_csep->bLength; | ||||
|       prev_ep_addr = 0; | ||||
|     } | ||||
|     else if (p_mdh->bDescriptorType == TUSB_DESC_ENDPOINT) { | ||||
|       // parse out the bulk endpoint info | ||||
|       tusb_desc_endpoint_t const *p_ep = (tusb_desc_endpoint_t const *)p_mdh; | ||||
|       TU_LOG2("found ENDPOINT Descriptor for %u\r\n", p_ep->bEndpointAddress); | ||||
|       if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) | ||||
|       { | ||||
|         TU_VERIFY(p_midi_host->ep_out == 0); | ||||
|         TU_VERIFY(p_midi_host->num_cables_tx == 0); | ||||
|         p_midi_host->ep_out = p_ep->bEndpointAddress; | ||||
|         p_midi_host->ep_out_max = p_ep->wMaxPacketSize; | ||||
|         if (p_midi_host->ep_out_max > CFG_TUH_MIDI_TX_BUFSIZE) | ||||
|           p_midi_host->ep_out_max = CFG_TUH_MIDI_TX_BUFSIZE; | ||||
|         prev_ep_addr = p_midi_host->ep_out; | ||||
|         out_desc = p_ep; | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         TU_VERIFY(p_midi_host->ep_in == 0); | ||||
|         TU_VERIFY(p_midi_host->num_cables_rx == 0); | ||||
|         p_midi_host->ep_in = p_ep->bEndpointAddress; | ||||
|         p_midi_host->ep_in_max = p_ep->wMaxPacketSize; | ||||
|         if (p_midi_host->ep_in_max > CFG_TUH_MIDI_RX_BUFSIZE) | ||||
|           p_midi_host->ep_in_max = CFG_TUH_MIDI_RX_BUFSIZE; | ||||
|         prev_ep_addr = p_midi_host->ep_in; | ||||
|         in_desc = p_ep; | ||||
|       } | ||||
|       len_parsed += p_mdh->bLength; | ||||
|     } | ||||
|     p_desc = tu_desc_next(p_desc); | ||||
|     p_mdh = (midi_desc_header_t const *)p_desc; | ||||
|   } | ||||
|   TU_VERIFY((p_midi_host->ep_out != 0 && p_midi_host->num_cables_tx != 0) || | ||||
|             (p_midi_host->ep_in != 0 && p_midi_host->num_cables_rx != 0)); | ||||
|   TU_LOG1("MIDI descriptor parsed successfully\r\n"); | ||||
|   // remove duplicate string indices | ||||
|   for (int idx=0; idx < p_midi_host->num_string_indices; idx++) { | ||||
|       for (int jdx = idx+1; jdx < p_midi_host->num_string_indices; jdx++) { | ||||
|           while (jdx < p_midi_host->num_string_indices &&  p_midi_host->all_string_indices[idx] == p_midi_host->all_string_indices[jdx]) { | ||||
|               // delete the duplicate by overwriting it with the last entry and reducing the number of entries by 1 | ||||
|               p_midi_host->all_string_indices[jdx] = p_midi_host->all_string_indices[p_midi_host->num_string_indices-1]; | ||||
|               --p_midi_host->num_string_indices; | ||||
|           } | ||||
|       } | ||||
|   } | ||||
|   if (in_desc) | ||||
|   { | ||||
|     TU_ASSERT(tuh_edpt_open(dev_addr, in_desc)); | ||||
|     // Some devices always return exactly the request length so transfers won't complete | ||||
|     // unless you assume every transfer is the last one. | ||||
|     // TODO usbh_edpt_force_last_buffer(dev_addr, p_midi_host->ep_in, true); | ||||
|   } | ||||
|   if (out_desc) | ||||
|   { | ||||
|     TU_ASSERT(tuh_edpt_open(dev_addr, out_desc)); | ||||
|   } | ||||
|   p_midi_host->dev_addr = dev_addr; | ||||
|  | ||||
|   if (tuh_midi_mount_cb) | ||||
|   { | ||||
|     tuh_midi_mount_cb(dev_addr, p_midi_host->ep_in, p_midi_host->ep_out, p_midi_host->num_cables_rx, p_midi_host->num_cables_tx); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool tuh_midi_configured(uint8_t dev_addr) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   return p_midi_host->configured; | ||||
| } | ||||
|  | ||||
| bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) | ||||
| { | ||||
|   (void) itf_num; | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   p_midi_host->configured = true; | ||||
|  | ||||
|   // TODO I don't think there are any special config things to do for MIDI | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| //--------------------------------------------------------------------+ | ||||
| // Stream API | ||||
| //--------------------------------------------------------------------+ | ||||
| static uint32_t write_flush(uint8_t dev_addr, midih_interface_t* midi) | ||||
| { | ||||
|   // No data to send | ||||
|   if ( !tu_fifo_count(&midi->tx_ff) ) return 0; | ||||
|  | ||||
|   // skip if previous transfer not complete | ||||
|   TU_VERIFY( usbh_edpt_claim(dev_addr, midi->ep_out) ); | ||||
|  | ||||
|   uint16_t count = tu_fifo_read_n(&midi->tx_ff, midi->epout_buf, midi->ep_out_max); | ||||
|  | ||||
|   if (count) | ||||
|   { | ||||
|     TU_ASSERT( usbh_edpt_xfer(dev_addr, midi->ep_out, midi->epout_buf, count), 0 ); | ||||
|     return count; | ||||
|   }else | ||||
|   { | ||||
|     // Release endpoint since we don't make any transfer | ||||
|     usbh_edpt_release(dev_addr, midi->ep_out); | ||||
|     return 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool tuh_midi_read_poll( uint8_t dev_addr ) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   bool result = false; | ||||
|  | ||||
|   bool in_edpt_not_busy = !usbh_edpt_busy(dev_addr, p_midi_host->ep_in); | ||||
|   if (in_edpt_not_busy) | ||||
|   { | ||||
|     TU_LOG2("Requesting poll IN endpoint %d\r\n", p_midi_host->ep_in); | ||||
|     TU_ASSERT(usbh_edpt_xfer(p_midi_host->dev_addr, p_midi_host->ep_in, _midi_host->epin_buf, _midi_host->ep_in_max), 0); | ||||
|     result = true; | ||||
|   } | ||||
|   else | ||||
|   { | ||||
|     // Maybe the IN endpoint is only busy because the RP2040 host hardware | ||||
|     // is retrying a NAK'd IN transfer forever. Try aborting the NAK'd | ||||
|     // transfer to allow other transfers to happen on the one shared | ||||
|     // epx endpoint. | ||||
|     // TODO for RP2040 USB shared endpoint: usbh_edpt_clear_in_on_nak(p_midi_host->dev_addr, p_midi_host->ep_in); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   TU_VERIFY(cable_num < p_midi_host->num_cables_tx); | ||||
|   midi_stream_t *stream = &p_midi_host->stream_write; | ||||
|  | ||||
|   uint32_t i = 0; | ||||
|   while ( (i < bufsize) && (tu_fifo_remaining(&p_midi_host->tx_ff) >= 4) ) | ||||
|   { | ||||
|     uint8_t const data = buffer[i]; | ||||
|     i++; | ||||
|     if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) | ||||
|     { | ||||
|       // real-time messages need to be sent right away | ||||
|       midi_stream_t streamrt; | ||||
|       streamrt.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; | ||||
|       streamrt.buffer[1] = data; | ||||
|       streamrt.index = 2; | ||||
|       streamrt.total = 2; | ||||
|       uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, streamrt.buffer, 4); | ||||
|       // FIFO overflown, since we already check fifo remaining. It is probably race condition | ||||
|       TU_ASSERT(count == 4, i); | ||||
|     } | ||||
|     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] = (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] = (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] = 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, i); | ||||
|       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 idx = stream->total; idx < 4; idx++) stream->buffer[idx] = 0; | ||||
|       TU_LOG3_MEM(stream->buffer, 4, 2); | ||||
|  | ||||
|       uint16_t const count = tu_fifo_write_n(&p_midi_host->tx_ff, 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, i); | ||||
|     } | ||||
|   } | ||||
|   return i; | ||||
| } | ||||
|  | ||||
| bool tuh_midi_packet_write (uint8_t dev_addr, uint8_t const packet[4]) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|  | ||||
|   if (tu_fifo_remaining(&p_midi_host->tx_ff) < 4) | ||||
|   { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   tu_fifo_write_n(&p_midi_host->tx_ff, packet, 4); | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| uint32_t tuh_midi_stream_flush( uint8_t dev_addr ) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|  | ||||
|   uint32_t bytes_flushed = 0; | ||||
|   if (!usbh_edpt_busy(p_midi_host->dev_addr, p_midi_host->ep_out)) | ||||
|   { | ||||
|     bytes_flushed = write_flush(dev_addr, p_midi_host); | ||||
|   } | ||||
|   return bytes_flushed; | ||||
| } | ||||
| //--------------------------------------------------------------------+ | ||||
| // Helper | ||||
| //--------------------------------------------------------------------+ | ||||
| uint8_t tuh_midih_get_num_tx_cables (uint8_t dev_addr) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   TU_VERIFY(p_midi_host->ep_out != 0); // returns 0 if fails | ||||
|   return p_midi_host->num_cables_tx; | ||||
| } | ||||
|  | ||||
| uint8_t tuh_midih_get_num_rx_cables (uint8_t dev_addr) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   TU_VERIFY(p_midi_host->ep_in != 0); // returns 0 if fails | ||||
|   return p_midi_host->num_cables_rx; | ||||
| } | ||||
|  | ||||
| bool tuh_midi_packet_read (uint8_t dev_addr, uint8_t packet[4]) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   TU_VERIFY(tu_fifo_count(&p_midi_host->rx_ff) >= 4); | ||||
|   return tu_fifo_read_n(&p_midi_host->rx_ff, packet, 4) == 4; | ||||
| } | ||||
|  | ||||
| uint32_t tuh_midi_stream_read (uint8_t dev_addr, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   uint32_t bytes_buffered = 0; | ||||
|   TU_ASSERT(p_cable_num); | ||||
|   TU_ASSERT(p_buffer); | ||||
|   TU_ASSERT(bufsize); | ||||
|   uint8_t one_byte; | ||||
|   if (!tu_fifo_peek(&p_midi_host->rx_ff, &one_byte)) | ||||
|   { | ||||
|     return 0; | ||||
|   } | ||||
|   *p_cable_num = (one_byte >> 4) & 0xf; | ||||
|   uint32_t nread = tu_fifo_read_n(&p_midi_host->rx_ff, p_midi_host->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_host->stream_read.buffer[0] >> 4) & 0x0f; | ||||
|     uint8_t bytes_to_add_to_stream = 0; | ||||
|     if (*p_cable_num < p_midi_host->num_cables_rx) | ||||
|     { | ||||
|       // ignore the CIN field; too many devices out there encode this wrong | ||||
|       uint8_t status = p_midi_host->stream_read.buffer[1]; | ||||
|       uint16_t cable_mask = 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; | ||||
|           uint8_t idx; | ||||
|           for (idx = 2; idx < 4; idx++) | ||||
|           { | ||||
|             if (p_midi_host->stream_read.buffer[idx] <= MIDI_MAX_DATA_VAL) | ||||
|             { | ||||
|               ++bytes_to_add_to_stream; | ||||
|             } | ||||
|             else if (p_midi_host->stream_read.buffer[idx] == MIDI_STATUS_SYSEX_END) | ||||
|             { | ||||
|               ++bytes_to_add_to_stream; | ||||
|               cable_sysex_in_progress &= ~cable_mask; | ||||
|               idx = 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 &= ~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 &= ~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; | ||||
|       } | ||||
|     } | ||||
|     uint8_t idx; | ||||
|     for (idx = 1; idx <= bytes_to_add_to_stream; idx++) | ||||
|     { | ||||
|       *p_buffer++ = p_midi_host->stream_read.buffer[idx]; | ||||
|     } | ||||
|     bytes_buffered += bytes_to_add_to_stream; | ||||
|     nread = 0; | ||||
|     if (tu_fifo_peek(&p_midi_host->rx_ff, &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_fifo_read_n(&p_midi_host->rx_ff, p_midi_host->stream_read.buffer, 4); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return bytes_buffered; | ||||
| } | ||||
|  | ||||
| uint8_t tuh_midi_get_num_rx_cables(uint8_t dev_addr) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   uint8_t num_cables = 0; | ||||
|   if (p_midi_host) | ||||
|   { | ||||
|     num_cables = p_midi_host->num_cables_rx; | ||||
|   } | ||||
|   return num_cables; | ||||
| } | ||||
|  | ||||
| uint8_t tuh_midi_get_num_tx_cables(uint8_t dev_addr) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   uint8_t num_cables = 0; | ||||
|   if (p_midi_host) | ||||
|   { | ||||
|     num_cables = p_midi_host->num_cables_tx; | ||||
|   } | ||||
|   return num_cables; | ||||
| } | ||||
|  | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
| static uint8_t find_string_index(midih_interface_t *ptr, uint8_t jack_id) | ||||
| { | ||||
|   uint8_t index = 0; | ||||
|   uint8_t assoc; | ||||
|   for (assoc = 0; index == 0 && assoc < ptr->next_in_jack; assoc++) | ||||
|   { | ||||
|     if (jack_id == ptr->in_jack_info[assoc].jack_id) | ||||
|     { | ||||
|       index = ptr->in_jack_info[assoc].string_index; | ||||
|     } | ||||
|   } | ||||
|   for (assoc = 0; index == 0 && assoc < ptr->next_out_jack; assoc++) | ||||
|   { | ||||
|     if (jack_id == ptr->out_jack_info[assoc].jack_id) | ||||
|     { | ||||
|       index = ptr->out_jack_info[assoc].string_index; | ||||
|     } | ||||
|   } | ||||
|   return index; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
| uint8_t tuh_midi_get_rx_cable_istrings(uint8_t dev_addr, uint8_t* istrings, uint8_t max_istrings) | ||||
| { | ||||
|   uint8_t nstrings = 0; | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   nstrings = p_midi_host->num_cables_rx; | ||||
|   if (nstrings > max_istrings) | ||||
|   { | ||||
|       nstrings = max_istrings; | ||||
|   } | ||||
|   uint8_t jack; | ||||
|   for (jack=0; jack<nstrings; jack++) | ||||
|   { | ||||
|     uint8_t jack_id = p_midi_host->ep_in_associated_jacks[jack]; | ||||
|     istrings[jack] = find_string_index(p_midi_host, jack_id); | ||||
|   } | ||||
|   return nstrings; | ||||
| } | ||||
|  | ||||
| uint8_t tuh_midi_get_tx_cable_istrings(uint8_t dev_addr, uint8_t* istrings, uint8_t max_istrings) | ||||
| { | ||||
|   uint8_t nstrings = 0; | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   nstrings = p_midi_host->num_cables_tx; | ||||
|   if (nstrings > max_istrings) | ||||
|   { | ||||
|       nstrings = max_istrings; | ||||
|   } | ||||
|   uint8_t jack; | ||||
|   for (jack=0; jack<nstrings; jack++) | ||||
|   { | ||||
|     uint8_t jack_id = p_midi_host->ep_out_associated_jacks[jack]; | ||||
|     istrings[jack] = find_string_index(p_midi_host, jack_id); | ||||
|   } | ||||
|   return nstrings; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| uint8_t tuh_midi_get_all_istrings(uint8_t dev_addr, const uint8_t** istrings) | ||||
| { | ||||
|   midih_interface_t *p_midi_host = get_midi_host(dev_addr); | ||||
|   TU_VERIFY(p_midi_host != NULL); | ||||
|   uint8_t nstrings = p_midi_host->num_string_indices; | ||||
|   if (nstrings) | ||||
|     *istrings = p_midi_host->all_string_indices; | ||||
|   return nstrings; | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										137
									
								
								src/class/midi/midi_host.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/class/midi/midi_host.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| /*  | ||||
|  * 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 | ||||
| //--------------------------------------------------------------------+ | ||||
|  | ||||
| // TODO Highspeed bulk transfer can be up to 512 bytes | ||||
| #ifndef CFG_TUH_HID_EPIN_BUFSIZE | ||||
| #define CFG_TUH_HID_EPIN_BUFSIZE 64 | ||||
| #endif | ||||
|  | ||||
| #ifndef CFG_TUH_HID_EPOUT_BUFSIZE | ||||
| #define CFG_TUH_HID_EPOUT_BUFSIZE 64 | ||||
| #endif | ||||
|  | ||||
|  | ||||
| //--------------------------------------------------------------------+ | ||||
| // Application API (Single Interface) | ||||
| //--------------------------------------------------------------------+ | ||||
| bool     tuh_midi_configured      (uint8_t dev_addr); | ||||
| uint32_t tuh_midi_available    (uint8_t dev_addr); | ||||
|  | ||||
| // return the number of virtual midi cables on the device's OUT endpoint | ||||
| uint8_t tuh_midih_get_num_tx_cables (uint8_t dev_addr); | ||||
|  | ||||
| // return the number of virtual midi cables on the device's IN endpoint | ||||
| uint8_t tuh_midih_get_num_rx_cables (uint8_t dev_addr); | ||||
|  | ||||
| // request available data from the device. tuh_midi_message_received_cb() will | ||||
| // be called if the device has any data to send. Otherwise, the device will | ||||
| // respond NAK. This function blocks until the transfer completes or the | ||||
| // devices sends NAK. | ||||
| // This function will return false if the hardware is busy. | ||||
| bool tuh_midi_read_poll( uint8_t dev_addr ); | ||||
|  | ||||
| // Queue a packet to the device. The application | ||||
| // must call tuh_midi_stream_flush to actually have the | ||||
| // data go out. It is up to the application to properly | ||||
| // format this packet; this function does not check. | ||||
| // Returns true if the packet was successfully queued. | ||||
| bool tuh_midi_packet_write (uint8_t dev_addr, uint8_t const packet[4]); | ||||
|  | ||||
| // Queue a message to the device. The application | ||||
| // must call tuh_midi_stream_flush to actually have the | ||||
| // data go out. | ||||
| uint32_t tuh_midi_stream_write (uint8_t dev_addr, uint8_t cable_num, uint8_t const* p_buffer, uint32_t bufsize); | ||||
|  | ||||
| // 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_stream_flush( uint8_t dev_addr); | ||||
|  | ||||
| // 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 dev_addr, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); | ||||
|  | ||||
| // Read a raw MIDI packet from the connected device | ||||
| // This function does not parse the packet format | ||||
| // Return true if a packet was returned | ||||
| bool tuh_midi_packet_read (uint8_t dev_addr, uint8_t packet[4]); | ||||
|  | ||||
| uint8_t tuh_midi_get_num_rx_cables(uint8_t dev_addr); | ||||
| uint8_t tuh_midi_get_num_tx_cables(uint8_t dev_addr); | ||||
| #if CFG_MIDI_HOST_DEVSTRINGS | ||||
| uint8_t tuh_midi_get_rx_cable_istrings(uint8_t dev_addr, uint8_t* istrings, uint8_t max_istrings); | ||||
| uint8_t tuh_midi_get_tx_cable_istrings(uint8_t dev_addr, uint8_t* istrings, uint8_t max_istrings); | ||||
| uint8_t tuh_midi_get_all_istrings(uint8_t dev_addr, const uint8_t** istrings); | ||||
| #endif | ||||
| //--------------------------------------------------------------------+ | ||||
| // Internal Class Driver API | ||||
| //--------------------------------------------------------------------+ | ||||
| void midih_init       (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 dev_addr); | ||||
|  | ||||
| //--------------------------------------------------------------------+ | ||||
| // Callbacks (Weak is optional) | ||||
| //--------------------------------------------------------------------+ | ||||
|  | ||||
| // Invoked when device with MIDI interface is mounted. | ||||
| // If the MIDI host application requires MIDI IN, it should requst an | ||||
| // IN transfer here. The device will likely NAK this transfer. How the driver | ||||
| // handles the NAK is hardware dependent. | ||||
| TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t dev_addr, uint8_t in_ep, uint8_t out_ep, uint8_t num_cables_rx, uint16_t num_cables_tx); | ||||
|  | ||||
| // Invoked when device with MIDI interface is un-mounted | ||||
| // For now, the instance parameter is always 0 and can be ignored | ||||
| TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t dev_addr, uint8_t instance); | ||||
|  | ||||
| TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t dev_addr, uint32_t num_packets); | ||||
| TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t dev_addr); | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif /* _TUSB_MIDI_HOST_H_ */ | ||||
		Reference in New Issue
	
	Block a user
	 rppicomidi
					rppicomidi