mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-22 23:26:30 +01:00
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:
parent
64b1d486b1
commit
b399fb85a9
2 changed files with 181 additions and 41 deletions
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue