Skip to content

Commit c525e7a

Browse files
titouanckartben
authored andcommitted
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_ops(device, &{.rx_packet_cb = handler})` 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 f19e5bc commit c525e7a

File tree

9 files changed

+1150
-0
lines changed

9 files changed

+1150
-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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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/usbd_midi2.h`.
7+
8+
API Reference
9+
*************
10+
11+
.. doxygengroup:: usb_midi
12+
.. doxygengroup:: midi_ump
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: MIDI2 device
5+
6+
compatible: "zephyr,midi2-device"
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+
MIDI2 Group terminal block.
20+
This represent a set of contiguous MIDI2 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 MIDI2 Group number (address) and number of Group Terminals (size)
29+
in this MIDI2 Group Terminal Block.
30+
The MIDI2 Groups 1 to 16 corresponds to address 0x0 to 0xf. There are
31+
at most 16 addressable groups (of 16 chans each) per MIDI2 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.

include/zephyr/audio/midi.h

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright (c) 2024 Titouan Christophe
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_AUDIO_MIDI_H_
8+
#define ZEPHYR_INCLUDE_AUDIO_MIDI_H_
9+
10+
#ifdef __cplusplus
11+
extern "C" {
12+
#endif
13+
14+
#include <stdint.h>
15+
16+
/**
17+
* @brief Universal MIDI Packet definitions
18+
* @defgroup midi_ump MIDI2 Universal MIDI Packet definitions
19+
* @ingroup audio_interface
20+
* @since 4.1
21+
* @version 0.1.0
22+
* @see ump112: "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol"
23+
* Document version 1.1.2
24+
* @{
25+
*/
26+
27+
/**
28+
* @brief Universal MIDI Packet container
29+
*/
30+
struct midi_ump {
31+
uint32_t data[4]; /**< Raw content, in the CPU native endianness */
32+
};
33+
34+
/**
35+
* @defgroup midi_ump_mt Message types
36+
* @ingroup midi_ump
37+
* @see ump112: 2.1.4 Message Type (MT) Allocation
38+
* @{
39+
*/
40+
41+
/** Utility Messages */
42+
#define UMP_MT_UTILITY 0x00
43+
/** System Real Time and System Common Messages (except System Exclusive) */
44+
#define UMP_MT_SYS_RT_COMMON 0x01
45+
/** MIDI 1.0 Channel Voice Messages */
46+
#define UMP_MT_MIDI1_CHANNEL_VOICE 0x02
47+
/** 64 bits Data Messages (including System Exclusive) */
48+
#define UMP_MT_DATA_64 0x03
49+
/** MIDI 2.0 Channel Voice Messages */
50+
#define UMP_MT_MIDI2_CHANNEL_VOICE 0x04
51+
/** 128 bits Data Messages */
52+
#define UMP_MT_DATA_128 0x05
53+
/** Flex Data Messages */
54+
#define UMP_MT_FLEX_DATA 0x0d
55+
/** UMP Stream Message */
56+
#define UMP_MT_UMP_STREAM 0x0f
57+
/** @} */
58+
59+
/**
60+
* @brief Message Type field of a Universal MIDI Packet
61+
* @param[in] ump Universal MIDI Packet
62+
*/
63+
#define UMP_MT(ump) \
64+
((ump).data[0] >> 28)
65+
66+
/**
67+
* There are 16 UMP message types, each of which can be 1 to 4 uint32 long.
68+
* Hence this packed representation of 16x2b array as an uint32 lookup table
69+
*/
70+
#define UMP_NUM_WORDS_LOOKUP_TABLE \
71+
((0U << 0) | (0U << 2) | (0U << 4) | (1U << 6) | \
72+
(1U << 8) | (3U << 10) | (0U << 12) | (0U << 14) | \
73+
(1U << 16) | (1U << 18) | (1U << 20) | (2U << 22) | \
74+
(2U << 24) | (3U << 26) | (3U << 28) | (3U << 30))
75+
76+
/**
77+
* @brief Size of a Universal MIDI Packet, in 32bit words
78+
* @param[in] ump Universal MIDI Packet
79+
* @see ump112: 2.1.4 Message Type (MT) Allocation
80+
*/
81+
#define UMP_NUM_WORDS(ump) \
82+
(1 + ((UMP_NUM_WORDS_LOOKUP_TABLE >> (2 * UMP_MT(ump))) & 3))
83+
84+
/**
85+
* @brief MIDI group field of a Universal MIDI Packet
86+
* @param[in] ump Universal MIDI Packet
87+
*/
88+
#define UMP_GROUP(ump) \
89+
(((ump).data[0] >> 24) & 0x0f)
90+
91+
/**
92+
* @brief Status byte of a MIDI channel voice or system message
93+
* @param[in] ump Universal MIDI Packet (containing a MIDI1 event)
94+
*/
95+
#define UMP_MIDI_STATUS(ump) \
96+
(((ump).data[0] >> 16) & 0xff)
97+
/**
98+
* @brief Command of a MIDI channel voice message
99+
* @param[in] ump Universal MIDI Packet (containing a MIDI event)
100+
* @see midi_ump_cmd
101+
*/
102+
#define UMP_MIDI_COMMAND(ump) \
103+
(UMP_MIDI_STATUS(ump) >> 4)
104+
/**
105+
* @brief Channel of a MIDI channel voice message
106+
* @param[in] ump Universal MIDI Packet (containing a MIDI event)
107+
*/
108+
#define UMP_MIDI_CHANNEL(ump) \
109+
(UMP_MIDI_STATUS(ump) & 0x0f)
110+
/**
111+
* @brief First parameter of a MIDI1 channel voice or system message
112+
* @param[in] ump Universal MIDI Packet (containing a MIDI1 message)
113+
*/
114+
#define UMP_MIDI1_P1(ump) \
115+
(((ump).data[0] >> 8) & 0x7f)
116+
/**
117+
* @brief Second parameter of a MIDI1 channel voice or system message
118+
* @param[in] ump Universal MIDI Packet (containing a MIDI1 message)
119+
*/
120+
#define UMP_MIDI1_P2(ump) \
121+
((ump).data[0] & 0x7f)
122+
123+
/**
124+
* @brief Initialize a UMP with a MIDI1 channel voice message
125+
* @remark For messages that take a single parameter, p2 is ignored by the receiver.
126+
* @param group The UMP group
127+
* @param command The MIDI1 command
128+
* @param channel The MIDI1 channel number
129+
* @param p1 The 1st MIDI1 parameter
130+
* @param p2 The 2nd MIDI1 parameter
131+
*/
132+
#define UMP_MIDI1_CHANNEL_VOICE(group, command, channel, p1, p2) \
133+
(struct midi_ump) {.data = { \
134+
(UMP_MT_MIDI1_CHANNEL_VOICE << 28) \
135+
| (((group) & 0x0f) << 24) \
136+
| (((command) & 0x0f) << 20) \
137+
| (((channel) & 0x0f) << 16) \
138+
| (((p1) & 0x7f) << 8) \
139+
| ((p2) & 0x7f) \
140+
}}
141+
142+
/**
143+
* @defgroup midi_ump_cmd MIDI commands
144+
* @ingroup midi_ump
145+
* @see ump112: 7.3 MIDI 1.0 Channel Voice Messages
146+
*
147+
* When UMP_MT(x)=UMP_MT_MIDI1_CHANNEL_VOICE or UMP_MT_MIDI2_CHANNEL_VOICE, then
148+
* UMP_MIDI_COMMAND(x) may be one of:
149+
* @{
150+
*/
151+
#define UMP_MIDI_NOTE_OFF 0x8 /**< Note Off (p1=note number, p2=velocity) */
152+
#define UMP_MIDI_NOTE_ON 0x9 /**< Note On (p1=note number, p2=velocity) */
153+
#define UMP_MIDI_AFTERTOUCH 0xa /**< Polyphonic aftertouch (p1=note number, p2=data) */
154+
#define UMP_MIDI_CONTROL_CHANGE 0xb /**< Control Change (p1=index, p2=data) */
155+
#define UMP_MIDI_PROGRAM_CHANGE 0xc /**< Control Change (p1=program) */
156+
#define UMP_MIDI_CHAN_AFTERTOUCH 0xd /**< Channel aftertouch (p1=data) */
157+
#define UMP_MIDI_PITCH_BEND 0xe /**< Pitch bend (p1=lsb, p2=msb) */
158+
/** @} */
159+
160+
/**
161+
* @brief Initialize a UMP with a System Real Time and System Common Message
162+
* @remark For messages that take only one (or no) parameter, p2 (and p1)
163+
* are ignored by the receiver.
164+
* @param group The UMP group
165+
* @param status The status byte
166+
* @param p1 The 1st parameter
167+
* @param p2 The 2nd parameter
168+
*/
169+
#define UMP_SYS_RT_COMMON(group, status, p1, p2) \
170+
(struct midi_ump) {.data = { \
171+
(UMP_MT_SYS_RT_COMMON << 28) \
172+
| (((group) & 0x0f) << 24) \
173+
| ((status) << 16) \
174+
| (((p1) & 0x7f) << 8) \
175+
| ((p2) & 0x7f) \
176+
}}
177+
178+
/**
179+
* @defgroup midi_ump_sys System common and System Real Time message status
180+
* @ingroup midi_ump
181+
* @see ump112: 7.6 System Common and System Real Time Messages
182+
*
183+
* When UMP_MT(x)=UMP_MT_SYS_RT_COMMON, UMP_MIDI_STATUS(x) may be one of:
184+
* @{
185+
*/
186+
#define UMP_SYS_MIDI_TIME_CODE 0xf1 /**< MIDI Time Code (no param) */
187+
#define UMP_SYS_SONG_POSITION 0xf2 /**< Song Position Pointer (p1=lsb, p2=msb) */
188+
#define UMP_SYS_SONG_SELECT 0xf3 /**< Song Select (p1=song number) */
189+
#define UMP_SYS_TUNE_REQUEST 0xf6 /**< Tune Request (no param) */
190+
#define UMP_SYS_TIMING_CLOCK 0xf8 /**< Timing Clock (no param) */
191+
#define UMP_SYS_START 0xfa /**< Start (no param) */
192+
#define UMP_SYS_CONTINUE 0xfb /**< Continue (no param) */
193+
#define UMP_SYS_STOP 0xfc /**< Stop (no param) */
194+
#define UMP_SYS_ACTIVE_SENSING 0xfe /**< Active sensing (no param) */
195+
#define UMP_SYS_RESET 0xff /**< Reset (no param) */
196+
/** @} */
197+
198+
/** @} */
199+
200+
#ifdef __cplusplus
201+
}
202+
#endif
203+
204+
#endif

include/zephyr/usb/class/usbd_midi2.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2024 Titouan Christophe
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_MIDI_H_
8+
#define ZEPHYR_INCLUDE_USB_CLASS_USBD_MIDI_H_
9+
10+
#ifdef __cplusplus
11+
extern "C" {
12+
#endif
13+
14+
/**
15+
* @brief USB MIDI 2.0 class device API
16+
* @defgroup usb_midi USB MIDI 2.0 Class device API
17+
* @ingroup usb
18+
* @since 4.1
19+
* @version 0.1.0
20+
* @see midi20: "Universal Serial Bus Device Class Definition for MIDI Devices"
21+
* Document Release 2.0 (May 5, 2020)
22+
* @{
23+
*/
24+
25+
#include <zephyr/device.h>
26+
#include <zephyr/audio/midi.h>
27+
28+
/**
29+
* @brief MIDI2 application event handlers
30+
*/
31+
struct usbd_midi_ops {
32+
/**
33+
* @brief Callback type for incoming Universal MIDI Packets from host
34+
* @param[in] dev The MIDI2 device receiving the packet
35+
* @param[in] ump The received packet in Universal MIDI Packet format
36+
*/
37+
void (*rx_packet_cb)(const struct device *dev, const struct midi_ump ump);
38+
39+
/**
40+
* @brief Callback type for MIDI2 interface runtime status change
41+
* @param[in] dev The MIDI2 device
42+
* @param[in] ready True if the interface is enabled by the host
43+
*/
44+
void (*ready_cb)(const struct device *dev, const bool ready);
45+
};
46+
47+
/**
48+
* @brief Send a Universal MIDI Packet to the host
49+
* @param[in] dev The MIDI2 device
50+
* @param[in] ump The packet to send, in Universal MIDI Packet format
51+
* @return 0 on success, all other values should be treated as error
52+
* -EIO if USB MIDI 2.0 is not enabled by the host
53+
* -ENOBUFS if there is no space in the TX buffer
54+
*/
55+
int usbd_midi_send(const struct device *dev, const struct midi_ump ump);
56+
57+
/**
58+
* @brief Set the application event handlers on a USB MIDI device
59+
* @param[in] dev The MIDI2 device
60+
* @param[in] ops The event handlers. Pass NULL to reset all callbacks
61+
*/
62+
void usbd_midi_set_ops(const struct device *dev, const struct usbd_midi_ops *ops);
63+
64+
/**
65+
* @}
66+
*/
67+
68+
#ifdef __cplusplus
69+
}
70+
#endif
71+
72+
#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_MIDI2_CLASS
76+
class/usbd_midi2.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.midi2"
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_MIDI2_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_MIDI2_CLASS
12+
13+
module = USBD_MIDI2
14+
module-str = usbd midi2
15+
source "subsys/logging/Kconfig.template.log_config"
16+
17+
endif

0 commit comments

Comments
 (0)