Skip to content

Commit 3c50822

Browse files
titouanckartben
authored andcommitted
samples: net: midi2: new sample
Add a new sample to demonstrate usage of the newly introduced Network MIDI 2.0 host stack. Signed-off-by: Titouan Christophe <[email protected]>
1 parent 38b1a9c commit 3c50822

File tree

8 files changed

+342
-0
lines changed

8 files changed

+342
-0
lines changed

samples/net/midi2/CMakeLists.txt

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

samples/net/midi2/Kconfig

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) 2025 Titouan Christophe
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
menu "Networking MIDI2 sample application"
5+
6+
choice NET_SAMPLE_MIDI2_AUTH_TYPE
7+
prompt "UMP Endpoint authentication method"
8+
default NET_SAMPLE_MIDI2_AUTH_NONE
9+
10+
config NET_SAMPLE_MIDI2_AUTH_NONE
11+
bool "No authentication"
12+
13+
config NET_SAMPLE_MIDI2_AUTH_SHARED_SECRET
14+
bool "Authentication with shared secret"
15+
select NETMIDI2_HOST_AUTH
16+
17+
config NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD
18+
bool "Authentication with username+password"
19+
select NETMIDI2_HOST_AUTH
20+
21+
endchoice
22+
23+
config NET_SAMPLE_MIDI2_SHARED_SECRET
24+
string "Shared secret"
25+
depends on NET_SAMPLE_MIDI2_AUTH_SHARED_SECRET
26+
27+
config NET_SAMPLE_MIDI2_USERNAME
28+
string "Username"
29+
depends on NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD
30+
31+
config NET_SAMPLE_MIDI2_PASSWORD
32+
string "Password"
33+
depends on NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD
34+
35+
endmenu
36+
37+
source "Kconfig.zephyr"

samples/net/midi2/README.rst

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
.. zephyr:code-sample:: netmidi2
2+
:name: MIDI2 network transport
3+
:relevant-api: midi_ump net_midi2
4+
5+
Exchange Universal MIDI Packets over the Network MIDI 2.0 protocol
6+
7+
Overview
8+
********
9+
10+
This sample demonstrates usage of the Network MIDI 2.0 stack:
11+
12+
* start a UMP Endpoint host reachable on the network
13+
* respond to UMP Stream discovery messages, so that clients can discover the
14+
topology described in the device tree
15+
* if ``midi_serial`` port is defined in the device tree,
16+
send MIDI1 data from UMP group 9 there
17+
* if ``midi_green_led`` node is defined in the device tree,
18+
light up the led when sending data on the serial port
19+
20+
Requirements
21+
************
22+
23+
This sample requires a board with IP networking support. To perform anything
24+
useful against the running sample, you will also need a Network MIDI2.0 client
25+
to connect to the target, for example `pymidi2`_
26+
27+
Building and Running
28+
********************
29+
30+
The easiest way to try out this sample without any hardware is using
31+
``native_sim``. See :ref:`native_sim ethernet driver <nsim_per_ethe>` to setup
32+
networking on your computer accordingly
33+
34+
.. zephyr-app-commands::
35+
:zephyr-app: samples/net/midi2/
36+
:board: native_sim
37+
:goals: run
38+
39+
The Network MIDI 2.0 endpoint should now be reachable on the network:
40+
41+
.. code-block:: console
42+
43+
$ pymidi2 find
44+
Zephyr-UDP-MIDI2 (udp://192.0.2.1:45486)
45+
- Block #0 [io : Recv/Send] 'Synthesizer' UMP groups {0, 1, 2, 3} [MIDI1 + MIDI2]
46+
- Block #1 [i- : Recv ] 'Keyboard' UMP groups {8} [MIDI1 only]
47+
- Block #2 [-o : Send] 'External output (MIDI DIN-5)' UMP groups {9} [MIDI1 31.25kb/s]
48+
49+
50+
Furthermore, this sample pairs well with the :ref:`olimex_shield_midi`,
51+
that conveniently defines the device tree nodes for the external MIDI OUT
52+
and its led. For example, using this shield on the ST Nucleo F429zi:
53+
54+
.. zephyr-app-commands::
55+
:zephyr-app: samples/net/midi2
56+
:board: nucleo_f429zi
57+
:shield: olimex_shield_midi
58+
:goals: build flash
59+
60+
Using authentication
61+
********************
62+
63+
To enable shared secret authentication to connect to the UMP endpoint host,
64+
enable :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_AUTH_SHARED_SECRET`,
65+
and then configure the desired shared secret in
66+
:kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_SHARED_SECRET`
67+
68+
To enable user/password authentication instead, enable
69+
:kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD`, and then
70+
configure the desired username/password in
71+
:kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_USERNAME`
72+
and :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_PASSWORD`
73+
74+
.. _pymidi2:
75+
https://github.com/titouanc/pymidi2

samples/net/midi2/app.overlay

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2025 Titouan Christophe
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/ {
7+
midi2: zephyr-midi2 {
8+
compatible = "zephyr,midi2-device";
9+
status = "okay";
10+
#address-cells = <1>;
11+
#size-cells = <1>;
12+
label = "Zephyr-UDP-MIDI2";
13+
14+
midi2@0 {
15+
reg = <0 4>;
16+
label = "Synthesizer";
17+
protocol = "midi2";
18+
};
19+
20+
midi1@8 {
21+
reg = <8 1>;
22+
protocol = "midi1-up-to-64b";
23+
terminal-type = "input-only";
24+
label = "Keyboard";
25+
};
26+
27+
ext_midi_out: midi1@9 {
28+
reg = <9 1>;
29+
protocol = "midi1-up-to-64b";
30+
terminal-type = "output-only";
31+
label = "External output (MIDI DIN-5)";
32+
serial-31250bps;
33+
};
34+
};
35+
};

samples/net/midi2/prj.conf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CONFIG_NETWORKING=y
2+
CONFIG_ZVFS_POLL_MAX=4
3+
CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5
4+
CONFIG_NET_CONFIG_SETTINGS=y
5+
CONFIG_NET_CONFIG_NEED_IPV6=y
6+
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
7+
CONFIG_NET_CONFIG_NEED_IPV4=y
8+
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
9+
10+
CONFIG_NET_HOSTNAME_ENABLE=y
11+
CONFIG_MDNS_RESPONDER=y
12+
CONFIG_DNS_SD=y
13+
14+
CONFIG_NETMIDI2_HOST=y
15+
CONFIG_MIDI2_UMP_STREAM_RESPONDER=y
16+
17+
CONFIG_LOG=y
18+
CONFIG_NET_LOG=y
19+
CONFIG_TEST_RANDOM_GENERATOR=y

samples/net/midi2/sample.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
common:
2+
harness: net
3+
depends_on: netif
4+
tags:
5+
- net
6+
- midi2
7+
sample:
8+
name: Network MIDI2.0 sample application
9+
description: Demonstrates usage of the Network MIDI2.0 (udp) host endpoint
10+
tests:
11+
sample.net.midi2.host: {}
12+
sample.net.midi2.host_auth_shared:
13+
extra_configs:
14+
- CONFIG_NET_SAMPLE_MIDI2_AUTH_SHARED_SECRET=y
15+
- CONFIG_NET_SAMPLE_MIDI2_SHARED_SECRET="some-secret"
16+
sample.net.midi2.host_auth_user:
17+
extra_configs:
18+
- CONFIG_NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD=y
19+
- CONFIG_NET_SAMPLE_MIDI2_USERNAME="user"
20+
- CONFIG_NET_SAMPLE_MIDI2_PASSWORD="passwd"

samples/net/midi2/src/main.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2025 Titouan Christophe
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <zephyr/kernel.h>
7+
#include <errno.h>
8+
#include <stdio.h>
9+
10+
#include <zephyr/audio/midi.h>
11+
#include <zephyr/net/dns_sd.h>
12+
#include <zephyr/net/midi2.h>
13+
14+
#include <ump_stream_responder.h>
15+
16+
#include <zephyr/logging/log.h>
17+
LOG_MODULE_REGISTER(net_midi2_sample, LOG_LEVEL_DBG);
18+
19+
#define ACT_LED_NODE DT_NODELABEL(midi_green_led)
20+
#define SERIAL_NODE DT_NODELABEL(midi_serial)
21+
22+
#if !DT_NODE_EXISTS(ACT_LED_NODE)
23+
#define CONFIGURE_LED()
24+
#define SET_LED(_state)
25+
#else /* DT_NODE_EXISTS(ACT_LED_NODE) */
26+
#include <zephyr/drivers/gpio.h>
27+
28+
static const struct gpio_dt_spec act_led = GPIO_DT_SPEC_GET(ACT_LED_NODE, gpios);
29+
30+
#define CONFIGURE_LED() gpio_pin_configure_dt(&act_led, GPIO_OUTPUT_INACTIVE)
31+
#define SET_LED(_state) gpio_pin_set_dt(&act_led, (_state))
32+
#endif /* DT_NODE_EXISTS(ACT_LED_NODE) */
33+
34+
35+
#if !DT_NODE_EXISTS(SERIAL_NODE)
36+
#define send_external_midi1(...)
37+
#else /* DT_NODE_EXISTS(SERIAL_NODE) */
38+
#include <zephyr/drivers/uart.h>
39+
40+
static const struct device *const uart_dev = DEVICE_DT_GET(SERIAL_NODE);
41+
42+
static inline void send_external_midi1(const struct midi_ump ump)
43+
{
44+
/* Only send MIDI events aimed at the external output */
45+
if (UMP_GROUP(ump) != DT_REG_ADDR(DT_NODELABEL(ext_midi_out))) {
46+
return;
47+
}
48+
49+
switch (UMP_MIDI_COMMAND(ump)) {
50+
case UMP_MIDI_PROGRAM_CHANGE:
51+
SET_LED(1);
52+
uart_poll_out(uart_dev, UMP_MIDI_STATUS(ump));
53+
uart_poll_out(uart_dev, UMP_MIDI1_P1(ump));
54+
SET_LED(0);
55+
break;
56+
57+
case UMP_MIDI_NOTE_OFF:
58+
case UMP_MIDI_NOTE_ON:
59+
case UMP_MIDI_AFTERTOUCH:
60+
case UMP_MIDI_CONTROL_CHANGE:
61+
case UMP_MIDI_PITCH_BEND:
62+
SET_LED(1);
63+
uart_poll_out(uart_dev, UMP_MIDI_STATUS(ump));
64+
uart_poll_out(uart_dev, UMP_MIDI1_P1(ump));
65+
uart_poll_out(uart_dev, UMP_MIDI1_P2(ump));
66+
SET_LED(0);
67+
break;
68+
}
69+
}
70+
#endif /* DT_NODE_EXISTS(SERIAL_NODE) */
71+
72+
73+
static const struct ump_endpoint_dt_spec ump_ep_dt =
74+
UMP_ENDPOINT_DT_SPEC_GET(DT_NODELABEL(midi2));
75+
76+
static inline void handle_ump_stream(struct netmidi2_session *session,
77+
const struct midi_ump ump)
78+
{
79+
const struct ump_stream_responder_cfg responder_cfg = {
80+
.dev = session,
81+
.send = (void (*)(void *, const struct midi_ump)) netmidi2_send,
82+
.ep_spec = &ump_ep_dt,
83+
};
84+
ump_stream_respond(&responder_cfg, ump);
85+
}
86+
87+
static void netmidi2_callback(struct netmidi2_session *session,
88+
const struct midi_ump ump)
89+
{
90+
switch (UMP_MT(ump)) {
91+
case UMP_MT_MIDI1_CHANNEL_VOICE:
92+
send_external_midi1(ump);
93+
break;
94+
case UMP_MT_UMP_STREAM:
95+
handle_ump_stream(session, ump);
96+
break;
97+
}
98+
}
99+
100+
#if defined(CONFIG_NET_SAMPLE_MIDI2_AUTH_NONE)
101+
/* Simple Network MIDI 2.0 endpoint without authentication */
102+
NETMIDI2_EP_DEFINE(midi_server, ump_ep_dt.name, NULL, 0);
103+
104+
#elif defined(CONFIG_NET_SAMPLE_MIDI2_AUTH_SHARED_SECRET)
105+
/* Network MIDI 2.0 endpoint with shared secret authentication */
106+
BUILD_ASSERT(
107+
sizeof(CONFIG_NET_SAMPLE_MIDI2_SHARED_SECRET) > 1,
108+
"CONFIG_NET_SAMPLE_MIDI2_SHARED_SECRET must be not empty"
109+
);
110+
111+
NETMIDI2_EP_DEFINE_WITH_AUTH(midi_server, ump_ep_dt.name, NULL, 0,
112+
CONFIG_NET_SAMPLE_MIDI2_SHARED_SECRET);
113+
114+
#elif defined(CONFIG_NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD)
115+
/* Network MIDI 2.0 endpoint with a single user/password*/
116+
BUILD_ASSERT(
117+
sizeof(CONFIG_NET_SAMPLE_MIDI2_USERNAME) > 1,
118+
"CONFIG_NET_SAMPLE_MIDI2_USERNAME must be not empty"
119+
);
120+
BUILD_ASSERT(
121+
sizeof(CONFIG_NET_SAMPLE_MIDI2_PASSWORD) > 1,
122+
"CONFIG_NET_SAMPLE_MIDI2_PASSWORD must be not empty"
123+
);
124+
125+
NETMIDI2_EP_DEFINE_WITH_USERS(midi_server, ump_ep_dt.name, NULL, 0,
126+
{.name = CONFIG_NET_SAMPLE_MIDI2_USERNAME,
127+
.password = CONFIG_NET_SAMPLE_MIDI2_PASSWORD});
128+
129+
#endif
130+
131+
DNS_SD_REGISTER_SERVICE(midi_dns, CONFIG_NET_HOSTNAME "-" CONFIG_BOARD,
132+
"_midi2", "_udp", "local", DNS_SD_EMPTY_TXT,
133+
&midi_server.addr4.sin_port);
134+
135+
int main(void)
136+
{
137+
CONFIGURE_LED();
138+
139+
midi_server.rx_packet_cb = netmidi2_callback;
140+
netmidi2_host_ep_start(&midi_server);
141+
142+
return 0;
143+
}

subsys/net/lib/midi2/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
config NETMIDI2_HOST
55
bool "Network MIDI2 (UDP) host [EXPERIMENTAL]"
6+
select EXPERIMENTAL
67
select NET_UDP
78
select NET_SOCKETS
89
select NET_SOCKETS_SERVICE

0 commit comments

Comments
 (0)