Skip to content

Commit 9be79bc

Browse files
titouanckartben
authored andcommitted
samples: subsys: usb: midi: add new sample
Add a sample application that demonstrates how to use the new USB-MIDI 2.0 device class. This shows how to set up the device tree, how to exchange MIDI data with the host, and how to use the data accessors provided by the new API. Signed-off-by: Titouan Christophe <[email protected]>
1 parent c525e7a commit 9be79bc

File tree

7 files changed

+240
-0
lines changed

7 files changed

+240
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(usb_midi)
6+
7+
include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
8+
FILE(GLOB app_sources src/*.c)
9+
target_sources(app PRIVATE ${app_sources})

samples/subsys/usb/midi/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2024 Titouan Christophe
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# Source common USB sample options used to initialize new experimental USB
5+
# device stack. The scope of these options is limited to USB samples in project
6+
# tree, you cannot use them in your own application.
7+
source "samples/subsys/usb/common/Kconfig.sample_usbd"
8+
9+
source "Kconfig.zephyr"

samples/subsys/usb/midi/README.rst

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
.. zephyr:code-sample:: midi
2+
:name: USB MIDI device class
3+
:relevant-api: usbd_api usb_midi input_events
4+
5+
Implements a simple USB-MIDI loopback and keyboard device.
6+
7+
Overview
8+
********
9+
10+
This sample demonstrates how to implement a USB MIDI device. It can run on
11+
any board with a USB device controller. This sample sends all MIDI1 messages
12+
sent to the device back to the host. In addition, presses and release on
13+
input keys (such as the board user buttons) are sent as MIDI1 note on and
14+
note off events.
15+
16+
The application exposes a single USB-MIDI interface with a single bidirectional
17+
group terminal. This allows exchanging data with the host on a "virtual wire"
18+
that carries MIDI1 messages, pretty much like a standard USB-MIDI in/out adapter
19+
would provide. The loopback acts as if a real MIDI cable was connected between
20+
the output and the input, and the input keys act as a MIDI keyboard.
21+
22+
Building and Running
23+
********************
24+
25+
The code can be found in :zephyr_file:`samples/subsys/usb/midi`.
26+
27+
To build and flash the application:
28+
29+
.. zephyr-app-commands::
30+
:zephyr-app: samples/subsys/usb/midi
31+
:board: nucleo_f429zi
32+
:goals: build flash
33+
:compact:
34+
35+
Using the MIDI interface
36+
************************
37+
38+
Once this sample is flashed, connect the device USB port to a host computer
39+
with MIDI support. For example, on Linux, you can use alsa to access the device:
40+
41+
.. code-block:: console
42+
43+
$ amidi -l
44+
Dir Device Name
45+
IO hw:2,1,0 Group 1 (USBD MIDI Sample)
46+
47+
On Mac OS you can use the system tool "Audio MIDI Setup" to view the device,
48+
see https://support.apple.com/guide/audio-midi-setup/set-up-midi-devices-ams875bae1e0/mac
49+
50+
The "USBD MIDI Sample" interface should also appear in any program with MIDI
51+
support; like your favorite Digital Audio Workstation or synthetizer. If you
52+
don't have any such program at hand, there are some webmidi programs online,
53+
for example: https://muted.io/piano/.
54+
55+
Testing loopback
56+
****************
57+
58+
Open a first shell, and start dumping MIDI events:
59+
60+
.. code-block:: console
61+
62+
$ amidi -p hw:2,1,0 -d
63+
64+
65+
Then, in a second shell, send some MIDI events (for example note-on/note-off):
66+
67+
.. code-block:: console
68+
69+
$ amidi -p hw:2,1,0 -S "90427f 804200"
70+
71+
These events should then appear in the first shell (dump)
72+
73+
On devboards with a user button, press it and observe that there are some note
74+
on/note off events delivered to the first shell (dump)
75+
76+
.. code-block:: console
77+
78+
$ amidi -p hw:2,1,0 -d
79+
80+
90 40 7F
81+
80 40 7F
82+
90 40 7F
83+
80 40 7F
84+
[...]

samples/subsys/usb/midi/app.overlay

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright (c) 2024 Titouan Christophe
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/ {
8+
usb_midi: usb_midi {
9+
compatible = "zephyr,midi2-device";
10+
status = "okay";
11+
#address-cells = <1>;
12+
#size-cells = <1>;
13+
14+
midi_in_out@0 {
15+
reg = <0 1>;
16+
protocol = "midi1-up-to-128b";
17+
};
18+
};
19+
};

samples/subsys/usb/midi/prj.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CONFIG_USB_DEVICE_STACK_NEXT=y
2+
CONFIG_USBD_MIDI2_CLASS=y
3+
4+
CONFIG_SAMPLE_USBD_PRODUCT="USBD MIDI Sample"
5+
CONFIG_SAMPLE_USBD_PID=0x0010
6+
7+
CONFIG_INPUT=y
8+
9+
CONFIG_LOG=y
10+
CONFIG_USBD_LOG_LEVEL_WRN=y
11+
CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y

samples/subsys/usb/midi/sample.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
sample:
2+
name: USB MIDI 2.0 device class sample
3+
tests:
4+
sample.usb_device_next.midi:
5+
depends_on: usbd
6+
tags: usb
7+
harness: console
8+
harness_config:
9+
type: one_line
10+
regex:
11+
- "USB device support enabled"

samples/subsys/usb/midi/src/main.c

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2024 Titouan Christophe
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* @file
7+
* @brief Sample application for USB MIDI 2.0 device class
8+
*/
9+
10+
#include <sample_usbd.h>
11+
12+
#include <zephyr/device.h>
13+
#include <zephyr/drivers/gpio.h>
14+
#include <zephyr/input/input.h>
15+
#include <zephyr/usb/class/usbd_midi2.h>
16+
17+
#include <zephyr/logging/log.h>
18+
LOG_MODULE_REGISTER(sample_usb_midi, LOG_LEVEL_INF);
19+
20+
static const struct device *const midi = DEVICE_DT_GET(DT_NODELABEL(usb_midi));
21+
22+
static struct gpio_dt_spec led = GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios, {0});
23+
24+
static void key_press(struct input_event *evt, void *user_data)
25+
{
26+
/* Only handle key presses in the 7bit MIDI range */
27+
if (evt->type != INPUT_EV_KEY || evt->code > 0x7f) {
28+
return;
29+
}
30+
uint8_t command = evt->value ? UMP_MIDI_NOTE_ON : UMP_MIDI_NOTE_OFF;
31+
uint8_t channel = 0;
32+
uint8_t note = evt->code;
33+
uint8_t velocity = 100;
34+
35+
struct midi_ump ump = UMP_MIDI1_CHANNEL_VOICE(0, command, channel,
36+
note, velocity);
37+
usbd_midi_send(midi, ump);
38+
}
39+
INPUT_CALLBACK_DEFINE(NULL, key_press, NULL);
40+
41+
static void on_midi_packet(const struct device *dev, const struct midi_ump ump)
42+
{
43+
LOG_INF("Received MIDI packet (MT=%X)", UMP_MT(ump));
44+
45+
/* Only send MIDI1 channel voice messages back to the host */
46+
if (UMP_MT(ump) == UMP_MT_MIDI1_CHANNEL_VOICE) {
47+
LOG_INF("Send back MIDI1 message %02X %02X %02X", UMP_MIDI_STATUS(ump),
48+
UMP_MIDI1_P1(ump), UMP_MIDI1_P2(ump));
49+
usbd_midi_send(dev, ump);
50+
}
51+
}
52+
53+
static void on_device_ready(const struct device *dev, const bool ready)
54+
{
55+
/* Light up the LED (if any) when USB-MIDI2.0 is enabled */
56+
if (led.port) {
57+
gpio_pin_set_dt(&led, ready);
58+
}
59+
}
60+
61+
static const struct usbd_midi_ops ops = {
62+
.rx_packet_cb = on_midi_packet,
63+
.ready_cb = on_device_ready,
64+
};
65+
66+
int main(void)
67+
{
68+
struct usbd_context *sample_usbd;
69+
70+
if (!device_is_ready(midi)) {
71+
LOG_ERR("MIDI device not ready");
72+
return -1;
73+
}
74+
75+
if (led.port) {
76+
if (gpio_pin_configure_dt(&led, GPIO_OUTPUT)) {
77+
LOG_ERR("Unable to setup LED, not using it");
78+
memset(&led, 0, sizeof(led));
79+
}
80+
}
81+
82+
usbd_midi_set_ops(midi, &ops);
83+
84+
sample_usbd = sample_usbd_init_device(NULL);
85+
if (sample_usbd == NULL) {
86+
LOG_ERR("Failed to initialize USB device");
87+
return -1;
88+
}
89+
90+
if (usbd_enable(sample_usbd)) {
91+
LOG_ERR("Failed to enable device support");
92+
return -1;
93+
}
94+
95+
LOG_INF("USB device support enabled");
96+
return 0;
97+
}

0 commit comments

Comments
 (0)