/* mbed Microcontroller Library * Copyright (c) 2018-2018 ARM Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "stdint.h" #include "USBMIDI.h" #include "EndpointResolver.h" #include "usb_phy_api.h" #define FLAG_WRITE_DONE (1 << 0) #define FLAG_DISCONNECT (1 << 1) #define FLAG_CONNECT (1 << 2) USBMIDI::USBMIDI(bool connect_blocking, uint16_t vendor_id, uint16_t product_id, uint16_t product_release) : USBDevice(get_usb_phy(), vendor_id, product_id, product_release) { _init(); if (connect_blocking) { USBDevice::connect(); wait_ready(); } else { init(); } } USBMIDI::USBMIDI(USBPhy *phy, uint16_t vendor_id, uint16_t product_id, uint16_t product_release) : USBDevice(phy, vendor_id, product_id, product_release) { _init(); // User or child responsible for calling connect or init } USBMIDI::~USBMIDI() { deinit(); } void USBMIDI::_init() { _bulk_buf_pos = 0; _bulk_buf_size = 0; _data_ready = false; _cur_data = 0; EndpointResolver resolver(endpoint_table()); resolver.endpoint_ctrl(64); _bulk_in = resolver.endpoint_in(USB_EP_TYPE_BULK, MaxSize); _bulk_out = resolver.endpoint_out(USB_EP_TYPE_BULK, MaxSize); MBED_ASSERT(resolver.valid()); } bool USBMIDI::ready() { return _flags.get() & FLAG_CONNECT ? true : false; } void USBMIDI::wait_ready() { _flags.wait_any(FLAG_CONNECT, osWaitForever, false); } // write plain MIDIMessage that will be converted to USBMidi event packet bool USBMIDI::write(MIDIMessage m) { _write_mutex.lock(); bool ret = true; // first byte keeped for retro-compatibility for (int p = 1; p < m.length; p += 3) { uint8_t buf[4]; // Midi message to USBMidi event packet buf[0] = m.data[1] >> 4; // SysEx if (buf[0] == 0xF) { if ((m.length - p) > 3) { // SysEx start or continue buf[0] = 0x4; } else { switch (m.length - p) { case 1: // SysEx end with one byte buf[0] = 0x5; break; case 2: // SysEx end with two bytes buf[0] = 0x6; break; case 3: // SysEx end with three bytes buf[0] = 0x7; break; } } } buf[1] = m.data[p]; if (p + 1 < m.length) { buf[2] = m.data[p + 1]; } else { buf[2] = 0; } if (p + 2 < m.length) { buf[3] = m.data[p + 2]; } else { buf[3] = 0; } _flags.clear(FLAG_WRITE_DONE); USBDevice::write_start(_bulk_in, buf, 4); uint32_t flags = _flags.wait_any(FLAG_WRITE_DONE | FLAG_DISCONNECT, osWaitForever, false); if (flags & FLAG_DISCONNECT) { ret = false; break; } USBDevice::write_finish(_bulk_in); } _write_mutex.unlock(); return ret; } bool USBMIDI::readable() { lock(); bool ret = _data_ready; unlock(); return ret; } bool USBMIDI::read(MIDIMessage *m) { lock(); // Invalidate message m->length = 0; if (!_data_ready) { unlock(); return false; } m->from_raw(_data, _cur_data); _cur_data = 0; _next_message(); if (!_data_ready) { read_start(_bulk_out, _bulk_buf, MaxSize); } unlock(); return true; } void USBMIDI::attach(mbed::Callback callback) { lock(); _callback = callback; unlock(); } void USBMIDI::callback_state_change(DeviceState new_state) { assert_locked(); if (new_state == Configured) { _flags.set(FLAG_CONNECT); _flags.clear(FLAG_DISCONNECT); } else { _flags.set(FLAG_DISCONNECT); _flags.clear(FLAG_CONNECT | FLAG_WRITE_DONE); } } void USBMIDI::callback_request(const setup_packet_t *setup) { assert_locked(); RequestResult result = PassThrough; uint8_t *data = NULL; uint32_t size = 0; complete_request(result, data, size); } void USBMIDI::callback_request_xfer_done(const setup_packet_t *setup, bool aborted) { assert_locked(); complete_request_xfer_done(false); } void USBMIDI::callback_set_configuration(uint8_t configuration) { assert_locked(); if (configuration == DEFAULT_CONFIGURATION) { complete_set_configuration(false); } endpoint_remove_all(); endpoint_add(_bulk_in, MaxSize, USB_EP_TYPE_BULK, &USBMIDI::_in_callback); endpoint_add(_bulk_out, MaxSize, USB_EP_TYPE_BULK, &USBMIDI::_out_callback); read_start(_bulk_out, _bulk_buf, MaxSize); complete_set_configuration(true); } void USBMIDI::callback_set_interface(uint16_t interface, uint8_t alternate) { assert_locked(); complete_set_interface(true); } const uint8_t *USBMIDI::string_iinterface_desc() { static const uint8_t string_iinterface_descriptor[] = { 0x0c, //bLength STRING_DESCRIPTOR, //bDescriptorType 0x03 'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0 //bString iInterface - Audio }; return string_iinterface_descriptor; } const uint8_t *USBMIDI::string_iproduct_desc() { static const uint8_t string_iproduct_descriptor[] = { 0x16, //bLength STRING_DESCRIPTOR, //bDescriptorType 0x03 'M', 0, 'b', 0, 'e', 0, 'd', 0, ' ', 0, 'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0 //bString iProduct - Mbed Audio }; return string_iproduct_descriptor; } const uint8_t *USBMIDI::configuration_desc(uint8_t index) { if (index != 0) { return NULL; } uint8_t config_descriptor_temp[] = { // configuration descriptor 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0xc0, 0x50, // The Audio Interface Collection 0x09, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, // Standard AC Interface Descriptor 0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x01, // Class-specific AC Interface Descriptor 0x09, 0x04, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00, // MIDIStreaming Interface Descriptors 0x07, 0x24, 0x01, 0x00, 0x01, 0x41, 0x00, // Class-Specific MS Interface Header Descriptor // MIDI IN JACKS 0x06, 0x24, 0x02, 0x01, 0x01, 0x00, 0x06, 0x24, 0x02, 0x02, 0x02, 0x00, // MIDI OUT JACKS 0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00, 0x09, 0x24, 0x03, 0x02, 0x06, 0x01, 0x01, 0x01, 0x00, // OUT endpoint - Standard MS Bulk Data Endpoint Descriptor 0x09, // bLength 0x05, // bDescriptorType _bulk_out, // bEndpointAddress 0x02, // bmAttributes 0x40, // wMaxPacketSize (LSB) 0x00, // wMaxPacketSize (MSB) 0x00, // bInterval (milliseconds) 0x00, // bRefresh 0x00, // bSynchAddress 0x05, 0x25, 0x01, 0x01, 0x01, // IN endpoint - Standard MS Bulk Data Endpoint Descriptor 0x09, // bLength 0x05, // bDescriptorType _bulk_in, // bEndpointAddress 0x02, // bmAttributes 0x40, // wMaxPacketSize (LSB) 0x00, // wMaxPacketSize (MSB) 0x00, // bInterval (milliseconds) 0x00, // bRefresh 0x00, // bSynchAddress 0x05, 0x25, 0x01, 0x01, 0x03, }; MBED_ASSERT(sizeof(config_descriptor_temp) == sizeof(_config_descriptor)); memcpy(_config_descriptor, config_descriptor_temp, sizeof(config_descriptor_temp)); return _config_descriptor; } void USBMIDI::_in_callback() { assert_locked(); _flags.set(FLAG_WRITE_DONE); } void USBMIDI::_out_callback() { assert_locked(); _bulk_buf_size = read_finish(_bulk_out); _bulk_buf_pos = 0; if (_callback && _next_message()) { _callback(); return; } read_start(_bulk_out, _bulk_buf, MaxSize); } bool USBMIDI::_next_message() { assert_locked(); bool data_ready = false; while (_bulk_buf_pos < _bulk_buf_size) { uint8_t data_read; bool data_end = true; switch (_bulk_buf[_bulk_buf_pos]) { case 0x2: // Two-bytes System Common Message - undefined in USBMidi 1.0 data_read = 2; break; case 0x4: // SysEx start or continue data_end = false; data_read = 3; break; case 0x5: // Single-byte System Common Message or SysEx end with one byte data_read = 1; break; case 0x6: // SysEx end with two bytes data_read = 2; break; case 0xC: // Program change data_read = 2; break; case 0xD: // Channel pressure data_read = 2; break; case 0xF: // Single byte data_read = 1; break; default: // Others three-bytes messages data_read = 3; break; } for (uint8_t j = 1; j < data_read + 1; j++) { if (_cur_data < sizeof(_data)) { _data[_cur_data] = _bulk_buf[_bulk_buf_pos + j]; } _cur_data++; } _bulk_buf_pos += 4; if (data_end) { // Message is ready to be read data_ready = true; break; } } _data_ready = data_ready; return data_ready; }