-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
usb: device_next: add new MIDI 2.0 device class #81197
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,4 @@ New USB device support APIs | |
usbd_hid_device.rst | ||
uac2_device.rst | ||
usbd_msc_device.rst | ||
usb_midi.rst | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
.. _usb_midi: | ||
|
||
MIDI 2.0 Class device API | ||
######################### | ||
|
||
USB MIDI 2.0 device specific API defined in :zephyr_file:`include/zephyr/usb/class/usbd_midi.h`. | ||
|
||
API Reference | ||
************* | ||
|
||
.. doxygengroup:: usb_midi | ||
.. doxygengroup:: midi_ump |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Copyright (c) 2024 Titouan Christophe | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
description: USB MIDI Class | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. description: MIDI2 device There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this confusing. We can exchange MIDI1 over a USB-MIDI2 device There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would agree with |
||
|
||
compatible: "zephyr,usb-midi" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. compatible: "zephyr,midi2-device" |
||
|
||
properties: | ||
"#address-cells": | ||
type: int | ||
const: 1 | ||
|
||
"#size-cells": | ||
type: int | ||
const: 1 | ||
|
||
child-binding: | ||
description: | | ||
USB MIDI Group terminal block. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MIDI2 Group terminal block? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmmh, not really. The concept of Group Terminal (Blocks) is in the USB-MIDI specification, not in MIDI2 (UMP) |
||
This represent a set of contiguous Universal MIDI groups through which the | ||
device exchange Universal MIDI Packets with the host. | ||
|
||
properties: | ||
reg: | ||
type: array | ||
required: true | ||
description: | | ||
First MIDI Group number (address) and number of Group Terminals (size) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First MIDI2 Group ? |
||
in this USB-MIDI Group Terminal Block. | ||
The MIDI Groups 1 to 16 corresponds to address 0x0 to 0xf. There are at | ||
most 16 addressable groups (of 16 channels each) per USB-MIDI interface. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. per MIDI2 interface. |
||
|
||
protocol: | ||
type: string | ||
enum: | ||
- "use-midi-ci" | ||
- "midi1-up-to-64b" | ||
- "midi1-up-to-128b" | ||
- "midi2" | ||
description: | | ||
Default MIDI protocol of the Group Terminals in this Block. | ||
|
||
terminal-type: | ||
type: string | ||
default: "bidirectional" | ||
enum: | ||
- "bidirectional" | ||
- "input-only" | ||
- "output-only" | ||
description: | | ||
Type (data direction) of Group Terminals in this Block. | ||
titouanc marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* | ||
* Copyright (c) 2024 Titouan Christophe | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#ifndef ZEPHYR_INCLUDE_AUDIO_MIDI_H_ | ||
#define ZEPHYR_INCLUDE_AUDIO_MIDI_H_ | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
#include <stdint.h> | ||
|
||
/** | ||
* @brief Universal MIDI Packet definitions | ||
* @defgroup midi_ump MIDI2 Universal MIDI Packet definitions | ||
* @ingroup audio_interface | ||
* @since 4.1 | ||
* @version 0.1.0 | ||
* @see ump112: "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" | ||
* Document version 1.1.2 | ||
* @{ | ||
*/ | ||
|
||
/** | ||
* @brief Universal MIDI Packet container | ||
*/ | ||
struct midi_ump { | ||
uint32_t data[4]; /**< Raw content, in the CPU native endianness */ | ||
}; | ||
|
||
/** | ||
* @defgroup midi_ump_mt Message types | ||
* @ingroup midi_ump | ||
* @see ump112: 2.1.4 Message Type (MT) Allocation | ||
* @{ | ||
*/ | ||
|
||
/** Utility Messages */ | ||
#define UMP_MT_UTILITY 0x00 | ||
/** System Real Time and System Common Messages (except System Exclusive) */ | ||
#define UMP_MT_SYS_RT_COMMON 0x01 | ||
/** MIDI 1.0 Channel Voice Messages */ | ||
#define UMP_MT_MIDI1_CHANNEL_VOICE 0x02 | ||
/** 64 bits Data Messages (including System Exclusive) */ | ||
#define UMP_MT_DATA_64 0x03 | ||
/** MIDI 2.0 Channel Voice Messages */ | ||
#define UMP_MT_MIDI2_CHANNEL_VOICE 0x04 | ||
/** 128 bits Data Messages */ | ||
#define UMP_MT_DATA_128 0x05 | ||
/** Flex Data Messages */ | ||
#define UMP_MT_FLEX_DATA 0x0d | ||
/** UMP Stream Message */ | ||
#define UMP_MT_UMP_STREAM 0x0f | ||
/** @} */ | ||
|
||
/** | ||
* @brief Message Type field of a Universal MIDI Packet | ||
* @param[in] ump Universal MIDI Packet | ||
*/ | ||
#define UMP_MT(ump) \ | ||
((ump).data[0] >> 28) | ||
|
||
/** | ||
* There are 16 UMP message types, each of which can be 1 to 4 uint32 long. | ||
* Hence this packed representation of 16x2b array as an uint32 lookup table | ||
*/ | ||
#define UMP_NUM_WORDS_LOOKUP_TABLE \ | ||
((0U << 0) | (0U << 2) | (0U << 4) | (1U << 6) | \ | ||
(1U << 8) | (3U << 10) | (0U << 12) | (0U << 14) | \ | ||
(1U << 16) | (1U << 18) | (1U << 20) | (2U << 22) | \ | ||
(2U << 24) | (3U << 26) | (3U << 28) | (3U << 30)) | ||
|
||
/** | ||
* @brief Size of a Universal MIDI Packet, in 32bit words | ||
* @param[in] ump Universal MIDI Packet | ||
* @see ump112: 2.1.4 Message Type (MT) Allocation | ||
*/ | ||
#define UMP_NUM_WORDS(ump) \ | ||
(1 + ((UMP_NUM_WORDS_LOOKUP_TABLE >> (2 * UMP_MT(ump))) & 3)) | ||
|
||
/** | ||
* @brief MIDI group field of a Universal MIDI Packet | ||
* @param[in] ump Universal MIDI Packet | ||
*/ | ||
#define UMP_GROUP(ump) \ | ||
(((ump).data[0] >> 24) & 0x0f) | ||
|
||
/** | ||
* @brief Status byte of a MIDI channel voice or system message | ||
* @param[in] ump Universal MIDI Packet (containing a MIDI1 event) | ||
*/ | ||
#define UMP_MIDI_STATUS(ump) \ | ||
(((ump).data[0] >> 16) & 0xff) | ||
/** | ||
* @brief Command of a MIDI channel voice message | ||
* @param[in] ump Universal MIDI Packet (containing a MIDI event) | ||
* @see midi_ump_cmd | ||
*/ | ||
#define UMP_MIDI_COMMAND(ump) \ | ||
(UMP_MIDI_STATUS(ump) >> 4) | ||
/** | ||
* @brief Channel of a MIDI channel voice message | ||
* @param[in] ump Universal MIDI Packet (containing a MIDI event) | ||
*/ | ||
#define UMP_MIDI_CHANNEL(ump) \ | ||
(UMP_MIDI_STATUS(ump) & 0x0f) | ||
/** | ||
* @brief First parameter of a MIDI1 channel voice or system message | ||
* @param[in] ump Universal MIDI Packet (containing a MIDI1 message) | ||
*/ | ||
#define UMP_MIDI1_P1(ump) \ | ||
(((ump).data[0] >> 8) & 0x7f) | ||
/** | ||
* @brief Second parameter of a MIDI1 channel voice or system message | ||
* @param[in] ump Universal MIDI Packet (containing a MIDI1 message) | ||
*/ | ||
#define UMP_MIDI1_P2(ump) \ | ||
((ump).data[0] & 0x7f) | ||
|
||
/** | ||
* @brief Initialize a UMP with a MIDI1 channel voice message | ||
* @remark For messages that take a single parameter, p2 is ignored by the receiver. | ||
* @param group The UMP group | ||
* @param command The MIDI1 command | ||
* @param channel The MIDI1 channel number | ||
* @param p1 The 1st MIDI1 parameter | ||
* @param p2 The 2nd MIDI1 parameter | ||
*/ | ||
#define UMP_MIDI1_CHANNEL_VOICE(group, command, channel, p1, p2) \ | ||
(struct midi_ump) {.data = { \ | ||
(UMP_MT_MIDI1_CHANNEL_VOICE << 28) \ | ||
| (((group) & 0x0f) << 24) \ | ||
| (((command) & 0x0f) << 20) \ | ||
| (((channel) & 0x0f) << 16) \ | ||
| (((p1) & 0x7f) << 8) \ | ||
| ((p2) & 0x7f) \ | ||
}} | ||
|
||
/** | ||
* @defgroup midi_ump_cmd MIDI commands | ||
* @ingroup midi_ump | ||
* @see ump112: 7.3 MIDI 1.0 Channel Voice Messages | ||
* | ||
* When UMP_MT(x)=UMP_MT_MIDI1_CHANNEL_VOICE or UMP_MT_MIDI2_CHANNEL_VOICE, then | ||
* UMP_MIDI_COMMAND(x) may be one of: | ||
* @{ | ||
*/ | ||
#define UMP_MIDI_NOTE_OFF 0x8 /**< Note Off (p1=note number, p2=velocity) */ | ||
#define UMP_MIDI_NOTE_ON 0x9 /**< Note On (p1=note number, p2=velocity) */ | ||
#define UMP_MIDI_AFTERTOUCH 0xa /**< Polyphonic aftertouch (p1=note number, p2=data) */ | ||
#define UMP_MIDI_CONTROL_CHANGE 0xb /**< Control Change (p1=index, p2=data) */ | ||
#define UMP_MIDI_PROGRAM_CHANGE 0xc /**< Control Change (p1=program) */ | ||
#define UMP_MIDI_CHAN_AFTERTOUCH 0xd /**< Channel aftertouch (p1=data) */ | ||
#define UMP_MIDI_PITCH_BEND 0xe /**< Pitch bend (p1=lsb, p2=msb) */ | ||
/** @} */ | ||
|
||
/** | ||
* @brief Initialize a UMP with a System Real Time and System Common Message | ||
* @remark For messages that take only one (or no) parameter, p2 (and p1) | ||
* are ignored by the receiver. | ||
* @param group The UMP group | ||
* @param status The status byte | ||
* @param p1 The 1st parameter | ||
* @param p2 The 2nd parameter | ||
*/ | ||
#define UMP_SYS_RT_COMMON(group, status, p1, p2) \ | ||
(struct midi_ump) {.data = { \ | ||
(UMP_MT_SYS_RT_COMMON << 28) \ | ||
| (((group) & 0x0f) << 24) \ | ||
| ((status) << 16) \ | ||
| (((p1) & 0x7f) << 8) \ | ||
| ((p2) & 0x7f) \ | ||
}} | ||
|
||
/** | ||
* @defgroup midi_ump_sys System common and System Real Time message status | ||
* @ingroup midi_ump | ||
* @see ump112: 7.6 System Common and System Real Time Messages | ||
* | ||
* When UMP_MT(x)=UMP_MT_SYS_RT_COMMON, UMP_MIDI_STATUS(x) may be one of: | ||
* @{ | ||
*/ | ||
#define UMP_SYS_MIDI_TIME_CODE 0xf1 /**< MIDI Time Code (no param) */ | ||
#define UMP_SYS_SONG_POSITION 0xf2 /**< Song Position Pointer (p1=lsb, p2=msb) */ | ||
#define UMP_SYS_SONG_SELECT 0xf3 /**< Song Select (p1=song number) */ | ||
#define UMP_SYS_TUNE_REQUEST 0xf6 /**< Tune Request (no param) */ | ||
#define UMP_SYS_TIMING_CLOCK 0xf8 /**< Timing Clock (no param) */ | ||
#define UMP_SYS_START 0xfa /**< Start (no param) */ | ||
#define UMP_SYS_CONTINUE 0xfb /**< Continue (no param) */ | ||
#define UMP_SYS_STOP 0xfc /**< Stop (no param) */ | ||
#define UMP_SYS_ACTIVE_SENSING 0xfe /**< Active sensing (no param) */ | ||
#define UMP_SYS_RESET 0xff /**< Reset (no param) */ | ||
/** @} */ | ||
|
||
/** @} */ | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright (c) 2024 Titouan Christophe | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_MIDI_H_ | ||
#define ZEPHYR_INCLUDE_USB_CLASS_USBD_MIDI_H_ | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
/** | ||
* @brief USB-MIDI 2.0 class device API | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
please also check below, s/USB-MIDI/USB MIDI |
||
* @defgroup usb_midi USB MIDI 2.0 Class device API | ||
* @ingroup usb | ||
* @since 4.1 | ||
* @version 0.1.0 | ||
* @see midi20: "Universal Serial Bus Device Class Definition for MIDI Devices" | ||
* Document Release 2.0 (May 5, 2020) | ||
* @{ | ||
*/ | ||
|
||
#include <zephyr/device.h> | ||
#include <zephyr/audio/midi.h> | ||
|
||
titouanc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** @brief Runtime status of a USB-MIDI2 interface */ | ||
enum usbd_midi_status { | ||
/** The interface is not enabled by the host */ | ||
USBD_MIDI_DISABLED = 0, | ||
/** The interface is enabled in USB-MIDI2.0 mode by the host */ | ||
USBD_MIDI_2_ENABLED, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use consistently Not MIDI_2, not USB-MIDI2.0 ....
Comment on lines
+30
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is nothing else as false and true and can be just |
||
}; | ||
|
||
/** | ||
* @brief USB-MIDI application event handlers | ||
*/ | ||
struct usbd_midi_ops { | ||
/** | ||
* @brief Callback type for incoming Universal MIDI Packets from host | ||
* @param[in] dev The USB-MIDI interface receiving the packet | ||
* @param[in] ump The received packet in Universal MIDI Packet format | ||
*/ | ||
void (*rx_packet_cb)(const struct device *dev, const struct midi_ump ump); | ||
|
||
/** | ||
* @brief Callback type for USB-MIDI interface runtime status change | ||
* @param[in] dev The USB-MIDI interface | ||
* @param[in] st The current status of the interface | ||
*/ | ||
void (*status_change_cb)(const struct device *dev, enum usbd_midi_status st); | ||
}; | ||
|
||
/** | ||
* @brief Send a Universal MIDI Packet to the host | ||
* @param[in] dev The USB-MIDI interface | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If you want to pass an interface, then you must not use Zephyr device model and DT, might be a better approach or not, if you are curious, check out the new USB DFU implementation PR. |
||
* @param[in] ump The packet to send, in Universal MIDI Packet format | ||
* @return 0 on success | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No undefined behavior |
||
* -EIO if MIDI2.0 is not enabled by the host | ||
* -EAGAIN if there isn't room in the transmission buffer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
*/ | ||
int usbd_midi_send(const struct device *dev, const struct midi_ump ump); | ||
|
||
/** | ||
* @brief Set the application event handlers on a USB-MIDI interface | ||
* @param[in] dev The USB-MIDI interface | ||
* @param[in] ops The event handlers. Pass NULL to reset all callbacks | ||
*/ | ||
void usbd_midi_set_ops(const struct device *dev, const struct usbd_midi_ops *ops); | ||
|
||
/** | ||
* @} | ||
*/ | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
cmake_minimum_required(VERSION 3.20.0) | ||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) | ||
project(usb_midi) | ||
|
||
include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) | ||
FILE(GLOB app_sources src/*.c) | ||
target_sources(app PRIVATE ${app_sources}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Copyright (c) 2024 Titouan Christophe | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
# Source common USB sample options used to initialize new experimental USB | ||
# device stack. The scope of these options is limited to USB samples in project | ||
# tree, you cannot use them in your own application. | ||
source "samples/subsys/usb/common/Kconfig.sample_usbd" | ||
|
||
source "Kconfig.zephyr" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you used markdown formatting(?) in the commit message, what is the intent behind that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I initially had one commit, and because Github uses the commit message by default for the PR description, this made for a nice description.
Will rewrite the commit message with the bare links only 👍