Skip to content

Commit b54c0bf

Browse files
committed
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 b34802e commit b54c0bf

File tree

7 files changed

+212
-0
lines changed

7 files changed

+212
-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: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
.. zephyr:code-sample:: midi
2+
:name: USB MIDI device class sample
3+
:relevant-api: usbd_api usb_midi
4+
5+
USB MIDI 2.0 device class sample
6+
7+
Overview
8+
********
9+
10+
This sample demonstrates how to implement a USB MIDI 2.0 device. It can run on
11+
any board with a USB device controller. This samples sends all MIDI1 messages
12+
sent to the device back to the host. In addition, on boards that have a
13+
user button, pressing and releasing that button will send MIDI1 note on and
14+
note off messages.
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 button acts as if there was a user keyboard
21+
to press a key.
22+
23+
Building and Running
24+
********************
25+
26+
The code can be found in :zephyr_file:`samples/subsys/usb/midi`.
27+
28+
To build and flash the application:
29+
30+
.. zephyr-app-commands::
31+
:zephyr-app: samples/subsys/usb/midi
32+
:board: nucleo_f429zi
33+
:goals: build flash
34+
:compact:
35+
36+
Using the MIDI interface
37+
************************
38+
39+
Once this sample is flashed, connect the device USB port to a host computer
40+
with MIDI support. For example, on Linux, you can use alsa to access the device:
41+
42+
.. code-block:: console
43+
44+
$ amidi -l
45+
Dir Device Name
46+
IO hw:2,1,0 Group 1 (USBD MIDI Sample)
47+
48+
49+
The "USBD MIDI Sample" interface should also appear in any program with MIDI
50+
support (for example your favorite Digital Audio Workstation)
51+
52+
Testing loopback
53+
****************
54+
55+
Open a first shell, and start dumping MIDI events:
56+
57+
.. code-block:: console
58+
59+
$ amidi -p hw:2,1,0 -d
60+
61+
62+
Then, in a second shell, send some MIDI events (for example note-on/note-off):
63+
64+
.. code-block:: console
65+
66+
$ amidi -p hw:2,1,0 -S "90427f 804200"
67+
68+
These events should then appear in the first shell (dump)
69+
70+
On devboards with a user button, press it and observe that there are some note
71+
on/note off events delivered to the first shell (dump)
72+
73+
.. code-block:: console
74+
75+
$ amidi -p hw:2,1,0 -d
76+
77+
90 40 7F
78+
80 40 7F
79+
90 40 7F
80+
80 40 7F
81+
[...]
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,usb-midi";
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_MIDI_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
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: 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+
* @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/input/input.h>
14+
#include <zephyr/usb/class/usb_midi.h>
15+
16+
#include <zephyr/logging/log.h>
17+
LOG_MODULE_REGISTER(sample_usb_midi, LOG_LEVEL_INF);
18+
19+
static const struct device *const midi = DEVICE_DT_GET(DT_NODELABEL(usb_midi));
20+
21+
/* On boards that have a user button; button press/release -> MIDI note on/off */
22+
#if DT_NODE_EXISTS(DT_NODELABEL(user_button))
23+
static const struct device *const button = DEVICE_DT_GET(DT_PARENT(DT_NODELABEL(user_button)));
24+
25+
static void key_press(struct input_event *evt, void *user_data)
26+
{
27+
uint8_t command = evt->value ? MIDI_NOTE_ON : MIDI_NOTE_OFF;
28+
29+
usb_midi_send(midi, &UMP_MIDI1(0, command, 0, 0x40, 0x7f));
30+
}
31+
INPUT_CALLBACK_DEFINE(button, key_press, NULL);
32+
#endif
33+
34+
static void on_midi_packet(const struct device *dev, const union ump *midi_packet)
35+
{
36+
LOG_INF("Received MIDI packet (MT=%X)", midi_packet->mt);
37+
38+
/* Only send MIDI1 channel voice messages back to the host */
39+
if (midi_packet->mt == MT_MIDI1_CHANNEL_VOICE) {
40+
const struct ump_midi1 *midi1 = &midi_packet->midi1;
41+
42+
LOG_INF("Send back MIDI1 message chan=%d status=%d %02X %02X", midi1->channel,
43+
midi1->status, midi1->p1, midi1->p2);
44+
usb_midi_send(dev, midi_packet);
45+
}
46+
}
47+
48+
int main(void)
49+
{
50+
struct usbd_context *sample_usbd;
51+
52+
if (!device_is_ready(midi)) {
53+
LOG_ERR("MIDI device not ready");
54+
return -1;
55+
}
56+
57+
usb_midi_set_callback(midi, on_midi_packet);
58+
59+
sample_usbd = sample_usbd_init_device(NULL);
60+
if (sample_usbd == NULL) {
61+
LOG_ERR("Failed to initialize USB device");
62+
return -1;
63+
}
64+
65+
if (!usbd_can_detect_vbus(sample_usbd) && usbd_enable(sample_usbd)) {
66+
LOG_ERR("Failed to enable device support");
67+
return -1;
68+
}
69+
70+
LOG_INF("USB device support enabled");
71+
return 0;
72+
}

0 commit comments

Comments
 (0)