diff mbox series

[alsa-lib,3/4] test: Add an example program to create a virtual UMP Endpoint

Message ID 20240619152855.6809-4-tiwai@suse.de
State New
Headers show
Series Add API helper functions for creating UMP Endpoint and Blocks | expand

Commit Message

Takashi Iwai June 19, 2024, 3:28 p.m. UTC
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>
---
 test/Makefile.am       |   3 +-
 test/seq-ump-example.c | 187 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 189 insertions(+), 1 deletion(-)
 create mode 100644 test/seq-ump-example.c
diff mbox series

Patch

diff --git a/test/Makefile.am b/test/Makefile.am
index 99c2c4ff9f06..635fa39bb7e3 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,6 +1,6 @@ 
 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 \
 	       oldapi queue_timer namehint client_event_filter \
 	       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_LDFLAGS= -lm
 seq_LDADD=../src/libasound.la
+seq_ump_example_LDADD=../src/libasound.la
 playmidi1_LDADD=../src/libasound.la
 timer_LDADD=../src/libasound.la
 rawmidi_LDADD=../src/libasound.la
diff --git a/test/seq-ump-example.c b/test/seq-ump-example.c
new file mode 100644
index 000000000000..7f6286827f36
--- /dev/null
+++ b/test/seq-ump-example.c
@@ -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;
+}