aplaymidi: Add UMP support

By switching via the new option -u, aplaymidi can behave as a UMP
client and output UMP packets instead of legacy sequencer events.
As of now, the only supported version is 1.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2023-03-22 08:55:10 +01:00
parent 64b1d486b1
commit b399fb85a9
2 changed files with 181 additions and 41 deletions

View file

@ -31,6 +31,12 @@ Prints a list of possible output ports.
Sets the sequencer port(s) to which the events in the MIDI file(s) are Sets the sequencer port(s) to which the events in the MIDI file(s) are
sent. sent.
.TP
.I \-u, \-\-ump=version
Changes the sequencer client to the given MIDI version and outputs via
the UMP packets instead of legacy sequencer events.
As of now, the only supported version is 1.
A client can be specified by its number, its name, or a prefix of its A client can be specified by its number, its name, or a prefix of its
name. A port is specified by its number; for port 0 of a client, the name. A port is specified by its number; for port 0 of a client, the
":0" part of the port specification can be omitted. ":0" part of the port specification can be omitted.

View file

@ -30,6 +30,9 @@
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include "aconfig.h" #include "aconfig.h"
#include "version.h" #include "version.h"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
#include <alsa/ump_msg.h>
#endif
/* /*
* 31.25 kbaud, one start bit, eight data bits, two stop bits. * 31.25 kbaud, one start bit, eight data bits, two stop bits.
@ -75,6 +78,9 @@ static int file_offset; /* current offset in input file */
static int num_tracks; static int num_tracks;
static struct track *tracks; static struct track *tracks;
static int smpte_timing; static int smpte_timing;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
static int ump_mode;
#endif
/* prints an error message to stderr */ /* prints an error message to stderr */
static void errormsg(const char *msg, ...) static void errormsg(const char *msg, ...)
@ -639,8 +645,130 @@ static void handle_big_sysex(snd_seq_event_t *ev)
ev->data.ext.len = length; ev->data.ext.len = length;
} }
static int fill_legacy_event(struct event* event, snd_seq_event_t *ev)
{
ev->type = event->type;
switch (ev->type) {
case SND_SEQ_EVENT_NOTEON:
case SND_SEQ_EVENT_NOTEOFF:
case SND_SEQ_EVENT_KEYPRESS:
snd_seq_ev_set_fixed(ev);
ev->data.note.channel = event->data.d[0];
ev->data.note.note = event->data.d[1];
ev->data.note.velocity = event->data.d[2];
break;
case SND_SEQ_EVENT_CONTROLLER:
snd_seq_ev_set_fixed(ev);
ev->data.control.channel = event->data.d[0];
ev->data.control.param = event->data.d[1];
ev->data.control.value = event->data.d[2];
break;
case SND_SEQ_EVENT_PGMCHANGE:
case SND_SEQ_EVENT_CHANPRESS:
snd_seq_ev_set_fixed(ev);
ev->data.control.channel = event->data.d[0];
ev->data.control.value = event->data.d[1];
break;
case SND_SEQ_EVENT_PITCHBEND:
snd_seq_ev_set_fixed(ev);
ev->data.control.channel = event->data.d[0];
ev->data.control.value = ((event->data.d[1]) |
((event->data.d[2]) << 7)) - 0x2000;
break;
case SND_SEQ_EVENT_SYSEX:
snd_seq_ev_set_variable(ev, event->data.length, event->sysex);
handle_big_sysex(ev);
break;
default:
fatal("Invalid event type %d!", ev->type);
}
return 0;
}
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
static unsigned char to_ump_status(unsigned char ev_type)
{
switch (ev_type) {
case SND_SEQ_EVENT_NOTEON:
return SND_UMP_MSG_NOTE_ON;
case SND_SEQ_EVENT_NOTEOFF:
return SND_UMP_MSG_NOTE_OFF;
case SND_SEQ_EVENT_KEYPRESS:
return SND_UMP_MSG_POLY_PRESSURE;
case SND_SEQ_EVENT_CONTROLLER:
return SND_UMP_MSG_CONTROL_CHANGE;
case SND_SEQ_EVENT_PGMCHANGE:
return SND_UMP_MSG_PROGRAM_CHANGE;
case SND_SEQ_EVENT_CHANPRESS:
return SND_UMP_MSG_CHANNEL_PRESSURE;
case SND_SEQ_EVENT_PITCHBEND:
return SND_UMP_MSG_PITCHBEND;
default:
return 0;
}
}
static int fill_ump_event(struct event* event, snd_seq_ump_event_t *ump_ev,
const snd_seq_event_t *ev)
{
snd_ump_msg_midi1_t ump = {};
unsigned char status = to_ump_status(event->type);
memcpy(ump_ev, ev, sizeof(*ev));
if (!status)
return 0; /* handle as is */
ump.note_on.type = SND_UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE;
switch (event->type) {
case SND_SEQ_EVENT_NOTEON:
/* correct the note-on with velocity 0 to note-off;
* UMP may handle velocity 0 differently
*/
if (!ev->data.note.velocity)
status = SND_UMP_MSG_NOTE_OFF;
/* fallthrough */
case SND_SEQ_EVENT_NOTEOFF:
case SND_SEQ_EVENT_KEYPRESS:
ump.note_on.status = status;
ump.note_on.channel = event->data.d[0];
ump.note_on.note = event->data.d[1];
ump.note_on.velocity = event->data.d[2];
break;
case SND_SEQ_EVENT_CONTROLLER:
ump.control_change.status = status;
ump.control_change.channel = event->data.d[0];
ump.control_change.index = event->data.d[1];
ump.control_change.data = event->data.d[2];
break;
case SND_SEQ_EVENT_PGMCHANGE:
ump.program_change.status = status;
ump.program_change.channel = event->data.d[0];
ump.program_change.program = event->data.d[1];
break;
case SND_SEQ_EVENT_CHANPRESS:
ump.channel_pressure.status = status;
ump.channel_pressure.channel = event->data.d[0];
ump.channel_pressure.data = event->data.d[1];
break;
case SND_SEQ_EVENT_PITCHBEND:
ump.pitchbend.status = status;
ump.pitchbend.channel = event->data.d[0];
ump.pitchbend.data_msb = event->data.d[2];
ump.pitchbend.data_lsb = event->data.d[1];
break;
default:
return 0; /* handle as is */
}
snd_seq_ev_set_ump_data(ump_ev, &ump, sizeof(ump));
return 0;
}
#endif /* HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION */
static void play_midi(void) static void play_midi(void)
{ {
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
snd_seq_ump_event_t ump_ev;
#endif
snd_seq_event_t ev; snd_seq_event_t ev;
int i, max_tick, err; int i, max_tick, err;
@ -688,52 +816,28 @@ static void play_midi(void)
event_track->current_event = event->next; event_track->current_event = event->next;
/* output the event */ /* output the event */
ev.type = event->type;
ev.time.tick = event->tick; ev.time.tick = event->tick;
ev.dest = ports[event->port]; ev.dest = ports[event->port];
switch (ev.type) { if (event->type == SND_SEQ_EVENT_TEMPO) {
case SND_SEQ_EVENT_NOTEON:
case SND_SEQ_EVENT_NOTEOFF:
case SND_SEQ_EVENT_KEYPRESS:
snd_seq_ev_set_fixed(&ev);
ev.data.note.channel = event->data.d[0];
ev.data.note.note = event->data.d[1];
ev.data.note.velocity = event->data.d[2];
break;
case SND_SEQ_EVENT_CONTROLLER:
snd_seq_ev_set_fixed(&ev);
ev.data.control.channel = event->data.d[0];
ev.data.control.param = event->data.d[1];
ev.data.control.value = event->data.d[2];
break;
case SND_SEQ_EVENT_PGMCHANGE:
case SND_SEQ_EVENT_CHANPRESS:
snd_seq_ev_set_fixed(&ev);
ev.data.control.channel = event->data.d[0];
ev.data.control.value = event->data.d[1];
break;
case SND_SEQ_EVENT_PITCHBEND:
snd_seq_ev_set_fixed(&ev);
ev.data.control.channel = event->data.d[0];
ev.data.control.value =
((event->data.d[1]) |
((event->data.d[2]) << 7)) - 0x2000;
break;
case SND_SEQ_EVENT_SYSEX:
snd_seq_ev_set_variable(&ev, event->data.length,
event->sysex);
handle_big_sysex(&ev);
break;
case SND_SEQ_EVENT_TEMPO:
snd_seq_ev_set_fixed(&ev);
ev.dest.client = SND_SEQ_CLIENT_SYSTEM; ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
ev.data.queue.queue = queue; ev.data.queue.queue = queue;
ev.data.queue.param.value = event->data.tempo; ev.data.queue.param.value = event->data.tempo;
break; } else {
default: err = fill_legacy_event(event, &ev);
fatal("Invalid event type %d!", ev.type); if (err < 0)
continue;
} }
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
if (ump_mode) {
err = fill_ump_event(event, &ump_ev, &ev);
if (err < 0)
continue;
err = snd_seq_ump_event_output(seq, &ump_ev);
check_snd("output event", err);
continue;
}
#endif
/* this blocks when the output pool has been filled */ /* this blocks when the output pool has been filled */
err = snd_seq_event_output(seq, &ev); err = snd_seq_event_output(seq, &ev);
@ -851,6 +955,9 @@ static void usage(const char *argv0)
"-V, --version print current version\n" "-V, --version print current version\n"
"-l, --list list all possible output ports\n" "-l, --list list all possible output ports\n"
"-p, --port=client:port,... set port(s) to play to\n" "-p, --port=client:port,... set port(s) to play to\n"
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
"-u, --ump=version UMP output (only version=1 is supported)\n"
#endif
"-d, --delay=seconds delay after song ends\n", "-d, --delay=seconds delay after song ends\n",
argv0); argv0);
} }
@ -860,18 +967,28 @@ static void version(void)
puts("aplaymidi version " SND_UTIL_VERSION_STR); puts("aplaymidi version " SND_UTIL_VERSION_STR);
} }
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
#define OPTIONS "hVlp:d:u:"
#else
#define OPTIONS "hVlp:d:"
#endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
static const char short_options[] = "hVlp:d:"; static const char short_options[] = OPTIONS;
static const struct option long_options[] = { static const struct option long_options[] = {
{"help", 0, NULL, 'h'}, {"help", 0, NULL, 'h'},
{"version", 0, NULL, 'V'}, {"version", 0, NULL, 'V'},
{"list", 0, NULL, 'l'}, {"list", 0, NULL, 'l'},
{"port", 1, NULL, 'p'}, {"port", 1, NULL, 'p'},
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
{"ump", 1, NULL, 'u'},
#endif
{"delay", 1, NULL, 'd'}, {"delay", 1, NULL, 'd'},
{0} {0}
}; };
int c; int c, err;
int do_list = 0; int do_list = 0;
init_seq(); init_seq();
@ -894,12 +1011,29 @@ int main(int argc, char *argv[])
case 'd': case 'd':
end_delay = atoi(optarg); end_delay = atoi(optarg);
break; break;
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
case 'u':
if (strcmp(optarg, "1")) {
errormsg("Only MIDI 1.0 is supported");
return 1;
}
ump_mode = 1;
break;
#endif
default: default:
usage(argv[0]); usage(argv[0]);
return 1; return 1;
} }
} }
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
if (ump_mode) {
err = snd_seq_set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_1_0);
check_snd("set midi version", err);
}
#endif
if (do_list) { if (do_list) {
list_ports(); list_ports();
} else { } else {