Skip to content
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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/connectivity/usb/device_next/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ New USB device support APIs
usbd_hid_device.rst
uac2_device.rst
usbd_msc_device.rst
usb_midi.rst
Copy link
Collaborator

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?

Copy link
Contributor Author

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 👍

12 changes: 12 additions & 0 deletions doc/connectivity/usb/device_next/api/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
51 changes: 51 additions & 0 deletions dts/bindings/usb/zephyr,usb-midi.yaml
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description: MIDI2 device

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with USB-MIDI2 device though


compatible: "zephyr,usb-midi"
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MIDI2 Group terminal block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
204 changes: 204 additions & 0 deletions include/zephyr/audio/midi.h
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
80 changes: 80 additions & 0 deletions include/zephyr/usb/class/usbd_midi.h
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @brief USB MIDI 2.0 class device API

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,
Copy link
Collaborator

@jfischer-no jfischer-no Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use consistently USB MIDI2, USB_MIDI2_, maybe usbd_midi2_

Not MIDI_2, not USB-MIDI2.0 ....

Comment on lines +30 to +33
Copy link
Collaborator

@jfischer-no jfischer-no Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is nothing else as false and true and can be just
void (*iface_ready_cb)(const struct device *dev, const bool ready);
I do not think anyone is going to implement MIDI1 class support.

};

/**
* @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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @param[in] dev MIDI2 device

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No undefined behavior
* @return 0 on success, all other values should be treated as error

* -EIO if MIDI2.0 is not enabled by the host
* -EAGAIN if there isn't room in the transmission buffer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-EAGAIN if there is no space in the TX buffer
IMO, ENOBUFS is better.

*/
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
9 changes: 9 additions & 0 deletions samples/subsys/usb/midi/CMakeLists.txt
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})
9 changes: 9 additions & 0 deletions samples/subsys/usb/midi/Kconfig
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"
Loading
Loading