├── 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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------