├── LICENSE ├── README.md ├── doc └── images │ ├── AmeNoteHoriz.png │ ├── UMPtoUSBMIDI1_0.png │ └── USBMIDI1_0toUMP.png ├── ump.h ├── ump_device.cpp └── ump_device.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MIDI2.dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AmeNote Logo 4 | 5 | # AmeNoteTM tusb_ump Device Driver for tinyUSB 6 | 7 | AmeNote Universal MIDI Packet (UMP) USB MIDI 2.0 / USB MIDI 1.0 Class Driver for tinyUSB Device 8 | 9 | ## Welcome 10 | 11 | As part of the midi2.dev community and with the intention of contributing back to tinyUSB, AmeNote is providing a class driver for USB MIDI (either 1.0 or 2.0) for tinyUSB USDB Device integrations. With the correct descriptors and configuration, the driver will enable embedded devices using the 12 | [tinyUSB embedded library](https://www.tinyusb.org). 13 | 14 | The driver is compatible with hosts selecting either USB MIDI 2.0 or USB MIDI 1.0 standard. If 15 | USB MIDI 1.0, the driver will translate between USB MIDI 1.0 and UMP packets. All embedded applications will interface using UMP packet data structure as a series of 32-bit words. 16 | 17 | TODO: Example code and integration guide will be provided 18 | 19 | ## UMP to USB MIDI 1.0 20 | 21 | When the host computer initializes the device as a USB MIDI 1.0 device, the UMP packets from embedded applications need to be converted to USB MIDI 1.0 packets to send to host computer. This is achieved by the following translation logic: 22 | 23 | UMP to USB MIDI 1.0 Flowchart 26 | 27 | ## USB MIDI 1.0 to UMP 28 | 29 | Similarly, when initialized as a USB MIDI 1.0 device, the data from the host computer needs to be converted to UMP packets for presentation to the embedded applications. This is achieved b y the following translation logic: 30 | 31 | UMP to USB MIDI 1.0 Flowchart 34 | 35 | ## MIDI Association ([www.midi.org](http://www.midi.org)) 36 | These drivers were developed and tested in conjunction of the ProtoZOA TM MIDI 2.0 Prototyping tool which was developed to support the MIDI Association towards their mission for corporate members to: 37 | - Develop and enhance MIDI to respond to new market needs 38 | - Create new MIDI 2.0 standards with broad industry participation 39 | - Ensure the interoperability of MIDI products 40 | - Protect the term MIDI and MIDI logo markets 41 | - Promote the use of MIDI technology and products. 42 | 43 | The tusb_ump for tinyUSB driver was developed in compliance to the standards provided by the MIDI Association. In addition, many member companies have utilized these drivers along with the ProtoZOA for their own prototyping efforts. These drivers have been through extensive operational testing. 44 | 45 | ## MIT License 46 | 47 | Copyright (c) 2023 MIDI2.dev 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining a copy 50 | of this software and associated documentation files (the "Software"), to deal 51 | in the Software without restriction, including without limitation the rights 52 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 53 | copies of the Software, and to permit persons to whom the Software is 54 | furnished to do so, subject to the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be included in all 57 | copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 65 | SOFTWARE. 66 | 67 | ## Contributors 68 | 69 | We wish to thank and acknowledge all contributors to this project. In particular we would like to callout to the following midi2.dev individuals who contributed early and extensively to the development of the tusb_ump driver. 70 | 71 | | Name | Organization | Email | Contribution | 72 | |:----------|:----------|:----------|:----------| 73 | | Michael Loh | AmeNote | [mloh@AmeNote.com ](mailto:mloh@AmeNote.com) | tinyUSB MIDI 2.0 Device Driver initial integration and other low level components. | 74 | | Mike Kent | AmeNote | [mikekent@AmeNote.com](mailto:mikekent@AmeNote.com) | Concept, Architecture, MIDI 2.0 Technical Support. | 75 | | Andrew Mee | AmeNote (consultant) | [primary.edw@gmail.com ](mailto:primary.edw@gmail.com) | Various firmware integration, MIDI 2.0 and UMP libraries, Capability Inquiry, MIDI 2.0 Technical support, testing. | 76 | | Franz Detro | Native Instruments | [franz.detro@native-instruments.de](mailto:franz.detro@native-instruments.de) | Inputs into usb midi 2.0 class driver to clean up descriptors and control endpoint sync. | 77 | 78 | ## Contributing 79 | 80 | We invite for collaborative and constructive contributions. You can contribute by submitting detailed information in Issues for requests for features and bugs. We also welcome contributions of code. 81 | 82 | TODO: Contribution agreement. 83 | 84 | ##### AmeNote, AmeNote Logo and ProtoZOA are trademarks of AmeNote Inc. 85 | ##### Copyright (c) MIDI2.dev, 2023. 86 | -------------------------------------------------------------------------------- /doc/images/AmeNoteHoriz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midi2-dev/tusb_ump/fbf96019e4bffd6b841082cb3fbc13d49fbd91bd/doc/images/AmeNoteHoriz.png -------------------------------------------------------------------------------- /doc/images/UMPtoUSBMIDI1_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midi2-dev/tusb_ump/fbf96019e4bffd6b841082cb3fbc13d49fbd91bd/doc/images/UMPtoUSBMIDI1_0.png -------------------------------------------------------------------------------- /doc/images/USBMIDI1_0toUMP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midi2-dev/tusb_ump/fbf96019e4bffd6b841082cb3fbc13d49fbd91bd/doc/images/USBMIDI1_0toUMP.png -------------------------------------------------------------------------------- /ump.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * Copyright (c) 2022 Michael Loh (AmeNote.com) 6 | * Copyright (c) 2022 Franz Detro (native-instruments.de) 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | * 26 | * This file is part of the TinyUSB stack. 27 | */ 28 | 29 | /** \ingroup group_class 30 | * \defgroup ClassDriver_CDC Communication Device Class (CDC) 31 | * Currently only Abstract Control Model subclass is supported 32 | * @{ */ 33 | 34 | #ifndef _TUSB_UMP_H__ 35 | #define _TUSB_UMP_H__ 36 | 37 | //#include "common/tusb_common.h" 38 | 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | //--------------------------------------------------------------------+ 44 | // UMP Protocol definitions 45 | //--------------------------------------------------------------------+ 46 | // Message Types 47 | #define UMP_MT_MASK 0xf0 48 | #define UMP_MT_UTILITY 0x00 49 | #define UMP_MT_SYSTEM 0x10 50 | #define UMP_MT_MIDI1_CV 0x20 51 | #define UMP_MT_DATA_64 0x30 52 | #define UMP_MT_MIDI2_CV 0x40 53 | #define UMP_MT_DATA_128 0x50 54 | #define UMP_MT_RESERVED_6 0x60 // 32bits reserved future 55 | #define UMP_MT_RESERVED_7 0x70 // 32bits reserved future 56 | #define UMP_MT_RESERVED_8 0x80 // 64bits reserved future 57 | #define UMP_MT_RESERVED_9 0x90 // 64bits reserved future 58 | #define UMP_MT_RESERVED_A 0xA0 // 64bits reserved future 59 | #define UMP_MT_RESERVED_B 0xB0 // 96bits reserved future 60 | #define UMP_MT_RESERVED_C 0xC0 // 96bits reserved future 61 | #define UMP_MT_FLEX_128 0xd0 62 | #define UMP_MT_RESERVED_E 0xE0 // 128bits reserved future 63 | #define UMP_MT_STREAM_128 0xf0 64 | 65 | // Group Number 66 | #define UMP_GROUP_MASK 0x0f 67 | 68 | // System Exclusive 7-Bit Status 69 | #define UMP_SYSEX7_STATUS_MASK 0xf0 70 | #define UMP_SYSEX7_COMPLETE 0x00 71 | #define UMP_SYSEX7_START 0x10 72 | #define UMP_SYSEX7_CONTINUE 0x20 73 | #define UMP_SYSEX7_END 0x30 74 | #define UMP_SYSEX7_SIZE_MASK 0x0f 75 | 76 | // System Common Status 77 | #define UMP_SYSTEM_MTC 0xf1 // 2 bytes incl status 78 | #define UMP_SYSTEM_SONG_POS_PTR 0xf2 // 3 bytes incl status 79 | #define UMP_SYSTEM_SONG_SELECT 0xf3 // 2 bytes incl status 80 | #define UMP_SYSTEM_UNDEFINED_F4 0xf4 // undefined 81 | #define UMP_SYSTEM_UNDEFINED_F5 0xf5 // undefined 82 | #define UMP_SYSTEM_TUNE_REQ 0xf6 // status byte only 83 | #define UMP_SYSTEM_TIMING_CLK 0xf8 // status byte only 84 | #define UMP_SYSTEM_UNDEFINED_F9 0xf9 // undefined 85 | #define UMP_SYSTEM_START 0xfa // status byte only 86 | #define UMP_SYSTEM_CONTINUE 0xfb // status byte only 87 | #define UMP_SYSTEM_STOP 0xfc // status byte only 88 | #define UMP_SYSTEM_UNDEFINED_FD 0xfd // undefined 89 | #define UMP_SYSTEM_ACTIVE_SENSE 0xfe // status byte only 90 | #define UMP_SYSTEM_RESET 0xff // status byte only 91 | 92 | //--------------------------------------------------------------------+ 93 | // Class Specific Descriptor 94 | //--------------------------------------------------------------------+ 95 | 96 | typedef enum 97 | { 98 | MIDI_CS_INTERFACE_HEADER = 0x01, 99 | MIDI_CS_INTERFACE_IN_JACK = 0x02, 100 | MIDI_CS_INTERFACE_OUT_JACK = 0x03, 101 | MIDI_CS_INTERFACE_ELEMENT = 0x04, 102 | MIDI_CS_INTERFACE_GR_TRM_BLOCK = 0x26, 103 | } midi_cs_interface_subtype_t; 104 | 105 | typedef enum 106 | { 107 | MIDI_CS_ENDPOINT_GENERAL = 0x01, 108 | MIDI20_CS_ENDPOINT_GENERAL = 0x02, 109 | } midi_cs_endpoint_subtype_t; 110 | 111 | typedef enum 112 | { 113 | MIDI_JACK_EMBEDDED = 0x01, 114 | MIDI_JACK_EXTERNAL = 0x02 115 | } midi_jack_type_t; 116 | 117 | typedef enum 118 | { 119 | MIDI_GR_TRM_BLOCK_HEADER = 0x01, 120 | MIDI_GR_TRM_BLOCK = 0x02 121 | } midi_group_terminal_block_type_t; 122 | 123 | typedef enum 124 | { 125 | MIDI_CIN_MISC = 0, 126 | MIDI_CIN_CABLE_EVENT = 1, 127 | MIDI_CIN_SYSCOM_2BYTE = 2, // 2 byte system common message e.g MTC, SongSelect 128 | MIDI_CIN_SYSCOM_3BYTE = 3, // 3 byte system common message e.g SPP 129 | MIDI_CIN_SYSEX_START = 4, // SysEx starts or continue 130 | MIDI_CIN_SYSEX_END_1BYTE = 5, // SysEx ends with 1 data, or 1 byte system common message 131 | MIDI_CIN_SYSEX_END_2BYTE = 6, // SysEx ends with 2 data 132 | MIDI_CIN_SYSEX_END_3BYTE = 7, // SysEx ends with 3 data 133 | MIDI_CIN_NOTE_ON = 8, 134 | MIDI_CIN_NOTE_OFF = 9, 135 | MIDI_CIN_POLY_KEYPRESS = 10, 136 | MIDI_CIN_CONTROL_CHANGE = 11, 137 | MIDI_CIN_PROGRAM_CHANGE = 12, 138 | MIDI_CIN_CHANNEL_PRESSURE = 13, 139 | MIDI_CIN_PITCH_BEND_CHANGE = 14, 140 | MIDI_CIN_1BYTE_DATA = 15 141 | } midi_code_index_number_t; 142 | 143 | // MIDI 1.0 status byte 144 | enum 145 | { 146 | //------------- System Exclusive -------------// 147 | MIDI_STATUS_SYSEX_START = 0xF0, 148 | MIDI_STATUS_SYSEX_END = 0xF7, 149 | 150 | //------------- System Common -------------// 151 | MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME = 0xF1, 152 | MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER = 0xF2, 153 | MIDI_STATUS_SYSCOM_SONG_SELECT = 0xF3, 154 | // F4, F5 is undefined 155 | MIDI_STATUS_SYSCOM_TUNE_REQUEST = 0xF6, 156 | 157 | //------------- System RealTime -------------// 158 | MIDI_STATUS_SYSREAL_TIMING_CLOCK = 0xF8, 159 | // 0xF9 is undefined 160 | MIDI_STATUS_SYSREAL_START = 0xFA, 161 | MIDI_STATUS_SYSREAL_CONTINUE = 0xFB, 162 | MIDI_STATUS_SYSREAL_STOP = 0xFC, 163 | // 0xFD is undefined 164 | MIDI_STATUS_SYSREAL_ACTIVE_SENSING = 0xFE, 165 | MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF, 166 | }; 167 | 168 | /// MIDI Interface Header Descriptor 169 | typedef struct TU_ATTR_PACKED 170 | { 171 | uint8_t bLength ; ///< Size of this descriptor in bytes. 172 | uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific 173 | uint8_t bDescriptorSubType ; ///< Descriptor SubType 174 | uint16_t bcdMSC ; ///< MidiStreaming SubClass release number in Binary-Coded Decimal 175 | uint16_t wTotalLength ; 176 | } midi_desc_header_t; 177 | 178 | /// MIDI In Jack Descriptor 179 | typedef struct TU_ATTR_PACKED 180 | { 181 | uint8_t bLength ; ///< Size of this descriptor in bytes. 182 | uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific 183 | uint8_t bDescriptorSubType ; ///< Descriptor SubType 184 | uint8_t bJackType ; ///< Embedded or External 185 | uint8_t bJackID ; ///< Unique ID for MIDI IN Jack 186 | uint8_t iJack ; ///< string descriptor 187 | } midi_desc_in_jack_t; 188 | 189 | 190 | /// MIDI Out Jack Descriptor with single pin 191 | typedef struct TU_ATTR_PACKED 192 | { 193 | uint8_t bLength ; ///< Size of this descriptor in bytes. 194 | uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific 195 | uint8_t bDescriptorSubType ; ///< Descriptor SubType 196 | uint8_t bJackType ; ///< Embedded or External 197 | uint8_t bJackID ; ///< Unique ID for MIDI IN Jack 198 | uint8_t bNrInputPins; 199 | 200 | uint8_t baSourceID; 201 | uint8_t baSourcePin; 202 | 203 | uint8_t iJack ; ///< string descriptor 204 | } midi_desc_out_jack_t ; 205 | 206 | /// MIDI Out Jack Descriptor with multiple pins 207 | #define midi_desc_out_jack_n_t(input_num) \ 208 | struct TU_ATTR_PACKED { \ 209 | uint8_t bLength ; \ 210 | uint8_t bDescriptorType ; \ 211 | uint8_t bDescriptorSubType ; \ 212 | uint8_t bJackType ; \ 213 | uint8_t bJackID ; \ 214 | uint8_t bNrInputPins ; \ 215 | struct TU_ATTR_PACKED { \ 216 | uint8_t baSourceID; \ 217 | uint8_t baSourcePin; \ 218 | } pins[input_num]; \ 219 | uint8_t iJack ; \ 220 | } 221 | 222 | /// MIDI Element Descriptor 223 | typedef struct TU_ATTR_PACKED 224 | { 225 | uint8_t bLength ; ///< Size of this descriptor in bytes. 226 | uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific 227 | uint8_t bDescriptorSubType ; ///< Descriptor SubType 228 | uint8_t bElementID; 229 | 230 | uint8_t bNrInputPins; 231 | uint8_t baSourceID; 232 | uint8_t baSourcePin; 233 | 234 | uint8_t bNrOutputPins; 235 | uint8_t bInTerminalLink; 236 | uint8_t bOutTerminalLink; 237 | uint8_t bElCapsSize; 238 | 239 | uint16_t bmElementCaps; 240 | uint8_t iElement; 241 | } midi_desc_element_t; 242 | 243 | /// MIDI Element Descriptor with multiple pins 244 | #define midi_desc_element_n_t(input_num) \ 245 | struct TU_ATTR_PACKED { \ 246 | uint8_t bLength; \ 247 | uint8_t bDescriptorType; \ 248 | uint8_t bDescriptorSubType; \ 249 | uint8_t bElementID; \ 250 | uint8_t bNrInputPins; \ 251 | struct TU_ATTR_PACKED { \ 252 | uint8_t baSourceID; \ 253 | uint8_t baSourcePin; \ 254 | } pins[input_num]; \ 255 | uint8_t bNrOutputPins; \ 256 | uint8_t bInTerminalLink; \ 257 | uint8_t bOutTerminalLink; \ 258 | uint8_t bElCapsSize; \ 259 | uint16_t bmElementCaps; \ 260 | uint8_t iElement; \ 261 | } 262 | 263 | /// MIDI 2 Streaming Data Endpoint Descriptor with one group terminal block 264 | typedef struct TU_ATTR_PACKED 265 | { 266 | uint8_t bLength ; ///< Size of this descriptor in bytes: 4+n 267 | uint8_t bDescriptorType ; ///< Descriptor Type: CS_ENDPOINT 268 | uint8_t bDescriptorSubType ; ///< Descriptor SubType: MIDI20_CS_ENDPOINT_GENERAL 269 | uint8_t bNumGrpTrmBlock ; ///< Number of Group Terminal Blocks: 1 270 | uint8_t bAssoGrpTrmBlkID ; ///< ID of the Group Terminal Block that is associated with this endpoint 271 | } midi2_desc_streaming_data_endpoint_t; 272 | 273 | /// MIDI 2 Streaming Data Endpoint Descriptor with multiple group terminal blocks 274 | #define midi2_desc_streaming_data_endpoint_n_t(group_terminal_block_num) \ 275 | struct TU_ATTR_PACKED { \ 276 | uint8_t bLength; \ 277 | uint8_t bDescriptorType; \ 278 | uint8_t bDescriptorSubType; \ 279 | uint8_t bNumGrpTrmBlock; \ 280 | uint8_t bNrInputPins; \ 281 | uint8_t baAssoGrpTrmBlkID[group_terminal_block_num]; \ 282 | } 283 | 284 | /// MIDI 2 Group Terminal Block Header Descriptor 285 | typedef struct TU_ATTR_PACKED 286 | { 287 | uint8_t bLength ; ///< Size of this descriptor in bytes: 5 288 | uint8_t bDescriptorType ; ///< Descriptor Type: MIDI_CS_INTERFACE_GR_TRM_BLOCK 289 | uint8_t bDescriptorSubType ; ///< Descriptor SubType: MIDI_GR_TRM_BLOCK_HEADER 290 | uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific Group Terminal Block descriptors. Includes the combined length of this header descriptor and all Group Terminal Block descriptors. 291 | } midi2_desc_group_terminal_block_header_t; 292 | 293 | /// MIDI 2 Group Terminal Block Descriptor 294 | typedef struct TU_ATTR_PACKED 295 | { 296 | uint8_t bLength ; ///< Size of this descriptor in bytes: 13 297 | uint8_t bDescriptorType ; ///< Descriptor Type: MIDI_CS_INTERFACE_GR_TRM_BLOCK 298 | uint8_t bDescriptorSubType ; ///< Descriptor SubType: MIDI_GR_TRM_BLOCK 299 | uint8_t bGrpTrmBlkID ; ///< ID of this Group Terminal Block 300 | uint8_t bGrpTrmBlkType ; ///< Group Terminal Block Type 301 | uint8_t nGroupTrm ; ///< The first member Group Terminal in this Block 302 | uint8_t nNumGroupTrm ; ///< Number of member Group Terminals spanned 303 | uint8_t iBlockItem ; ///< ID of STRING descriptor for UI representation of Block item 304 | uint8_t bMIDIProtocol ; ///< Default MIDI protocol 305 | uint16_t wMaxInputBandwidth ; ///< Maximum Input Bandwidth Capability in 4KB/second 306 | uint16_t wMaxOutputBandwidth; ///< Maximum Output Bandwidth Capability in 4KB/second 307 | } midi2_desc_group_terminal_block_t; 308 | 309 | /// MIDI 2 Group Terminal Blocks Descriptor with one group terminal block 310 | typedef struct TU_ATTR_PACKED 311 | { 312 | midi2_desc_group_terminal_block_header_t header; 313 | midi2_desc_group_terminal_block_t block; 314 | } midi2_cs_interface_desc_group_terminal_blocks_t; 315 | 316 | /// MIDI 2 Group Terminal Blocks Descriptor with one group terminal block 317 | #define midi2_cs_interface_desc_group_terminal_blocks_n_t(group_terminal_block_num) \ 318 | struct TU_ATTR_PACKED { \ 319 | midi2_desc_group_terminal_block_header_t header; \ 320 | midi2_desc_group_terminal_block_t aBlock[group_terminal_block_num]; \ 321 | } 322 | 323 | /** @} */ 324 | 325 | #ifdef __cplusplus 326 | } 327 | #endif 328 | 329 | #endif 330 | 331 | /** @} */ 332 | -------------------------------------------------------------------------------- /ump_device.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * Copyright (c) 2022 Michael Loh (AmeNote.com) 6 | * 7 | * NOTE: Code adjustments made to support USB MIDI 2.0 UMP Packet format as 8 | * Alternate Interface 1. See USB Device Class Definition for MIDI Devices, 9 | * Version 2.0 - May 2, 2020. 10 | * 11 | * UMP Driver Version 0.1 - June 28, 2022 12 | * UMP Driver Version 0.2 - Dec. 13, 2022 13 | * - Splitting UMP Driver base from tud_midi 14 | * UMP Driver Version 0.3 - June 10, 2023 15 | * - fixes issue with virtual cable ID and group IDs translation between 16 | * USB MIDI 1.0 and USB MIDI 2.0 17 | * UMP Driver Version 0.4 - Sept. 4, 2023 18 | * - further fixes for multiple virtual cables when translating between 19 | * USB MIDI 1.0 and USB MIDI 2.0. Remove dependance on external libraries. 20 | * UMP Driver Version 0.5 - Sept. 18, 2023 21 | * - bug fixes, USB MIDI 1.0 SYSEX on USB IN and USB OUT. 22 | * UMP Driver Version 1.0 - Sept. 26, 2024 23 | * - handling of USB MIDI 1.0 SYSEX translation 24 | * - Update of driver to latest tinyUSB implementation requirements 25 | * 26 | * The driver is backwards compatible with USB MIDI 1.0 if connected to an 27 | * operating system or other USB Hosting that does not support USB MIDI 2.0. 28 | * The driver does not currently support CIN 0xF, Single Byte as no known 29 | * operating system hosting is expected to send to device as CIN 0xf. If 30 | * CIN of 0xf is required, the implementer is welcome to contribute this 31 | * added processing. 32 | * 33 | * Permission is hereby granted, free of charge, to any person obtaining a copy 34 | * of this software and associated documentation files (the "Software"), to deal 35 | * in the Software without restriction, including without limitation the rights 36 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | * copies of the Software, and to permit persons to whom the Software is 38 | * furnished to do so, subject to the following conditions: 39 | * 40 | * The above copyright notice and this permission notice shall be included in 41 | * all copies or substantial portions of the Software. 42 | * 43 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 49 | * THE SOFTWARE. 50 | 51 | */ 52 | 53 | #include "tusb_option.h" 54 | 55 | #if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_UMP) 56 | 57 | //--------------------------------------------------------------------+ 58 | // INCLUDE 59 | //--------------------------------------------------------------------+ 60 | #include "device/usbd.h" 61 | #include "device/usbd_pvt.h" 62 | 63 | #include "ump_device.h" 64 | 65 | //--------------------------------------------------------------------+ 66 | // APP SPECIFIC DRIVERS 67 | //--------------------------------------------------------------------+ 68 | #define TUSB_NUM_APP_DRIVERS 1 // defines number of app drivers 69 | usbd_class_driver_t const tusb_app_drivers[TUSB_NUM_APP_DRIVERS] = { 70 | { 71 | #if TUSB_VERSION_MAJOR == 0 && TUSB_VERSION_MINOR > 16 72 | "UMP", 73 | #endif 74 | umpd_init, // Driver init function 75 | #if TUSB_VERSION_MAJOR == 0 && TUSB_VERSION_MINOR > 16 76 | umpd_deinit, // Driver deinit function 77 | #endif 78 | umpd_reset, // Driver reset function 79 | umpd_open, // Driver open function 80 | umpd_control_xfer_cb, // Driver control transfer callback function 81 | umpd_xfer_cb, // Driver transfer callback function 82 | NULL // Driver sof function 83 | } 84 | }; 85 | 86 | /** 87 | * @brief Routine to load app specific drivers 88 | * This routine is used for tinyUSB to associate to external application specific 89 | * drivers. This will be used until ump_device is included in the standard set of 90 | * tinyUSB drivers. 91 | * 92 | * @param driver_count Set with number of app specific drivers 93 | * @return usbd_class_driver_t const* return pointer to structure 94 | */ 95 | usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) 96 | { 97 | *driver_count = TUSB_NUM_APP_DRIVERS; 98 | 99 | return tusb_app_drivers; 100 | } 101 | 102 | //--------------------------------------------------------------------+ 103 | // MACRO CONSTANT TYPEDEF 104 | //--------------------------------------------------------------------+ 105 | typedef struct 106 | { 107 | uint8_t buffer[4]; 108 | uint8_t index; 109 | }midid_stream_t; 110 | 111 | typedef struct 112 | { 113 | uint8_t wordCount; 114 | union ump_device 115 | { 116 | uint32_t umpWords[4]; 117 | uint8_t umpBytes[sizeof(uint32_t)*4]; 118 | } umpData; 119 | } UMP_PACKET, *PUMP_PACKET; 120 | 121 | // 122 | // Structure to aid in UMP SYSEX to USB MIDI 1.0 123 | // 124 | #define SYSEX_BS_RB_SIZE 16 125 | typedef struct UMP_TO_MIDI1_SYSEX_t 126 | { 127 | bool inSysex; 128 | uint8_t sysexBS[SYSEX_BS_RB_SIZE]; 129 | uint8_t usbMIDI1Tail; 130 | uint8_t usbMIDI1Head; 131 | } UMP_TO_MIDI1_SYSEX; 132 | 133 | #define MAX_NUM_GROUPS_CABLES 16 134 | 135 | typedef struct 136 | { 137 | uint8_t itf_num; 138 | uint8_t ep_in; 139 | uint8_t ep_out; 140 | 141 | bool midi1IsInSysex[MAX_NUM_GROUPS_CABLES]; 142 | UMP_TO_MIDI1_SYSEX midi1OutSysex[MAX_NUM_GROUPS_CABLES]; 143 | 144 | /*------------- From this point, data is not cleared by bus reset -------------*/ 145 | // FIFO 146 | tu_fifo_t rx_ff; // reference to rx fifo 147 | tu_fifo_t tx_ff; // reference to tx fifo 148 | uint8_t rx_ff_buf[CFG_TUD_UMP_RX_BUFSIZE]; // storage buffer for rx fifo 149 | uint8_t tx_ff_buf[CFG_TUD_UMP_TX_BUFSIZE]; // storage buffer for tx fifo 150 | 151 | #if CFG_FIFO_MUTEX 152 | osal_mutex_def_t rx_ff_mutex; // mutex for rx fifo if needed 153 | osal_mutex_def_t tx_ff_mutex; // mutex for tx fifo if needed 154 | #endif 155 | 156 | // Endpoint Transfer buffer 157 | CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_UMP_EP_BUFSIZE]; // temp endpoint storage buffer 158 | CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_UMP_EP_BUFSIZE]; // temp endpoint storage buffer 159 | 160 | // Selected Interface 161 | uint8_t ump_interface_selected; // for interface seclection - needed for USB MIDI 2.0 (UMP) 162 | 163 | } umpd_interface_t; 164 | 165 | // Default Group Terminal Block Descriptor 166 | static uint8_t default_ump_group_terminal_blk_desc[] = 167 | { 168 | // header 169 | 5, // bLength 170 | MIDI_CS_INTERFACE_GR_TRM_BLOCK, 171 | MIDI_GR_TRM_BLOCK_HEADER, 172 | U16_TO_U8S_LE(sizeof(midi2_cs_interface_desc_group_terminal_blocks_t)), // wTotalLength 173 | 174 | // block 175 | 13, // bLength 176 | MIDI_CS_INTERFACE_GR_TRM_BLOCK, 177 | MIDI_GR_TRM_BLOCK, 178 | 1, // bGrpTrmBlkID 179 | 0x00, // bGrpTrmBlkType: bi-directional 180 | 0x00, // nGroupTrm 181 | 1, // nNumGroupTrm 182 | 0, // iBlockItem: no string 183 | 0x00, // bMIDIProtocol: Unknown (Use MIDI-CI) 184 | 0x00, 0x00, // wMaxInputBandwidth: Unknown or Not Fixed 185 | 0x00, 0x00 // wMaxOutputBandwidth: Unknown or Not Fixed 186 | }; 187 | 188 | #define ITF_MEM_RESET_SIZE offsetof(umpd_interface_t, rx_ff) 189 | 190 | //--------------------------------------------------------------------+ 191 | // INTERNAL OBJECT & FUNCTION DECLARATION 192 | //--------------------------------------------------------------------+ 193 | CFG_TUSB_MEM_SECTION umpd_interface_t _umpd_itf[CFG_TUD_UMP]; 194 | 195 | extern "C" 196 | { 197 | bool tud_USBMIDI1ToUMP (uint32_t usbMidi1Pkt, bool* pbIsInSysex, PUMP_PACKET umpPkt); 198 | 199 | bool tud_ump_n_mounted (uint8_t itf) 200 | { 201 | umpd_interface_t* ump = &_umpd_itf[itf]; 202 | return ump->ep_in && ump->ep_out; 203 | } 204 | 205 | static void _prep_out_transaction (umpd_interface_t* p_ump) 206 | { 207 | uint8_t const rhport = TUD_OPT_RHPORT; 208 | uint16_t available = tu_fifo_remaining(&p_ump->rx_ff); 209 | 210 | // Prepare for incoming data but only allow what we can store in the ring buffer. 211 | // TODO Actually we can still carry out the transfer, keeping count of received bytes 212 | // and slowly move it to the FIFO when read(). 213 | // This pre-check reduces endpoint claiming 214 | TU_VERIFY(available >= sizeof(p_ump->epout_buf), ); 215 | 216 | // claim endpoint 217 | TU_VERIFY(usbd_edpt_claim(rhport, p_ump->ep_out), ); 218 | 219 | // fifo can be changed before endpoint is claimed 220 | available = tu_fifo_remaining(&p_ump->rx_ff); 221 | 222 | if ( available >= sizeof(p_ump->epout_buf) ) { 223 | usbd_edpt_xfer(rhport, p_ump->ep_out, p_ump->epout_buf, sizeof(p_ump->epout_buf)); 224 | }else 225 | { 226 | // Release endpoint since we don't make any transfer 227 | usbd_edpt_release(rhport, p_ump->ep_out); 228 | } 229 | } 230 | 231 | //--------------------------------------------------------------------+ 232 | // READ API 233 | //--------------------------------------------------------------------+ 234 | uint32_t tud_ump_n_available(uint8_t itf) 235 | { 236 | umpd_interface_t* ump = &_umpd_itf[itf]; 237 | 238 | // is amount in fifo / 4 for 32 bit word count 239 | return tu_fifo_count(&ump->rx_ff) / 4; 240 | } 241 | 242 | /** 243 | * @brief return if MIDI UMP is enabled. 244 | * 245 | * @param itf interface number 246 | * @return bool true if enabled 247 | */ 248 | uint8_t tud_alt_setting( uint8_t itf) { 249 | umpd_interface_t* ump = &_umpd_itf[itf]; 250 | return ump->ump_interface_selected; 251 | } 252 | 253 | /** 254 | * @brief Process data read from USB OUT Stream and process into UMP data. 255 | * Read 32bit data words from USB stream, convert if necessary from USB MIDI 1.0 256 | * to UMP or pass UMP packets. 257 | * 258 | * @param itf interface number 259 | * @param pkts Array of 32 bit formatted UMP packet data (BE Formatted) 260 | * @param numAvail Number of 32 bit UMP words that are available in handle 261 | * to populate. Note to accomodate possible SYSEX, needs to be at least 2 262 | * for 64bit size UMP Packet. 263 | * @return uint16_t Number of 32 bit UMP words populated in handle 264 | */ 265 | uint16_t tud_ump_read( uint8_t itf, uint32_t *pkts, uint16_t numAvail ) 266 | { 267 | umpd_interface_t* ump = &_umpd_itf[itf]; 268 | uint16_t numRead = 0; 269 | uint16_t numProcessed = 0; 270 | 271 | static UMP_PACKET umpPacket; 272 | 273 | TU_VERIFY(ump->ep_out); 274 | 275 | // Determine if MIDI 1 276 | if (!ump->ump_interface_selected) 277 | { 278 | // Always look for enough space to process in a SYSEX message 279 | while ((numAvail - numProcessed) >= 2) 280 | { 281 | // Get next word from USB 282 | uint32_t readWord; 283 | if (tu_fifo_read_n(&ump->rx_ff, (void *)&readWord, sizeof(uint32_t)) != sizeof(uint32_t) ) 284 | { 285 | goto END_READ; 286 | } 287 | numProcessed++; 288 | //readWord = RtlUlongByteSwap(readWord); 289 | 290 | if (readWord) 291 | { 292 | uint8_t *pBuffer = (uint8_t *)&readWord; 293 | uint8_t cbl_num = (pBuffer[0] & 0xf0) >> 4; 294 | UMP_PACKET pkt; 295 | if (tud_USBMIDI1ToUMP(readWord, &ump->midi1IsInSysex[cbl_num], &pkt)) 296 | { 297 | for (uint8_t count = 0; count < pkt.wordCount; count++) 298 | { 299 | pkts[numRead++] = pkt.umpData.umpWords[count]; 300 | } 301 | } 302 | } 303 | } 304 | } 305 | else 306 | { 307 | uint8_t umpBuffer[4]; 308 | // Read in as much data as possible 309 | while (numRead < numAvail && 310 | tu_fifo_read_n(&ump->rx_ff, umpBuffer, 4) == 4) 311 | { 312 | pkts[numRead++] = *(uint32_t *)umpBuffer; 313 | } 314 | } 315 | 316 | END_READ: 317 | _prep_out_transaction(ump); 318 | 319 | return numRead; 320 | } 321 | 322 | //--------------------------------------------------------------------+ 323 | // WRITE API 324 | //--------------------------------------------------------------------+ 325 | 326 | uint32_t tud_ump_n_writeable(uint8_t itf) 327 | { 328 | umpd_interface_t* ump = &_umpd_itf[itf]; 329 | 330 | // is amount in fifo / 4 for 32 bit word count 331 | return tu_fifo_remaining(&ump->tx_ff) / 4; 332 | } 333 | 334 | static uint32_t write_flush(umpd_interface_t* ump) 335 | { 336 | // No data to send 337 | if ( !tu_fifo_count(&ump->tx_ff) ) return 0; 338 | 339 | uint8_t const rhport = TUD_OPT_RHPORT; 340 | 341 | // skip if previous transfer not complete 342 | TU_VERIFY( usbd_edpt_claim(rhport, ump->ep_in), 0 ); 343 | 344 | uint16_t count = tu_fifo_read_n(&ump->tx_ff, ump->epin_buf, CFG_TUD_UMP_EP_BUFSIZE); 345 | 346 | if (count) 347 | { 348 | TU_ASSERT( usbd_edpt_xfer(rhport, ump->ep_in, ump->epin_buf, count), 0 ); 349 | return count; 350 | }else 351 | { 352 | // Release endpoint since we don't make any transfer 353 | usbd_edpt_release(rhport, ump->ep_in); 354 | return 0; 355 | } 356 | } 357 | 358 | /** 359 | * @brief Process data write for USB IN Stream to host device. 360 | * Will write up to the number of UMP packets provided to the USB data stream. 361 | * If required, will convert to USB MIDI 1.0 stream format. 362 | * NOTE: This routine will translate to a single USB endpoint data message. Therefore 363 | * for optimization, it is suggested to group 32 bit UMP Words as much as possible. 364 | * Depending on if Full Speed or High Speed, the single transfer will be 64 bytes 365 | * or 512 bytes respectively - meaning 16 or 128 UMP words per transfer. 366 | * 367 | * @param itf interface number 368 | * @param words pointer to 32 bit formatted UMP data arrray 369 | * @param numWords number of 32 bit UMP packets to try to write 370 | * @return uint16_t number of packets written 371 | */ 372 | uint16_t tud_ump_write( uint8_t itf, uint32_t *words, uint16_t numWords ) 373 | { 374 | umpd_interface_t* ump = &_umpd_itf[itf]; 375 | TU_VERIFY(ump->ep_out); 376 | 377 | uint16_t numProcessed = 0; 378 | uint8_t *pBuffer = (uint8_t *)words; 379 | bool bEnterSysex; 380 | bool bEndSysex; 381 | uint8_t numberBytes; 382 | uint8_t sysexStatus; 383 | static UMP_PACKET umpPacket; 384 | static UMP_PACKET umpWritePacket; // used as storage to translate to USB MIDI 1.0 385 | 386 | // As long as there is data to process and room to write into fifo 387 | while (numProcessed < numWords) 388 | { 389 | // Process UMP Packet 390 | // Convert to USB MIDI 1.0? 391 | if ( ump->ump_interface_selected != 1 ) 392 | { 393 | umpPacket.wordCount = 0; 394 | 395 | // Determine size of UMP packet based on message type 396 | //umpPacket.umpData.umpWords[0] = RtlUlongByteSwap(words[numProcessed]); 397 | umpPacket.umpData.umpWords[0] = words[numProcessed]; 398 | 399 | switch (umpPacket.umpData.umpBytes[0] & UMP_MT_MASK) 400 | { 401 | case UMP_MT_UTILITY: 402 | case UMP_MT_SYSTEM: 403 | case UMP_MT_MIDI1_CV: 404 | case UMP_MT_RESERVED_6: 405 | case UMP_MT_RESERVED_7: 406 | umpPacket.wordCount = 1; 407 | break; 408 | 409 | case UMP_MT_DATA_64: 410 | case UMP_MT_MIDI2_CV: 411 | case UMP_MT_RESERVED_8: 412 | case UMP_MT_RESERVED_9: 413 | case UMP_MT_RESERVED_A: 414 | umpPacket.wordCount = 2; 415 | break; 416 | 417 | case UMP_MT_RESERVED_B: 418 | case UMP_MT_RESERVED_C: 419 | umpPacket.wordCount = 3; 420 | break; 421 | 422 | case UMP_MT_DATA_128: 423 | case UMP_MT_FLEX_128: 424 | case UMP_MT_STREAM_128: 425 | case UMP_MT_RESERVED_E: 426 | umpPacket.wordCount = 4; 427 | break; 428 | 429 | default: 430 | // Unhandled or corrupt data, force to move on 431 | numProcessed++; 432 | continue; 433 | } 434 | 435 | // Confirm have enough data for full packet 436 | if ((numWords - numProcessed) < umpPacket.wordCount) 437 | { 438 | // If not, let system populate more 439 | goto exitWrite; 440 | } 441 | // Get rest of words if needed for UMP Packet 442 | for (int count = 1; count < umpPacket.wordCount; count++) 443 | { 444 | umpPacket.umpData.umpWords[count] = words[numProcessed + count]; 445 | } 446 | 447 | // Now that we have full UMP packet, need to convert to USB MIDI 1.0 format 448 | uint8_t cbl_num = umpPacket.umpData.umpBytes[0] & UMP_GROUP_MASK; // if used, cable num is group block num 449 | 450 | uint8_t mtVal = umpPacket.umpData.umpBytes[0] & UMP_MT_MASK; 451 | switch (mtVal) 452 | { 453 | case UMP_MT_SYSTEM: // System Common messages 454 | umpWritePacket.wordCount = 1; // All types are single USB UMP 1.0 message 455 | // Now need to determine number of bytes for CIN 456 | switch (umpPacket.umpData.umpBytes[1]) 457 | { 458 | case UMP_SYSTEM_TUNE_REQ: 459 | case UMP_SYSTEM_TIMING_CLK: 460 | case UMP_SYSTEM_START: 461 | case UMP_SYSTEM_CONTINUE: 462 | case UMP_SYSTEM_STOP: 463 | case UMP_SYSTEM_ACTIVE_SENSE: 464 | case UMP_SYSTEM_RESET: 465 | case UMP_SYSTEM_UNDEFINED_F4: 466 | case UMP_SYSTEM_UNDEFINED_F5: 467 | case UMP_SYSTEM_UNDEFINED_F9: 468 | case UMP_SYSTEM_UNDEFINED_FD: 469 | umpWritePacket.umpData.umpBytes[0] = (cbl_num << 4) | MIDI_CIN_SYSEX_END_1BYTE; 470 | break; 471 | 472 | case UMP_SYSTEM_MTC: 473 | case UMP_SYSTEM_SONG_SELECT: 474 | umpWritePacket.umpData.umpBytes[0] = (cbl_num << 4) | MIDI_CIN_SYSCOM_2BYTE; 475 | break; 476 | 477 | case UMP_SYSTEM_SONG_POS_PTR: 478 | umpWritePacket.umpData.umpBytes[0] = (cbl_num << 4) | MIDI_CIN_SYSCOM_3BYTE; 479 | break; 480 | 481 | default: 482 | umpWritePacket.wordCount = 0; 483 | break; 484 | } 485 | 486 | // Copy over actual data 487 | for (int count = 1; count < 4; count++) 488 | { 489 | umpWritePacket.umpData.umpBytes[count] = umpPacket.umpData.umpBytes[count]; 490 | } 491 | break; 492 | 493 | case UMP_MT_MIDI1_CV: 494 | umpWritePacket.wordCount = 1; 495 | umpWritePacket.umpData.umpBytes[0] = (cbl_num << 4) | ((umpPacket.umpData.umpBytes[1] & 0xf0) >> 4); 496 | for (int count = 1; count < 4; count++) 497 | { 498 | umpWritePacket.umpData.umpBytes[count] = umpPacket.umpData.umpBytes[count]; 499 | } 500 | break; 501 | 502 | case UMP_MT_DATA_64: 503 | bEnterSysex = false; 504 | bEndSysex = false; 505 | 506 | umpWritePacket.wordCount = 0; 507 | 508 | // Determine if sysex will end after this message 509 | switch (umpPacket.umpData.umpBytes[1] & UMP_SYSEX7_STATUS_MASK) 510 | { 511 | case UMP_SYSEX7_COMPLETE: 512 | bEnterSysex = true; 513 | case UMP_SYSEX7_END: 514 | bEndSysex = true; 515 | break; 516 | 517 | case UMP_SYSEX7_START: 518 | bEnterSysex = true; 519 | default: 520 | bEndSysex = false; 521 | break; 522 | } 523 | 524 | if (bEnterSysex) 525 | { 526 | // Determine if believed already in Sysex and if so, reset converter 527 | if (ump->midi1OutSysex[cbl_num].inSysex) 528 | { 529 | ump->midi1OutSysex[cbl_num].inSysex = false; 530 | } 531 | } 532 | 533 | if (bEnterSysex && !ump->midi1OutSysex[cbl_num].inSysex) 534 | { 535 | ump->midi1OutSysex[cbl_num].usbMIDI1Head = 0; 536 | ump->midi1OutSysex[cbl_num].usbMIDI1Tail = 0; 537 | ump->midi1OutSysex[cbl_num].inSysex = true; 538 | } 539 | 540 | uint8_t byteStream[SYSEX_BS_RB_SIZE]; 541 | sysexStatus = (umpPacket.umpData.umpBytes[1] >> 4); 542 | numberBytes = 0; 543 | 544 | if (sysexStatus <= 1 && numberBytes < SYSEX_BS_RB_SIZE) 545 | { 546 | byteStream[numberBytes++] = MIDI_STATUS_SYSEX_START; 547 | } 548 | for (uint8_t count = 0; count < (umpPacket.umpData.umpBytes[1] & 0xf); count++) 549 | { 550 | if (numberBytes < SYSEX_BS_RB_SIZE) 551 | { 552 | byteStream[numberBytes++] = umpPacket.umpData.umpBytes[2 + count]; 553 | } 554 | } 555 | if ((sysexStatus == 0 || sysexStatus == 3) && numberBytes < SYSEX_BS_RB_SIZE) 556 | { 557 | byteStream[numberBytes++] = MIDI_STATUS_SYSEX_END; 558 | } 559 | 560 | // Move into sysex circular buffer queue 561 | for (uint8_t count = 0; count < numberBytes; count++) 562 | { 563 | ump->midi1OutSysex[cbl_num].sysexBS[ump->midi1OutSysex[cbl_num].usbMIDI1Head++] 564 | = byteStream[count]; 565 | ump->midi1OutSysex[cbl_num].usbMIDI1Head %= SYSEX_BS_RB_SIZE; 566 | } 567 | 568 | // How many bytes available in BS 569 | numberBytes = (ump->midi1OutSysex[cbl_num].usbMIDI1Head > ump->midi1OutSysex[cbl_num].usbMIDI1Tail) 570 | ? ump->midi1OutSysex[cbl_num].usbMIDI1Head - ump->midi1OutSysex[cbl_num].usbMIDI1Tail 571 | : (SYSEX_BS_RB_SIZE - ump->midi1OutSysex[cbl_num].usbMIDI1Tail) + ump->midi1OutSysex[cbl_num].usbMIDI1Head; 572 | 573 | while (numberBytes && umpWritePacket.wordCount < 4) 574 | { 575 | umpWritePacket.umpData.umpWords[umpWritePacket.wordCount] = 0; 576 | 577 | if (numberBytes > 2) 578 | { 579 | uint8_t *pumpBytes = (uint8_t*) & umpWritePacket.umpData.umpWords[umpWritePacket.wordCount]; 580 | for (uint8_t count = 0; count < 3; count++) 581 | { 582 | pumpBytes[count + 1] = 583 | ump->midi1OutSysex[cbl_num].sysexBS[ump->midi1OutSysex[cbl_num].usbMIDI1Tail++]; 584 | ump->midi1OutSysex[cbl_num].usbMIDI1Tail %= SYSEX_BS_RB_SIZE; 585 | numberBytes--; 586 | } 587 | // Mark cable number and CIN for start / continue SYSEX in USB MIDI 1.0 format 588 | if (bEndSysex && !numberBytes) 589 | { 590 | pumpBytes[0] = (uint8_t)(cbl_num << 4) | MIDI_CIN_SYSEX_END_3BYTE; 591 | } 592 | else 593 | { 594 | pumpBytes[0] = (uint8_t)(cbl_num << 4) | MIDI_CIN_SYSEX_START; 595 | } 596 | } 597 | else 598 | { 599 | // If less than two and we have a word to populate, check if the end of sysex 600 | if (bEndSysex) 601 | { 602 | // Process bytes 603 | uint8_t* pumpBytes = (uint8_t*)&umpWritePacket.umpData.umpWords[umpWritePacket.wordCount]; 604 | uint8_t count; 605 | for (count = 0; numberBytes; count++) 606 | { 607 | pumpBytes[count + 1] = 608 | ump->midi1OutSysex[cbl_num].sysexBS[ump->midi1OutSysex[cbl_num].usbMIDI1Tail++]; 609 | ump->midi1OutSysex[cbl_num].usbMIDI1Tail %= SYSEX_BS_RB_SIZE; 610 | numberBytes--; 611 | } 612 | // Mark cable number and CIN for start / continue SYSEX in USB MIDI 1.0 format 613 | switch (count) 614 | { 615 | case 1: 616 | pumpBytes[0] = (uint8_t)(cbl_num << 4) | MIDI_CIN_SYSEX_END_1BYTE; 617 | break; 618 | 619 | case 2: 620 | default: 621 | pumpBytes[0] = (uint8_t)(cbl_num << 4) | MIDI_CIN_SYSEX_END_2BYTE; 622 | break; 623 | } 624 | } 625 | else 626 | { 627 | break; 628 | } 629 | } 630 | umpWritePacket.wordCount++; 631 | } 632 | break; 633 | 634 | default: 635 | // Not handled so ignore 636 | numProcessed += umpPacket.wordCount; // ignore this UMP packet as corrupted 637 | umpWritePacket.wordCount = 0; 638 | } 639 | 640 | if (umpWritePacket.wordCount) 641 | { 642 | numProcessed += umpPacket.wordCount; 643 | 644 | tu_fifo_write_n(&ump->tx_ff, (void*)&umpWritePacket.umpData.umpBytes[0], 645 | umpWritePacket.wordCount*4); 646 | } 647 | } 648 | else 649 | { 650 | // Should already be UMP formatted, so just pass along 651 | uint16_t numAvailable = tu_fifo_remaining(&ump->tx_ff) / 4; 652 | numProcessed = (numAvailable < numWords) ? numAvailable : numWords; 653 | tu_fifo_write_n(&ump->tx_ff, (void*)words, numProcessed*4); 654 | } 655 | } 656 | 657 | exitWrite : 658 | 659 | // Make sure fifo is pushed to endpoint 660 | write_flush(ump); 661 | 662 | // Let calling routine know how many words processed 663 | return numProcessed; 664 | } 665 | 666 | //--------------------------------------------------------------------+ 667 | // USBD Driver API 668 | //--------------------------------------------------------------------+ 669 | void umpd_init(void) 670 | { 671 | tu_memclr((void *)_umpd_itf, sizeof(_umpd_itf)); 672 | 673 | for(uint8_t i=0; irx_ff, ump->rx_ff_buf, CFG_TUD_UMP_RX_BUFSIZE, 1, false); 679 | tu_fifo_config(&ump->tx_ff, ump->tx_ff_buf, CFG_TUD_UMP_TX_BUFSIZE, 1, false); 680 | 681 | // Default select the first interface 682 | ump->ump_interface_selected = 0; 683 | 684 | #if CFG_FIFO_MUTEX 685 | tu_fifo_config_mutex(&ump->rx_ff, NULL, osal_mutex_create(&ump->rx_ff_mutex)); 686 | tu_fifo_config_mutex(&ump->tx_ff, osal_mutex_create(&ump->tx_ff_mutex), NULL); 687 | #endif 688 | } 689 | } 690 | 691 | bool umpd_deinit(void) 692 | { 693 | // Need to add to handle cleanup of multiple instances 694 | 695 | return true; 696 | } 697 | 698 | void umpd_reset(uint8_t rhport) 699 | { 700 | (void) rhport; 701 | 702 | for(uint8_t i=0; irx_ff); 707 | tu_fifo_clear(&ump->tx_ff); 708 | 709 | // Reset any current processing condition 710 | for(uint8_t grp=0; grpmidi1IsInSysex[grp] = false; 713 | ump->midi1OutSysex[grp].inSysex = 0; 714 | ump->midi1OutSysex[grp].usbMIDI1Head = 0; 715 | ump->midi1OutSysex[grp].usbMIDI1Tail = 0; 716 | } 717 | 718 | ump->ump_interface_selected = 0; 719 | } 720 | } 721 | 722 | uint16_t umpd_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) 723 | { 724 | // 1st Interface is Audio Control v1 725 | TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && 726 | AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && 727 | AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol, 0); 728 | 729 | uint16_t drv_len = tu_desc_len(desc_itf); 730 | uint8_t const * p_desc = tu_desc_next(desc_itf); 731 | 732 | // Skip Class Specific descriptors 733 | while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) 734 | { 735 | drv_len += tu_desc_len(p_desc); 736 | p_desc = tu_desc_next(p_desc); 737 | } 738 | 739 | // 2nd Interface is MIDI Streaming 740 | TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); 741 | tusb_desc_interface_t const * desc_ump = (tusb_desc_interface_t const *) p_desc; 742 | 743 | TU_VERIFY(TUSB_CLASS_AUDIO == desc_ump->bInterfaceClass && 744 | AUDIO_SUBCLASS_MIDI_STREAMING == desc_ump->bInterfaceSubClass && 745 | AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_ump->bInterfaceProtocol, 0); 746 | 747 | // Find available interface 748 | umpd_interface_t * p_ump = NULL; 749 | for(uint8_t i=0; iitf_num = desc_ump->bInterfaceNumber; 760 | (void) p_ump->itf_num; 761 | 762 | // next descriptor 763 | drv_len += tu_desc_len(p_desc); 764 | p_desc = tu_desc_next(p_desc); 765 | 766 | // Find and open endpoint descriptors 767 | uint8_t found_endpoints = 0; 768 | while ( (found_endpoints < desc_ump->bNumEndpoints) && (drv_len <= max_len) ) 769 | { 770 | if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) 771 | { 772 | TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); 773 | uint8_t ep_addr = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; 774 | 775 | if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) 776 | { 777 | p_ump->ep_in = ep_addr; 778 | } else { 779 | p_ump->ep_out = ep_addr; 780 | } 781 | 782 | // Class Specific MIDI Stream endpoint descriptor 783 | drv_len += tu_desc_len(p_desc); 784 | p_desc = tu_desc_next(p_desc); 785 | 786 | found_endpoints += 1; 787 | } 788 | 789 | drv_len += tu_desc_len(p_desc); 790 | p_desc = tu_desc_next(p_desc); 791 | } 792 | 793 | // Finish off any further class specific definitions for interface 794 | while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) 795 | { 796 | drv_len += tu_desc_len(p_desc); 797 | p_desc = tu_desc_next(p_desc); 798 | } 799 | 800 | // See if there is an alternate interface for UMP USB MIDI 2.0 801 | if ( TUSB_DESC_INTERFACE == tu_desc_type(p_desc) ) drv_len = max_len; 802 | 803 | // Prepare for incoming data 804 | _prep_out_transaction(p_ump); 805 | 806 | return drv_len; 807 | } 808 | 809 | // Invoked when a control transfer occurred on an interface of this class 810 | // Driver response accordingly to the request and the transfer stage (setup/data/ack) 811 | // return false to stall control endpoint (e.g unsupported request) 812 | bool umpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) 813 | { 814 | // nothing to with DATA & ACK stage 815 | if (stage != CONTROL_STAGE_SETUP) return true; 816 | 817 | umpd_interface_t* ump = &_umpd_itf[rhport]; 818 | 819 | switch ( request->bRequest ) 820 | { 821 | case TUSB_REQ_SET_INTERFACE : 822 | // Set the interface type for driver operaiton 823 | ump->ump_interface_selected = tu_u16_low(request->wValue); 824 | 825 | // As we are using still bulk transfer, no reason to close and open endpoints, however we should clear 826 | // fifos to start from scratch 827 | tu_fifo_clear(&ump->rx_ff); 828 | tu_fifo_clear(&ump->tx_ff); 829 | 830 | // invoke set interface callback if available 831 | if (tud_ump_set_itf_cb) tud_ump_set_itf_cb(tu_u16_low(request->wIndex), ump->ump_interface_selected); 832 | 833 | tud_control_status(rhport, request); // send a status zero length packet 834 | 835 | return true; 836 | 837 | case TUSB_REQ_GET_DESCRIPTOR : 838 | if ( request->wValue == 0x2601 ) //0x26 - CS_GR_TRM_BLOCK 0x01 - alternate interface setting 839 | { 840 | // invoke midi class specific get request callback if available 841 | if (tud_ump_get_req_itf_cb && tud_ump_get_req_itf_cb(rhport, request)) return true; 842 | 843 | // return default group block descriptor if not handled by client code 844 | uint16_t length = request->wLength; 845 | if ( length > sizeof( default_ump_group_terminal_blk_desc ) ) 846 | { 847 | length = sizeof( default_ump_group_terminal_blk_desc ); 848 | } 849 | tud_control_xfer(rhport, request, (void *)default_ump_group_terminal_blk_desc, length ); 850 | return true; 851 | } 852 | else 853 | return false; 854 | 855 | default : 856 | return false; 857 | } 858 | } 859 | 860 | bool umpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) 861 | { 862 | (void) result; 863 | (void) rhport; 864 | 865 | uint8_t itf; 866 | umpd_interface_t* p_ump; 867 | 868 | // Identify which interface to use 869 | for (itf = 0; itf < CFG_TUD_UMP; itf++) 870 | { 871 | p_ump = &_umpd_itf[itf]; 872 | if ( ( ep_addr == p_ump->ep_out ) || ( ep_addr == p_ump->ep_in ) ) break; 873 | } 874 | TU_ASSERT(itf < CFG_TUD_UMP); 875 | 876 | // receive new data 877 | if ( ep_addr == p_ump->ep_out ) 878 | { 879 | tu_fifo_write_n(&p_ump->rx_ff, p_ump->epout_buf, xferred_bytes); 880 | 881 | // invoke receive callback if available 882 | if (tud_ump_rx_cb) tud_ump_rx_cb(itf); 883 | 884 | // prepare for next 885 | // TODO for now ep_out is not used by public API therefore there is no race condition, 886 | // and does not need to claim like ep_in 887 | _prep_out_transaction(p_ump); 888 | } 889 | else if ( ep_addr == p_ump->ep_in ) 890 | { 891 | if (0 == write_flush(p_ump)) 892 | { 893 | // If there is no data left, a ZLP should be sent if 894 | // xferred_bytes is multiple of EP size and not zero 895 | if ( !tu_fifo_count(&p_ump->tx_ff) && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_UMP_EP_BUFSIZE)) ) 896 | { 897 | if ( usbd_edpt_claim(rhport, p_ump->ep_in) ) 898 | { 899 | usbd_edpt_xfer(rhport, p_ump->ep_in, NULL, 0); 900 | } 901 | } 902 | } 903 | } 904 | 905 | return true; 906 | } 907 | 908 | bool 909 | tud_USBMIDI1ToUMP( 910 | uint32_t usbMidi1Pkt, 911 | bool* pbIsInSysex, 912 | PUMP_PACKET umpPkt 913 | ) 914 | /*++ 915 | Routine Description: 916 | 917 | Helper routine to handle conversion of USB MIDI 1.0 32 bit word packet 918 | to UMP formatted packet. The routine will only populate UMP message 919 | types 1: System, 2: MIDI 1.0 Channel Voice, and 3: 64 bit data (SYSEX) 920 | messages. It is rsponsibility of other routines to convert between 921 | MIDI 2.0 Channel voice if needed. 922 | 923 | NOTE: This routine was refined from the USB MIDI 2.0 Host Driver developed as open 924 | source to be included in Windows by the Association of Musical Electronics Industry. 925 | 926 | Copyright 2023 Association of Musical Electronics Industry 927 | Copyright 2023 Microsoft 928 | Driver source code developed by AmeNote. Some components Copyright 2023 AmeNote Inc. 929 | 930 | Permission is hereby granted, free of charge, to any person obtaining a copy 931 | of this software and associated documentation files (the "Software"), to deal 932 | in the Software without restriction, including without limitation the rights 933 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 934 | copies of the Software, and to permit persons to whom the Software is 935 | furnished to do so, subject to the following conditions: 936 | 937 | The above copyright notice and this permission notice shall be included in all 938 | copies or substantial portions of the Software. 939 | 940 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 941 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 942 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 943 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 944 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 945 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 946 | SOFTWARE. 947 | 948 | Arguments: 949 | 950 | usbMidi1Pkt: The USB MIDI 1.0 packet presented as a 32 bit word 951 | pbIsInSysex: Reference to boolean variable where to store if processing 952 | a SYSEX message or not. Note that calling funciton is 953 | responsible to maintain this state for each USB MIDI 1.0 954 | data stream to process. The intial value should be false. 955 | umpPkt: Reference to data location to create UMP packet into. Note 956 | that for optimizations, the data beyond wordCount is not 957 | cleared, therefore calling function should not process beyond 958 | wordCount or zero any additional dataspace. 959 | 960 | Return Value: 961 | 962 | bool: Indicates true of umpPkt is ready, false otherwise. 963 | 964 | --*/ 965 | { 966 | // Checked passed parameters 967 | if (!usbMidi1Pkt || !pbIsInSysex || !umpPkt) 968 | { 969 | return false; 970 | } 971 | 972 | uint8_t* pBuffer = (uint8_t*)&usbMidi1Pkt; 973 | umpPkt->wordCount = 0; 974 | 975 | // Determine packet cable number from group 976 | uint8_t cbl_num = (pBuffer[0] & 0xf0) >> 4; 977 | 978 | // USB MIDI 1.0 uses a CIN as an identifier for packet, grab CIN. 979 | uint8_t code_index = pBuffer[0] & 0x0f; 980 | 981 | // Handle special case of single byte data 982 | if (code_index == MIDI_CIN_1BYTE_DATA && (pBuffer[1] & 0x80)) 983 | { 984 | switch (pBuffer[1]) 985 | { 986 | case UMP_SYSTEM_TUNE_REQ: 987 | case UMP_SYSTEM_TIMING_CLK: 988 | case UMP_SYSTEM_START: 989 | case UMP_SYSTEM_CONTINUE: 990 | case UMP_SYSTEM_STOP: 991 | case UMP_SYSTEM_ACTIVE_SENSE: 992 | case UMP_SYSTEM_RESET: 993 | case UMP_SYSTEM_UNDEFINED_F4: 994 | case UMP_SYSTEM_UNDEFINED_F5: 995 | case UMP_SYSTEM_UNDEFINED_F9: 996 | case UMP_SYSTEM_UNDEFINED_FD: 997 | code_index = MIDI_CIN_SYSEX_END_1BYTE; 998 | break; 999 | 1000 | default: 1001 | break; 1002 | } 1003 | } 1004 | 1005 | uint8_t firstByte = 1; 1006 | uint8_t lastByte = 4; 1007 | uint8_t copyPos; 1008 | 1009 | switch (code_index) 1010 | { 1011 | case MIDI_CIN_SYSEX_START: // or continue 1012 | 1013 | if (!*pbIsInSysex) 1014 | { 1015 | // SYSEX Start means first byte should be SYSEX start 1016 | if (pBuffer[1] != MIDI_STATUS_SYSEX_START) return false; 1017 | firstByte = 2; 1018 | lastByte = 4; 1019 | 1020 | // As this is start of SYSEX, need to set status to indicate so and copy 2 bytes of data 1021 | // as first byte of MIDI_STATUS_SYSEX_START 1022 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_START | 2; 1023 | 1024 | // Set that in SYSEX 1025 | *pbIsInSysex = true; 1026 | } 1027 | else 1028 | { 1029 | firstByte = 1; 1030 | lastByte = 4; 1031 | 1032 | // As this is in SYSEX, then need to indicate continue 1033 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_CONTINUE | 3; 1034 | } 1035 | 1036 | // Capture Cable number 1037 | umpPkt->umpData.umpBytes[0] = UMP_MT_DATA_64 | cbl_num; // Message Type and group 1038 | 1039 | umpPkt->wordCount = 2; 1040 | // Transfer in bytes 1041 | copyPos = firstByte; 1042 | for (uint8_t count = 2; count < 8; count++) 1043 | { 1044 | umpPkt->umpData.umpBytes[count] = (copyPos < lastByte) 1045 | ? pBuffer[copyPos++] : 0x00; 1046 | } 1047 | break; 1048 | 1049 | case MIDI_CIN_SYSEX_END_1BYTE: // or single byte System Common 1050 | // Determine if a system common 1051 | if ( (pBuffer[1] & 0x80) // most significant bit set and not sysex ending 1052 | && (pBuffer[1] != MIDI_STATUS_SYSEX_END)) 1053 | { 1054 | umpPkt->umpData.umpBytes[0] = UMP_MT_SYSTEM | cbl_num; 1055 | umpPkt->umpData.umpBytes[1] = pBuffer[1]; 1056 | firstByte = 1; 1057 | lastByte = 1; 1058 | goto COMPLETE_1BYTE; 1059 | } 1060 | 1061 | umpPkt->umpData.umpBytes[0] = UMP_MT_DATA_64 | cbl_num; 1062 | 1063 | // Determine if complete based on if currently in SYSEX 1064 | if (*pbIsInSysex) 1065 | { 1066 | if (pBuffer[1] != MIDI_STATUS_SYSEX_END) return false; 1067 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_END | 0; 1068 | *pbIsInSysex = false; // we are done with SYSEX 1069 | firstByte = 1; 1070 | lastByte = 1; 1071 | } 1072 | else 1073 | { 1074 | // should not get here 1075 | return false; 1076 | } 1077 | 1078 | COMPLETE_1BYTE: 1079 | umpPkt->wordCount = 2; 1080 | // Transfer in bytes 1081 | copyPos = firstByte; 1082 | for (uint8_t count = 2; count < 8; count++) 1083 | { 1084 | umpPkt->umpData.umpBytes[count] = (copyPos < lastByte) 1085 | ? pBuffer[copyPos++] : 0x00; 1086 | } 1087 | break; 1088 | 1089 | case MIDI_CIN_SYSEX_END_2BYTE: 1090 | umpPkt->umpData.umpBytes[0] = UMP_MT_DATA_64 | cbl_num; 1091 | 1092 | // Determine if complete based on if currently in SYSEX 1093 | if (*pbIsInSysex) 1094 | { 1095 | if (pBuffer[2] != MIDI_STATUS_SYSEX_END) return false; 1096 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_END | 1; 1097 | *pbIsInSysex = false; // we are done with SYSEX 1098 | firstByte = 1; 1099 | lastByte = 2; 1100 | } 1101 | else 1102 | { 1103 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_COMPLETE | 0; 1104 | *pbIsInSysex = false; // we are done with SYSEX 1105 | firstByte = 1; 1106 | lastByte = 1; 1107 | } 1108 | 1109 | umpPkt->wordCount = 2; 1110 | // Transfer in bytes 1111 | copyPos = firstByte; 1112 | for (uint8_t count = 2; count < 8; count++) 1113 | { 1114 | umpPkt->umpData.umpBytes[count] = (copyPos < lastByte) 1115 | ? pBuffer[copyPos++] : 0x00; 1116 | } 1117 | break; 1118 | 1119 | case MIDI_CIN_SYSEX_END_3BYTE: 1120 | umpPkt->umpData.umpBytes[0] = UMP_MT_DATA_64 | cbl_num; 1121 | 1122 | // Determine if complete based on if currently in SYSEX 1123 | if (*pbIsInSysex) 1124 | { 1125 | if (pBuffer[3] != MIDI_STATUS_SYSEX_END) return false; 1126 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_END | 2; 1127 | *pbIsInSysex = false; // we are done with SYSEX 1128 | firstByte = 1; 1129 | lastByte = 3; 1130 | } 1131 | else 1132 | { 1133 | if (pBuffer[1] != MIDI_STATUS_SYSEX_START || pBuffer[3] != MIDI_STATUS_SYSEX_END) return false; 1134 | umpPkt->umpData.umpBytes[1] = UMP_SYSEX7_COMPLETE | 1; 1135 | *pbIsInSysex = false; // we are done with SYSEX 1136 | firstByte = 2; 1137 | lastByte = 3; 1138 | } 1139 | 1140 | umpPkt->wordCount = 2; 1141 | // Transfer in bytes 1142 | copyPos = firstByte; 1143 | for (uint8_t count = 2; count < 8; count++) 1144 | { 1145 | umpPkt->umpData.umpBytes[count] = (copyPos < lastByte) 1146 | ? pBuffer[copyPos++] : 0x00; 1147 | } 1148 | break; 1149 | 1150 | // MIDI1 Channel Voice Messages 1151 | case MIDI_CIN_NOTE_ON: 1152 | case MIDI_CIN_NOTE_OFF: 1153 | case MIDI_CIN_POLY_KEYPRESS: 1154 | case MIDI_CIN_CONTROL_CHANGE: 1155 | case MIDI_CIN_PROGRAM_CHANGE: 1156 | case MIDI_CIN_CHANNEL_PRESSURE: 1157 | case MIDI_CIN_PITCH_BEND_CHANGE: 1158 | umpPkt->umpData.umpBytes[0] = UMP_MT_MIDI1_CV | cbl_num; // message type 2 1159 | *pbIsInSysex = false; // ensure we end any current sysex packets, other layers need to handle error 1160 | 1161 | // Copy in rest of data 1162 | for (int count = 1; count < 4; count++) 1163 | { 1164 | umpPkt->umpData.umpBytes[count] = pBuffer[count]; 1165 | } 1166 | 1167 | umpPkt->wordCount = 1; 1168 | break; 1169 | 1170 | case MIDI_CIN_SYSCOM_2BYTE: 1171 | case MIDI_CIN_SYSCOM_3BYTE: 1172 | umpPkt->umpData.umpBytes[0] = UMP_MT_SYSTEM | cbl_num; 1173 | for (int count = 1; count < 4; count++) 1174 | { 1175 | umpPkt->umpData.umpBytes[count] = pBuffer[count]; 1176 | } 1177 | umpPkt->wordCount = 1; 1178 | break; 1179 | 1180 | case MIDI_CIN_MISC: 1181 | case MIDI_CIN_CABLE_EVENT: 1182 | // These are reserved for future use and will not be translated, drop data with no processing 1183 | default: 1184 | // Not valid USB MIDI 1.0 transfer or NULL, skip 1185 | return false; 1186 | } 1187 | 1188 | return true; 1189 | } 1190 | 1191 | } // extern "C" 1192 | #endif 1193 | -------------------------------------------------------------------------------- /ump_device.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * Copyright (c) 2022 Michael Loh (AmeNote.com) 6 | * Copyright (c) 2022 Franz Detro (native-instruments.de) 7 | * 8 | * NOTE: Code adjustments made to support USB MIDI 2.0 UMP Packet format as 9 | * Alternate Interface 1. See USB Device Class Definition for MIDI Devices, 10 | * Version 2.0 - May 2, 2020. 11 | * 12 | * UMP Driver Version 0.2 - July 13, 2022 13 | * UMP Driver Version 0.2 - Dec. 13, 2022 14 | * - Splitting UMP Driver base from tud_midi 15 | * 16 | * Permission is hereby granted, free of charge, to any person obtaining a copy 17 | * of this software and associated documentation files (the "Software"), to deal 18 | * in the Software without restriction, including without limitation the rights 19 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | * copies of the Software, and to permit persons to whom the Software is 21 | * furnished to do so, subject to the following conditions: 22 | * 23 | * The above copyright notice and this permission notice shall be included in 24 | * all copies or substantial portions of the Software. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | */ 34 | 35 | #ifndef _TUSB_UMP_DEVICE_H_ 36 | #define _TUSB_UMP_DEVICE_H_ 37 | 38 | #include "class/audio/audio.h" 39 | #include "ump.h" 40 | 41 | //--------------------------------------------------------------------+ 42 | // Class Driver Configuration 43 | //--------------------------------------------------------------------+ 44 | 45 | #ifndef CFG_TUD_UMP_EP_BUFSIZE 46 | #define CFG_TUD_UMP_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 47 | #endif 48 | 49 | #ifdef __cplusplus 50 | extern "C" { 51 | #endif 52 | 53 | /** \addtogroup UMP 54 | * @{ 55 | * \defgroup UMP Device 56 | * @{ */ 57 | 58 | //--------------------------------------------------------------------+ 59 | // Application API (Multiple Interfaces) 60 | // CFG_TUD_UMP > 1 61 | //--------------------------------------------------------------------+ 62 | 63 | // Check if UMP interface is mounted 64 | bool tud_ump_n_mounted (uint8_t itf); 65 | 66 | // Get the number of words (32 bits) available for reading 67 | uint32_t tud_ump_n_available (uint8_t itf ); 68 | 69 | // Get the number of words (32 bits) available for writing 70 | uint32_t tud_ump_n_writeable (uint8_t itf ); 71 | 72 | // Write UMP words 73 | uint16_t tud_ump_write ( uint8_t itf, uint32_t *words, uint16_t numWords ); 74 | 75 | // Read UMP words 76 | uint16_t tud_ump_read ( uint8_t itf, uint32_t *words, uint16_t numAvail ); 77 | 78 | //Get Alternate Setting 79 | uint8_t tud_alt_setting( uint8_t itf); 80 | 81 | //--------------------------------------------------------------------+ 82 | // Application Callback API (weak is optional) 83 | //--------------------------------------------------------------------+ 84 | TU_ATTR_WEAK void tud_ump_rx_cb(uint8_t itf); 85 | 86 | // Invoked when midi set interface request received 87 | TU_ATTR_WEAK void tud_ump_set_itf_cb(uint8_t itf, uint8_t alt); 88 | 89 | // Invoked when midi class specific get request received for an interface 90 | TU_ATTR_WEAK bool tud_ump_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); 91 | 92 | //--------------------------------------------------------------------+ 93 | // Internal Class Driver API 94 | //--------------------------------------------------------------------+ 95 | void umpd_init (void); 96 | bool umpd_deinit (void); 97 | void umpd_reset (uint8_t rhport); 98 | uint16_t umpd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); 99 | bool umpd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); 100 | bool umpd_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); 101 | #ifdef __cplusplus 102 | } 103 | #endif 104 | 105 | #endif /* _TUSB_UMP_DEVICE_H_ */ 106 | 107 | /** @} */ 108 | /** @} */ 109 | --------------------------------------------------------------------------------