Skip to content

Commit b34802e

Browse files
committed
usb: device_next: add new MIDI 2.0 device class
This adds a new USB device class (based on usb/device_next) that implements revision 2.0 of the MIDIStreaming interface, a sub-class of the USB audio device class. In practice, the MIDI interface is much more simple and has little in common with Audio, so it makes sense to have it as a separate class driver. MIDI inputs and outputs are configured through the device tree, under a node `compatible = "zephyr,usb-midi"`. As per the USB-MIDI2.0 spec, a single usb-midi interface can convey up to 16 Universal MIDI groups, comprising 16 channels each. Data is carried from/to the host via so-called Group Terminals, that are organized in Group Terminal Blocks. They are represented as children of the usb-midi interface in the device tree. From the Zephyr application programmer perspective, MIDI data is exchanged with the host through the device associated with the `zephyr,usb-midi` interface, using the following API: * Send a Universal MIDI Packet to the host: `usb_midi_send(device, pkt)` * Universal MIDI Packets from the host are delivered to the function passed in `usb_midi_set_callback(device, void (device, pkt){...})` Compliant USB-MIDI 2.0 devices are required to expose a USB-MIDI1.0 interface as alt setting 0, and the 2.0 interface on alt setting 1. To avoid the extra complexity of generating backward compatible USB descriptors and translating Universal MIDI Packets from/to the old USB-MIDI1.0 format, this driver generates an empty MIDI1.0 interface (without any input/output); and therefore will only be able to exchange MIDI data when the host has explicitely enabled MIDI2.0 (alt setting 1). This implementation is based on the following documents, which are referred to in the inline comments: * `midi20`: [Universal Serial Bus Device Class Definition for MIDI Devices Release 2.0](https://www.usb.org/sites/default/files/USB%20MIDI%20v2_0.pdf) * `ump112`: [Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol With MIDI 1.0 Protocol in UMP Format _Document Version 1.1.2_](https://midi.org/universal-midi-packet-ump-and-midi-2-0-protocol-specification) Signed-off-by: Titouan Christophe <[email protected]>
1 parent b1f42f2 commit b34802e

File tree

9 files changed

+1050
-0
lines changed

9 files changed

+1050
-0
lines changed

doc/connectivity/usb/device_next/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ New USB device support APIs
1111
usbd_hid_device.rst
1212
uac2_device.rst
1313
usbd_msc_device.rst
14+
usb_midi.rst
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. _usb_midi:
2+
3+
MIDI 2.0 Class device API
4+
#########################
5+
6+
USB MIDI 2.0 device specific API defined in :zephyr_file:`include/zephyr/usb/class/usb_midi.h`.
7+
8+
API Reference
9+
*************
10+
11+
.. doxygengroup:: usb_midi
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (c) 2024 Titouan Christophe
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: USB MIDI Class
5+
6+
compatible: "zephyr,usb-midi"
7+
8+
properties:
9+
"#address-cells":
10+
type: int
11+
const: 1
12+
13+
"#size-cells":
14+
type: int
15+
const: 1
16+
17+
child-binding:
18+
description: |
19+
USB MIDI Group terminal block.
20+
This represent a set of contiguous Universal MIDI groups through which the
21+
device exchange Universal MIDI Packets with the host.
22+
23+
properties:
24+
reg:
25+
type: array
26+
required: true
27+
description: |
28+
First MIDI Group number (address) and number of Group Terminals (size)
29+
in this USB-MIDI Group Terminal Block.
30+
The MIDI Groups 1 to 16 corresponds to address 0x0 to 0xf. There are at
31+
most 16 addressable groups (of 16 channels each) per USB-MIDI interface
32+
33+
protocol:
34+
type: string
35+
enum:
36+
- "use-midi-ci"
37+
- "midi1-up-to-64b"
38+
- "midi1-up-to-128b"
39+
- "midi2"
40+
description: |
41+
Default MIDI protocol of the Group Terminals in this Block
42+
43+
terminal-type:
44+
type: string
45+
default: "bidirectional"
46+
enum:
47+
- "bidirectional"
48+
- "input-only"
49+
- "output-only"
50+
description: |
51+
Type (data direction) of Group Terminals in this Block.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright (c) 2024 Titouan Christophe
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_USB_CLASS_USB_MIDI_H_
8+
#define ZEPHYR_INCLUDE_USB_CLASS_USB_MIDI_H_
9+
10+
/**
11+
* @brief USB-MIDI 2.0 class device API
12+
* @defgroup usb_midi USB MIDI 2.0 Class device API
13+
* @ingroup usb
14+
* @since 4.1
15+
* @version 0.1.0
16+
* @see ump112: "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol"
17+
* Document version 1.1.2
18+
* @{
19+
*/
20+
21+
#include <zephyr/device.h>
22+
23+
/**
24+
* @defgroup usb_midi_mt Universal MIDI Packet message types
25+
* @ingroup usb_midi
26+
* @see ump112: 2.1.4 Message Type (MT) Allocation
27+
* @{
28+
*/
29+
30+
#define MT_UTILITY 0x00
31+
#define MT_SYS_RT_COMMON 0x01
32+
#define MT_MIDI1_CHANNEL_VOICE 0x02
33+
#define MT_DATA 0x03
34+
#define MT_MIDI2_CHANNEL_VOICE 0x04
35+
#define MT_FLEX_DATA 0x0d
36+
#define MT_UMP_STREAM 0x0f
37+
38+
/**
39+
* @}
40+
*
41+
* @brief Size of a Universal MIDI Packet, in 32bit words
42+
* @param[in] mt The packet Message Type
43+
* @see ump112: 2.1.4 Message Type (MT) Allocation
44+
*/
45+
static inline size_t ump_words(uint8_t mt)
46+
{
47+
return ((uint8_t[16]) {1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4})[mt & 0x0f];
48+
}
49+
50+
51+
/**
52+
* @brief MIDI1 channel voice message in Universal MIDI Packet format
53+
* @see ump112: 7.3 MIDI 1.0 Channel Voice Messages
54+
*/
55+
struct ump_midi1 {
56+
#ifdef CONFIG_LITTLE_ENDIAN
57+
/** @brief The 2nd MIDI1 parameter */
58+
uint8_t p2;
59+
/** @brief The 1st MIDI1 parameter */
60+
uint8_t p1;
61+
/** @brief The MIDI1 channel number */
62+
unsigned channel: 4;
63+
/** @brief The MIDI1 status nibble (command) */
64+
unsigned status: 4;
65+
/** @brief The UMP group to which this message belongs */
66+
unsigned group: 4;
67+
/** @brief The UMP message type (MT_MIDI1_CHANNEL_VOICE) */
68+
unsigned mt: 4;
69+
#else
70+
/** @brief The UMP message type (MT_MIDI1_CHANNEL_VOICE) */
71+
unsigned mt: 4;
72+
/** @brief The UMP group to which this message belongs */
73+
unsigned group: 4;
74+
/** @brief The MIDI1 status nibble (command) */
75+
unsigned status: 4;
76+
/** @brief The MIDI1 channel number */
77+
unsigned channel: 4;
78+
/** @brief The 1st MIDI1 parameter */
79+
uint8_t p1;
80+
/** @brief The 2nd MIDI1 parameter */
81+
uint8_t p2;
82+
#endif
83+
} __packed;
84+
85+
/**
86+
* @brief The Universal MIDI Packet
87+
* @see ump112: 2. Universal MIDI Packet (UMP) Format
88+
*/
89+
union ump {
90+
uint32_t words[4];
91+
struct {
92+
#ifdef CONFIG_LITTLE_ENDIAN
93+
unsigned _reserved: 24;
94+
/** @brief The UMP group to which this message belongs */
95+
unsigned group: 4;
96+
/** @brief The UMP message type */
97+
unsigned mt: 4;
98+
#else
99+
/** @brief The UMP message type */
100+
unsigned mt: 4;
101+
/** @brief The UMP group to which this message belongs */
102+
unsigned group: 4;
103+
#endif
104+
} __packed;
105+
struct ump_midi1 midi1;
106+
} __packed;
107+
108+
/**
109+
* @brief Initialize a MIDI1 Universal Midi Packet
110+
* @param _group The UMP group
111+
* @param _status The MIDI1 status nibble (command)
112+
* @param _channel The MIDI1 channel number
113+
* @param _p1 The 1st MIDI1 parameter
114+
* @param _p2 The 2nd MIDI1 parameter
115+
*/
116+
#define UMP_MIDI1(_group, _status, _channel, _p1, _p2) \
117+
((union ump){.midi1 = {.mt = MT_MIDI1_CHANNEL_VOICE, \
118+
.group = _group, \
119+
.status = _status, \
120+
.channel = _channel, \
121+
.p1 = _p1, \
122+
.p2 = _p2}})
123+
124+
/**
125+
* @defgroup usb_midi_status MIDI status nibble (command)
126+
* @ingroup usb_midi
127+
* @see ump112: 7.3 MIDI 1.0 Channel Voice Messages
128+
* @{
129+
*/
130+
#define MIDI_NOTE_OFF 0x8
131+
#define MIDI_NOTE_ON 0x9
132+
#define MIDI_AFTERTOUCH 0xa
133+
#define MIDI_CONTROL_CHANGE 0xb
134+
#define MIDI_PROGRAM_CHANGE 0xc
135+
#define MIDI_CHAN_AFTERTOUCH 0xd
136+
#define MIDI_PITCH_BEND 0xe
137+
138+
/**
139+
* @}
140+
*
141+
* @brief Send a Universal MIDI Packet to the host
142+
* @param[in] dev The USB-MIDI interface
143+
* @param[in] pkt The Universal MIDI packet to send
144+
* @return 0 on success
145+
* -EIO if MIDI2.0 is not enabled by the host
146+
* -EAGAIN if there isn't room in the transmission buffer
147+
*
148+
*/
149+
int usb_midi_send(const struct device *dev, const union ump *pkt);
150+
151+
/**
152+
* @brief Callback type for incoming Universal MIDI Packets from host
153+
* @param[in] dev The USB-MIDI interface receiving the packet
154+
* @param[in] pkt The received packet
155+
*/
156+
typedef void (*usb_midi_callback)(const struct device *dev, const union ump *pkt);
157+
158+
/**
159+
* @brief Callback for incoming Universal MIDI Packets from host
160+
* Set the function to call when a Universal MIDI Packet is received from the
161+
* host on that interface
162+
* @param[in] dev The USB-MIDI interface
163+
* @param[in] cb The function to call
164+
*/
165+
void usb_midi_set_callback(const struct device *dev, usb_midi_callback cb);
166+
167+
/**
168+
* @}
169+
*/
170+
171+
#endif

subsys/usb/device_next/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ zephyr_library_sources_ifdef(
7171
class/usbd_uac2.c
7272
)
7373

74+
zephyr_library_sources_ifdef(
75+
CONFIG_USBD_MIDI_CLASS
76+
class/usbd_midi.c
77+
)
78+
7479
zephyr_library_sources_ifdef(
7580
CONFIG_USBD_HID_SUPPORT
7681
class/usbd_hid.c

subsys/usb/device_next/class/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ rsource "Kconfig.bt"
1010
rsource "Kconfig.msc"
1111
rsource "Kconfig.uac2"
1212
rsource "Kconfig.hid"
13+
rsource "Kconfig.midi"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) 2024 Titouan Christophe
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
config USBD_MIDI_CLASS
6+
bool "USB MIDI 2.0 class support [EXPERIMENTAL]"
7+
select RING_BUFFER
8+
help
9+
Enable the USB MIDI 2.0 device class support.
10+
11+
if USBD_MIDI_CLASS
12+
13+
module = USBD_MIDI
14+
module-str = usbd midi
15+
source "subsys/logging/Kconfig.template.log_config"
16+
17+
endif

0 commit comments

Comments
 (0)