test: Add an example program to create a virtual UMP Endpoint

Provide an example program to demonstrate how to create a UMP Endpoint
and Blocks, i.e. a virtual UMP device.

It's a simple filtering application that just haves the incoming note
on/off velocity and sends out to the output.  The UMP Endpoint and
Block attributes can be adjusted via command-line options.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2024-06-19 17:22:15 +02:00
parent 6167b8ce3e
commit f784035a90
2 changed files with 189 additions and 1 deletions

View file

@ -1,6 +1,6 @@
SUBDIRS=. lsb SUBDIRS=. lsb
check_PROGRAMS=control pcm pcm_min latency seq \ check_PROGRAMS=control pcm pcm_min latency seq seq-ump-example \
playmidi1 timer rawmidi midiloop \ playmidi1 timer rawmidi midiloop \
oldapi queue_timer namehint client_event_filter \ oldapi queue_timer namehint client_event_filter \
chmap audio_time user-ctl-element-set pcm-multi-thread chmap audio_time user-ctl-element-set pcm-multi-thread
@ -12,6 +12,7 @@ pcm_min_LDADD=../src/libasound.la
latency_LDADD=../src/libasound.la latency_LDADD=../src/libasound.la
latency_LDFLAGS= -lm latency_LDFLAGS= -lm
seq_LDADD=../src/libasound.la seq_LDADD=../src/libasound.la
seq_ump_example_LDADD=../src/libasound.la
playmidi1_LDADD=../src/libasound.la playmidi1_LDADD=../src/libasound.la
timer_LDADD=../src/libasound.la timer_LDADD=../src/libasound.la
rawmidi_LDADD=../src/libasound.la rawmidi_LDADD=../src/libasound.la

187
test/seq-ump-example.c Normal file
View file

@ -0,0 +1,187 @@
// An example program to create a virtual UMP Endpoint
//
// A client simply reads each UMP packet and sends to subscribers
// while the note on/off velocity is halved
#include <stdio.h>
#include <getopt.h>
#include <alsa/asoundlib.h>
#include <alsa/ump_msg.h>
/* make the note on/off velocity half for MIDI1 CVM */
static void midi1_half_note_velocity(snd_seq_ump_event_t *ev)
{
snd_ump_msg_midi1_t *midi1 = (snd_ump_msg_midi1_t *)ev->ump;
switch (snd_ump_msg_status(ev->ump)) {
case SND_UMP_MSG_NOTE_OFF:
case SND_UMP_MSG_NOTE_ON:
midi1->note_on.velocity >>= 1;
break;
}
}
/* make the note on/off velocity half for MIDI2 CVM */
static void midi2_half_note_velocity(snd_seq_ump_event_t *ev)
{
snd_ump_msg_midi2_t *midi2 = (snd_ump_msg_midi2_t *)ev->ump;
switch (snd_ump_msg_status(ev->ump)) {
case SND_UMP_MSG_NOTE_OFF:
case SND_UMP_MSG_NOTE_ON:
midi2->note_on.velocity >>= 1;
break;
}
}
static void help(void)
{
printf("seq-ump-example: Create a virtual UMP Endpoint and Blocks\n"
"\n"
"Usage: seq-ump-example [OPTIONS]\n"
"\n"
"-n,--num-blocks blocks Number of blocks (groups) to create\n"
"-m,--midi-version version MIDI protocol version (1 or 2)\n"
"-N--name UMP Endpoint name string\n"
"-P,--product name UMP Product ID string\n"
"-M,--manufacturer id UMP Manufacturer ID value (24bit)\n"
"-F,--family id UMP Family ID value (16bit)\n"
"-O,--model id UMP Model ID value (16bit)\n"
"-R,--sw-revision id UMP Software Revision ID (32bit)\n");
}
int main(int argc, char **argv)
{
int midi_version = 2;
int num_blocks = 1;
const char *name = "ACMESynth";
const char *product = "Halfmoon";
unsigned int manufacturer = 0x123456;
unsigned int family = 0x1234;
unsigned int model = 0xabcd;
unsigned int sw_revision = 0x12345678;
snd_seq_t *seq;
snd_ump_endpoint_info_t *ep;
snd_ump_block_info_t *blk;
snd_seq_ump_event_t *ev;
int i, c, err;
unsigned char tmp[4];
static const struct option long_option[] = {
{"num-blocks", required_argument, 0, 'n'},
{"midi-version", required_argument, 0, 'm'},
{"name", required_argument, 0, 'N'},
{"product", required_argument, 0, 'P'},
{"manufacturer", required_argument, 0, 'M'},
{"family", required_argument, 0, 'F'},
{"model", required_argument, 0, 'O'},
{"sw-revision", required_argument, 0, 'R'},
{0, 0, 0, 0}
};
while ((c = getopt_long(argc, argv, "n:m:N:P:M:F:O:R:",
long_option, NULL)) >= 0) {
switch (c) {
case 'n':
num_blocks = atoi(optarg);
break;
case 'm':
midi_version = atoi(optarg);
break;
case 'N':
name = optarg;
break;
case 'P':
product = optarg;
break;
case 'M':
manufacturer = strtol(optarg, NULL, 0);
break;
case 'F':
family = strtol(optarg, NULL, 0);
break;
case 'O':
model = strtol(optarg, NULL, 0);
break;
case 'R':
sw_revision = strtol(optarg, NULL, 0);
break;
default:
help();
return 1;
}
}
err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
if (err < 0) {
fprintf(stderr, "failed to open sequencer: %d\n", err);
return 1;
}
snd_ump_endpoint_info_alloca(&ep);
snd_ump_endpoint_info_set_name(ep, name);
snd_ump_endpoint_info_set_product_id(ep, product);
if (midi_version == 1) {
snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI1);
snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI1);
} else {
snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI2);
snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI2);
}
snd_ump_endpoint_info_set_num_blocks(ep, num_blocks);
snd_ump_endpoint_info_set_manufacturer_id(ep, manufacturer);
snd_ump_endpoint_info_set_family_id(ep, family);
snd_ump_endpoint_info_set_model_id(ep, model);
for (i = 0; i < 4; i++)
tmp[i] = (sw_revision >> ((3 - i) * 8)) & 0xff;
snd_ump_endpoint_info_set_sw_revision(ep, tmp);
err = snd_seq_create_ump_endpoint(seq, ep, num_blocks);
if (err < 0) {
fprintf(stderr, "failed to set UMP EP info: %d\n", err);
return 1;
}
snd_ump_block_info_alloca(&blk);
for (i = 0; i < num_blocks; i++) {
char blkname[32];
sprintf(blkname, "Filter %d", i + 1);
snd_ump_block_info_set_name(blk, blkname);
snd_ump_block_info_set_direction(blk, SND_UMP_DIR_BIDIRECTION);
snd_ump_block_info_set_first_group(blk, i);
snd_ump_block_info_set_num_groups(blk, 1);
snd_ump_block_info_set_ui_hint(blk, SND_UMP_BLOCK_UI_HINT_BOTH);
err = snd_seq_create_ump_block(seq, i, blk);
if (err < 0) {
fprintf(stderr, "failed to set UMP block info %d: %d\n",
i, err);
return 1;
}
}
/* halve the incoming note-on / off velocity and pass through
* to subscribers
*/
while (snd_seq_ump_event_input(seq, &ev) >= 0) {
if (!snd_seq_ev_is_ump(ev))
continue;
switch (snd_ump_msg_type(ev->ump)) {
case SND_UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
midi1_half_note_velocity(ev);
break;
case SND_UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
midi2_half_note_velocity(ev);
break;
}
snd_seq_ev_set_subs(ev);
snd_seq_ev_set_direct(ev);
snd_seq_ump_event_output(seq, ev);
snd_seq_drain_output(seq);
}
return 0;
}