Skip to content

Commit ddb2bc7

Browse files
committed
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 cc0e290 commit ddb2bc7

File tree

8 files changed

+335
-0
lines changed

8 files changed

+335
-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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
default "the-secret-key"
27+
28+
if NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD
29+
30+
comment "First user"
31+
config NET_SAMPLE_MIDI2_USERNAME1
32+
string "Username 1"
33+
default "root"
34+
35+
config NET_SAMPLE_MIDI2_PASSWORD1
36+
string "Password 1"
37+
default "root"
38+
39+
comment "Second user"
40+
config NET_SAMPLE_MIDI2_USERNAME2
41+
string "Username 2"
42+
default "user"
43+
44+
config NET_SAMPLE_MIDI2_PASSWORD2
45+
string "Password 2"
46+
default "password"
47+
48+
endif
49+
50+
endmenu
51+
52+
source "Kconfig.zephyr"

samples/net/midi2/README.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 MIDI2.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+
40+
Furthermore, this sample pairs well with the :ref:`olimex_shield_midi`,
41+
that conveniently defines the device tree nodes for the external MIDI OUT
42+
and its led. For example, using this shield on the ST Nucleo F429zi:
43+
44+
.. zephyr-app-commands::
45+
:zephyr-app: samples/net/midi2
46+
:board: nucleo_f429zi
47+
:shield: olimex_shield_midi
48+
:goals: build flash
49+
50+
Using authentication
51+
********************
52+
53+
To enable shared secret authentication to connect to the UMP endpoint host,
54+
enable :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_AUTH_SHARED_SECRET`.
55+
By default, the shared access key is ``the-secret-key``. This can be changed
56+
with :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_SHARED_SECRET`
57+
58+
To enable user/password authentication instead, enable
59+
:kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_AUTH_USER_PASSWORD`. By default, this defines
60+
two users ``root:root`` and ``user:password``, but this can be further edited
61+
with
62+
63+
* :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_USERNAME1` and :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_PASSWORD1`
64+
* :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_USERNAME2` and :kconfig:option:`CONFIG_NET_SAMPLE_MIDI2_PASSWORD2`
65+
66+
.. _pymidi2:
67+
https://github.com/titouanc/pymidi2

samples/net/midi2/TODO.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
* [x] Generate random nonce for authentication
2+
* [ ] Handle IPv6 / IPv4
3+
* [x] Use net_buf pool where possible
4+
* [x] Proper logging configuration
5+
* [x] Proper product instance ID etc... in endpoint invitation
6+
* [ ] Add tests
7+
* [ ] UMP stream responder (self-contained)
8+
* [ ] UDP-MIDI (?)

samples/net/midi2/app.overlay

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

samples/net/midi2/prj.conf

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
CONFIG_LOG=y
2+
CONFIG_LOG_MODE_IMMEDIATE=y
3+
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192
4+
5+
CONFIG_ZVFS_POLL_MAX=4
6+
7+
CONFIG_NETWORKING=y
8+
CONFIG_NET_LOG=y
9+
CONFIG_NET_STATISTICS=y
10+
CONFIG_NET_SOCKETS_SERVICE_STACK_SIZE=2048
11+
12+
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
13+
CONFIG_NET_CONFIG_NEED_IPV4=y
14+
CONFIG_NET_CONFIG_SETTINGS=y
15+
16+
CONFIG_NET_HOSTNAME_ENABLE=y
17+
CONFIG_MDNS_RESPONDER=y
18+
CONFIG_DNS_SD=y
19+
20+
CONFIG_NETMIDI2_HOST=y
21+
CONFIG_MIDI2_UMP_STREAM_RESPONDER=y

samples/net/midi2/sample.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
common:
2+
harness: net
3+
tags:
4+
- net
5+
sample:
6+
description: Demonstrates usage of the Network MIDI2.0 host endpoint
7+
name: MIDI2.0 over the network (udp)

samples/net/midi2/src/main.c

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

0 commit comments

Comments
 (0)