Add MIDI host support to tinyusb

This commit is contained in:
rppicomidi
2022-08-12 11:06:36 -07:00
committed by atoktoto
parent e434a1dc05
commit 2ddd74fada
8 changed files with 1226 additions and 3 deletions

View File

@@ -93,6 +93,7 @@ if (NOT TARGET _rp2040_family_inclusion_marker)
${TOP}/src/class/cdc/cdc_host.c
${TOP}/src/class/hid/hid_host.c
${TOP}/src/class/msc/msc_host.c
${TOP}/src/class/midi/midi_host.c
${TOP}/src/class/vendor/vendor_host.c
)

View 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.

View File

@@ -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
View 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
View 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_ */

View File

@@ -40,7 +40,7 @@
//--------------------------------------------------------------------+
#ifndef CFG_TUH_ENDPOINT_MAX
#define CFG_TUH_ENDPOINT_MAX (CFG_TUH_HUB + CFG_TUH_HID*2 + CFG_TUH_MSC*2 + CFG_TUH_CDC*3)
#define CFG_TUH_ENDPOINT_MAX (CFG_TUH_HUB + CFG_TUH_HID*2 + CFG_TUH_MSC*2 + CFG_TUH_CDC*3 + CFG_TUH_MIDI*2)
// #ifdef TUP_HCD_ENDPOINT_MAX
// #define CFG_TUH_ENDPPOINT_MAX TUP_HCD_ENDPOINT_MAX
// #else

View File

@@ -166,6 +166,17 @@ static usbh_class_driver_t const usbh_class_drivers[] =
},
#endif
#if CFG_TUH_MIDI
{
DRIVER_NAME("MIDI")
.init = midih_init,
.open = midih_open,
.set_config = midih_set_config,
.xfer_cb = midih_xfer_cb,
.close = midih_close
},
#endif
#if CFG_TUH_HUB
{
DRIVER_NAME("HUB")

View File

@@ -56,6 +56,10 @@
#include "class/cdc/cdc_host.h"
#endif
#if CFG_TUH_MIDI
#include "class/midi/midi_host.h"
#endif
#if CFG_TUH_VENDOR
#include "class/vendor/vendor_host.h"
#endif