diff options
Diffstat (limited to 'alsa-utils/seq/aplaymidi/aplaymidi.c')
-rw-r--r-- | alsa-utils/seq/aplaymidi/aplaymidi.c | 927 |
1 files changed, 0 insertions, 927 deletions
diff --git a/alsa-utils/seq/aplaymidi/aplaymidi.c b/alsa-utils/seq/aplaymidi/aplaymidi.c deleted file mode 100644 index 5ed7bab..0000000 --- a/alsa-utils/seq/aplaymidi/aplaymidi.c +++ /dev/null @@ -1,927 +0,0 @@ -/* - * aplaymidi.c - play Standard MIDI Files to sequencer port(s) - * - * Copyright (c) 2004-2006 Clemens Ladisch <clemens@ladisch.de> - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/* TODO: sequencer queue timer selection */ - -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <string.h> -#include <getopt.h> -#include <unistd.h> -#include <alsa/asoundlib.h> -#include "aconfig.h" -#include "version.h" - -#define MIDI_BYTES_PER_SEC 3125 - -/* - * A MIDI event after being parsed/loaded from the file. - * There could be made a case for using snd_seq_event_t instead. - */ -struct event { - struct event *next; /* linked list */ - - unsigned char type; /* SND_SEQ_EVENT_xxx */ - unsigned char port; /* port index */ - unsigned int tick; - union { - unsigned char d[3]; /* channel and data bytes */ - int tempo; - unsigned int length; /* length of sysex data */ - } data; - unsigned char sysex[0]; -}; - -struct track { - struct event *first_event; /* list of all events in this track */ - int end_tick; /* length of this track */ - - struct event *current_event; /* used while loading and playing */ -}; - -static snd_seq_t *seq; -static int client; -static int port_count; -static snd_seq_addr_t *ports; -static int queue; -static int end_delay = 2; -static const char *file_name; -static FILE *file; -static int file_offset; /* current offset in input file */ -static int num_tracks; -static struct track *tracks; -static int smpte_timing; - -/* prints an error message to stderr */ -static void errormsg(const char *msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vfprintf(stderr, msg, ap); - va_end(ap); - fputc('\n', stderr); -} - -/* prints an error message to stderr, and dies */ -static void fatal(const char *msg, ...) -{ - va_list ap; - - va_start(ap, msg); - vfprintf(stderr, msg, ap); - va_end(ap); - fputc('\n', stderr); - exit(EXIT_FAILURE); -} - -/* memory allocation error handling */ -static void check_mem(void *p) -{ - if (!p) - fatal("Out of memory"); -} - -/* error handling for ALSA functions */ -static void check_snd(const char *operation, int err) -{ - if (err < 0) - fatal("Cannot %s - %s", operation, snd_strerror(err)); -} - -static void init_seq(void) -{ - int err; - - /* open sequencer */ - err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); - check_snd("open sequencer", err); - - /* set our name (otherwise it's "Client-xxx") */ - err = snd_seq_set_client_name(seq, "aplaymidi"); - check_snd("set client name", err); - - /* find out who we actually are */ - client = snd_seq_client_id(seq); - check_snd("get client id", client); -} - -/* parses one or more port addresses from the string */ -static void parse_ports(const char *arg) -{ - char *buf, *s, *port_name; - int err; - - /* make a copy of the string because we're going to modify it */ - buf = strdup(arg); - check_mem(buf); - - for (port_name = s = buf; s; port_name = s + 1) { - /* Assume that ports are separated by commas. We don't use - * spaces because those are valid in client names. */ - s = strchr(port_name, ','); - if (s) - *s = '\0'; - - ++port_count; - ports = realloc(ports, port_count * sizeof(snd_seq_addr_t)); - check_mem(ports); - - err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name); - if (err < 0) - fatal("Invalid port %s - %s", port_name, snd_strerror(err)); - } - - free(buf); -} - -static void create_source_port(void) -{ - snd_seq_port_info_t *pinfo; - int err; - - snd_seq_port_info_alloca(&pinfo); - - /* the first created port is 0 anyway, but let's make sure ... */ - snd_seq_port_info_set_port(pinfo, 0); - snd_seq_port_info_set_port_specified(pinfo, 1); - - snd_seq_port_info_set_name(pinfo, "aplaymidi"); - - snd_seq_port_info_set_capability(pinfo, 0); /* sic */ - snd_seq_port_info_set_type(pinfo, - SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION); - - err = snd_seq_create_port(seq, pinfo); - check_snd("create port", err); -} - -static void create_queue(void) -{ - queue = snd_seq_alloc_named_queue(seq, "aplaymidi"); - check_snd("create queue", queue); - /* the queue is now locked, which is just fine */ -} - -static void connect_ports(void) -{ - int i, err; - - /* - * We send MIDI events with explicit destination addresses, so we don't - * need any connections to the playback ports. But we connect to those - * anyway to force any underlying RawMIDI ports to remain open while - * we're playing - otherwise, ALSA would reset the port after every - * event. - */ - for (i = 0; i < port_count; ++i) { - err = snd_seq_connect_to(seq, 0, ports[i].client, ports[i].port); - if (err < 0) - fatal("Cannot connect to port %d:%d - %s", - ports[i].client, ports[i].port, snd_strerror(err)); - } -} - -static int read_byte(void) -{ - ++file_offset; - return getc(file); -} - -/* reads a little-endian 32-bit integer */ -static int read_32_le(void) -{ - int value; - value = read_byte(); - value |= read_byte() << 8; - value |= read_byte() << 16; - value |= read_byte() << 24; - return !feof(file) ? value : -1; -} - -/* reads a 4-character identifier */ -static int read_id(void) -{ - return read_32_le(); -} -#define MAKE_ID(c1, c2, c3, c4) ((c1) | ((c2) << 8) | ((c3) << 16) | ((c4) << 24)) - -/* reads a fixed-size big-endian number */ -static int read_int(int bytes) -{ - int c, value = 0; - - do { - c = read_byte(); - if (c == EOF) - return -1; - value = (value << 8) | c; - } while (--bytes); - return value; -} - -/* reads a variable-length number */ -static int read_var(void) -{ - int value, c; - - c = read_byte(); - value = c & 0x7f; - if (c & 0x80) { - c = read_byte(); - value = (value << 7) | (c & 0x7f); - if (c & 0x80) { - c = read_byte(); - value = (value << 7) | (c & 0x7f); - if (c & 0x80) { - c = read_byte(); - value = (value << 7) | c; - if (c & 0x80) - return -1; - } - } - } - return !feof(file) ? value : -1; -} - -/* allocates a new event */ -static struct event *new_event(struct track *track, int sysex_length) -{ - struct event *event; - - event = malloc(sizeof(struct event) + sysex_length); - check_mem(event); - - event->next = NULL; - - /* append at the end of the track's linked list */ - if (track->current_event) - track->current_event->next = event; - else - track->first_event = event; - track->current_event = event; - - return event; -} - -static void skip(int bytes) -{ - while (bytes > 0) - read_byte(), --bytes; -} - -/* reads one complete track from the file */ -static int read_track(struct track *track, int track_end) -{ - int tick = 0; - unsigned char last_cmd = 0; - unsigned char port = 0; - - /* the current file position is after the track ID and length */ - while (file_offset < track_end) { - unsigned char cmd; - struct event *event; - int delta_ticks, len, c; - - delta_ticks = read_var(); - if (delta_ticks < 0) - break; - tick += delta_ticks; - - c = read_byte(); - if (c < 0) - break; - - if (c & 0x80) { - /* have command */ - cmd = c; - if (cmd < 0xf0) - last_cmd = cmd; - } else { - /* running status */ - ungetc(c, file); - file_offset--; - cmd = last_cmd; - if (!cmd) - goto _error; - } - - switch (cmd >> 4) { - /* maps SMF events to ALSA sequencer events */ - static const unsigned char cmd_type[] = { - [0x8] = SND_SEQ_EVENT_NOTEOFF, - [0x9] = SND_SEQ_EVENT_NOTEON, - [0xa] = SND_SEQ_EVENT_KEYPRESS, - [0xb] = SND_SEQ_EVENT_CONTROLLER, - [0xc] = SND_SEQ_EVENT_PGMCHANGE, - [0xd] = SND_SEQ_EVENT_CHANPRESS, - [0xe] = SND_SEQ_EVENT_PITCHBEND - }; - - case 0x8: /* channel msg with 2 parameter bytes */ - case 0x9: - case 0xa: - case 0xb: - case 0xe: - event = new_event(track, 0); - event->type = cmd_type[cmd >> 4]; - event->port = port; - event->tick = tick; - event->data.d[0] = cmd & 0x0f; - event->data.d[1] = read_byte() & 0x7f; - event->data.d[2] = read_byte() & 0x7f; - break; - - case 0xc: /* channel msg with 1 parameter byte */ - case 0xd: - event = new_event(track, 0); - event->type = cmd_type[cmd >> 4]; - event->port = port; - event->tick = tick; - event->data.d[0] = cmd & 0x0f; - event->data.d[1] = read_byte() & 0x7f; - break; - - case 0xf: - switch (cmd) { - case 0xf0: /* sysex */ - case 0xf7: /* continued sysex, or escaped commands */ - len = read_var(); - if (len < 0) - goto _error; - if (cmd == 0xf0) - ++len; - event = new_event(track, len); - event->type = SND_SEQ_EVENT_SYSEX; - event->port = port; - event->tick = tick; - event->data.length = len; - if (cmd == 0xf0) { - event->sysex[0] = 0xf0; - c = 1; - } else { - c = 0; - } - for (; c < len; ++c) - event->sysex[c] = read_byte(); - break; - - case 0xff: /* meta event */ - c = read_byte(); - len = read_var(); - if (len < 0) - goto _error; - - switch (c) { - case 0x21: /* port number */ - if (len < 1) - goto _error; - port = read_byte() % port_count; - skip(len - 1); - break; - - case 0x2f: /* end of track */ - track->end_tick = tick; - skip(track_end - file_offset); - return 1; - - case 0x51: /* tempo */ - if (len < 3) - goto _error; - if (smpte_timing) { - /* SMPTE timing doesn't change */ - skip(len); - } else { - event = new_event(track, 0); - event->type = SND_SEQ_EVENT_TEMPO; - event->port = port; - event->tick = tick; - event->data.tempo = read_byte() << 16; - event->data.tempo |= read_byte() << 8; - event->data.tempo |= read_byte(); - skip(len - 3); - } - break; - - default: /* ignore all other meta events */ - skip(len); - break; - } - break; - - default: /* invalid Fx command */ - goto _error; - } - break; - - default: /* cannot happen */ - goto _error; - } - } -_error: - errormsg("%s: invalid MIDI data (offset %#x)", file_name, file_offset); - return 0; -} - -/* reads an entire MIDI file */ -static int read_smf(void) -{ - int header_len, type, time_division, i, err; - snd_seq_queue_tempo_t *queue_tempo; - - /* the curren position is immediately after the "MThd" id */ - header_len = read_int(4); - if (header_len < 6) { -invalid_format: - errormsg("%s: invalid file format", file_name); - return 0; - } - - type = read_int(2); - if (type != 0 && type != 1) { - errormsg("%s: type %d format is not supported", file_name, type); - return 0; - } - - num_tracks = read_int(2); - if (num_tracks < 1 || num_tracks > 1000) { - errormsg("%s: invalid number of tracks (%d)", file_name, num_tracks); - num_tracks = 0; - return 0; - } - tracks = calloc(num_tracks, sizeof(struct track)); - if (!tracks) { - errormsg("out of memory"); - num_tracks = 0; - return 0; - } - - time_division = read_int(2); - if (time_division < 0) - goto invalid_format; - - /* interpret and set tempo */ - snd_seq_queue_tempo_alloca(&queue_tempo); - smpte_timing = !!(time_division & 0x8000); - if (!smpte_timing) { - /* time_division is ticks per quarter */ - snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); /* default: 120 bpm */ - snd_seq_queue_tempo_set_ppq(queue_tempo, time_division); - } else { - /* upper byte is negative frames per second */ - i = 0x80 - ((time_division >> 8) & 0x7f); - /* lower byte is ticks per frame */ - time_division &= 0xff; - /* now pretend that we have quarter-note based timing */ - switch (i) { - case 24: - snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); - snd_seq_queue_tempo_set_ppq(queue_tempo, 12 * time_division); - break; - case 25: - snd_seq_queue_tempo_set_tempo(queue_tempo, 400000); - snd_seq_queue_tempo_set_ppq(queue_tempo, 10 * time_division); - break; - case 29: /* 30 drop-frame */ - snd_seq_queue_tempo_set_tempo(queue_tempo, 100000000); - snd_seq_queue_tempo_set_ppq(queue_tempo, 2997 * time_division); - break; - case 30: - snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); - snd_seq_queue_tempo_set_ppq(queue_tempo, 15 * time_division); - break; - default: - errormsg("%s: invalid number of SMPTE frames per second (%d)", - file_name, i); - return 0; - } - } - err = snd_seq_set_queue_tempo(seq, queue, queue_tempo); - if (err < 0) { - errormsg("Cannot set queue tempo (%u/%i)", - snd_seq_queue_tempo_get_tempo(queue_tempo), - snd_seq_queue_tempo_get_ppq(queue_tempo)); - return 0; - } - - /* read tracks */ - for (i = 0; i < num_tracks; ++i) { - int len; - - /* search for MTrk chunk */ - for (;;) { - int id = read_id(); - len = read_int(4); - if (feof(file)) { - errormsg("%s: unexpected end of file", file_name); - return 0; - } - if (len < 0 || len >= 0x10000000) { - errormsg("%s: invalid chunk length %d", file_name, len); - return 0; - } - if (id == MAKE_ID('M', 'T', 'r', 'k')) - break; - skip(len); - } - if (!read_track(&tracks[i], file_offset + len)) - return 0; - } - return 1; -} - -static int read_riff(void) -{ - /* skip file length */ - read_byte(); - read_byte(); - read_byte(); - read_byte(); - - /* check file type ("RMID" = RIFF MIDI) */ - if (read_id() != MAKE_ID('R', 'M', 'I', 'D')) { -invalid_format: - errormsg("%s: invalid file format", file_name); - return 0; - } - /* search for "data" chunk */ - for (;;) { - int id = read_id(); - int len = read_32_le(); - if (feof(file)) { -data_not_found: - errormsg("%s: data chunk not found", file_name); - return 0; - } - if (id == MAKE_ID('d', 'a', 't', 'a')) - break; - if (len < 0) - goto data_not_found; - skip((len + 1) & ~1); - } - /* the "data" chunk must contain data in SMF format */ - if (read_id() != MAKE_ID('M', 'T', 'h', 'd')) - goto invalid_format; - return read_smf(); -} - -static void cleanup_file_data(void) -{ - int i; - struct event *event; - - for (i = 0; i < num_tracks; ++i) { - event = tracks[i].first_event; - while (event) { - struct event *next = event->next; - free(event); - event = next; - } - } - num_tracks = 0; - free(tracks); - tracks = NULL; -} - -static void handle_big_sysex(snd_seq_event_t *ev) -{ - unsigned int length; - ssize_t event_size; - int err; - - length = ev->data.ext.len; - if (length > MIDI_BYTES_PER_SEC) - ev->data.ext.len = MIDI_BYTES_PER_SEC; - event_size = snd_seq_event_length(ev); - if (event_size + 1 > snd_seq_get_output_buffer_size(seq)) { - err = snd_seq_drain_output(seq); - check_snd("drain output", err); - err = snd_seq_set_output_buffer_size(seq, event_size + 1); - check_snd("set output buffer size", err); - } - while (length > MIDI_BYTES_PER_SEC) { - err = snd_seq_event_output(seq, ev); - check_snd("output event", err); - err = snd_seq_drain_output(seq); - check_snd("drain output", err); - err = snd_seq_sync_output_queue(seq); - check_snd("sync output", err); - if (sleep(1)) - fatal("aborted"); - ev->data.ext.ptr += MIDI_BYTES_PER_SEC; - length -= MIDI_BYTES_PER_SEC; - } - ev->data.ext.len = length; -} - -static void play_midi(void) -{ - snd_seq_event_t ev; - int i, max_tick, err; - - /* calculate length of the entire file */ - max_tick = -1; - for (i = 0; i < num_tracks; ++i) { - if (tracks[i].end_tick > max_tick) - max_tick = tracks[i].end_tick; - } - - /* initialize current position in each track */ - for (i = 0; i < num_tracks; ++i) - tracks[i].current_event = tracks[i].first_event; - - /* common settings for all our events */ - snd_seq_ev_clear(&ev); - ev.queue = queue; - ev.source.port = 0; - ev.flags = SND_SEQ_TIME_STAMP_TICK; - - err = snd_seq_start_queue(seq, queue, NULL); - check_snd("start queue", err); - /* The queue won't be started until the START_QUEUE event is - * actually drained to the kernel, which is exactly what we want. */ - - for (;;) { - struct event* event = NULL; - struct track* event_track = NULL; - int i, min_tick = max_tick + 1; - - /* search next event */ - for (i = 0; i < num_tracks; ++i) { - struct track *track = &tracks[i]; - struct event *e2 = track->current_event; - if (e2 && e2->tick < min_tick) { - min_tick = e2->tick; - event = e2; - event_track = track; - } - } - if (!event) - break; /* end of song reached */ - - /* advance pointer to next event */ - event_track->current_event = event->next; - - /* output the event */ - ev.type = event->type; - ev.time.tick = event->tick; - ev.dest = ports[event->port]; - 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; - case SND_SEQ_EVENT_TEMPO: - snd_seq_ev_set_fixed(&ev); - ev.dest.client = SND_SEQ_CLIENT_SYSTEM; - ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; - ev.data.queue.queue = queue; - ev.data.queue.param.value = event->data.tempo; - break; - default: - fatal("Invalid event type %d!", ev.type); - } - - /* this blocks when the output pool has been filled */ - err = snd_seq_event_output(seq, &ev); - check_snd("output event", err); - } - - /* schedule queue stop at end of song */ - snd_seq_ev_set_fixed(&ev); - ev.type = SND_SEQ_EVENT_STOP; - ev.time.tick = max_tick; - ev.dest.client = SND_SEQ_CLIENT_SYSTEM; - ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; - ev.data.queue.queue = queue; - err = snd_seq_event_output(seq, &ev); - check_snd("output event", err); - - /* make sure that the sequencer sees all our events */ - err = snd_seq_drain_output(seq); - check_snd("drain output", err); - - /* - * There are three possibilities how to wait until all events have - * been played: - * 1) send an event back to us (like pmidi does), and wait for it; - * 2) wait for the EVENT_STOP notification for our queue which is sent - * by the system timer port (this would require a subscription); - * 3) wait until the output pool is empty. - * The last is the simplest. - */ - err = snd_seq_sync_output_queue(seq); - check_snd("sync output", err); - - /* give the last notes time to die away */ - if (end_delay > 0) - sleep(end_delay); -} - -static void play_file(void) -{ - int ok; - - if (!strcmp(file_name, "-")) - file = stdin; - else - file = fopen(file_name, "rb"); - if (!file) { - errormsg("Cannot open %s - %s", file_name, strerror(errno)); - return; - } - - file_offset = 0; - ok = 0; - - switch (read_id()) { - case MAKE_ID('M', 'T', 'h', 'd'): - ok = read_smf(); - break; - case MAKE_ID('R', 'I', 'F', 'F'): - ok = read_riff(); - break; - default: - errormsg("%s is not a Standard MIDI File", file_name); - break; - } - - if (file != stdin) - fclose(file); - - if (ok) - play_midi(); - - cleanup_file_data(); -} - -static void list_ports(void) -{ - snd_seq_client_info_t *cinfo; - snd_seq_port_info_t *pinfo; - - snd_seq_client_info_alloca(&cinfo); - snd_seq_port_info_alloca(&pinfo); - - puts(" Port Client name Port name"); - - snd_seq_client_info_set_client(cinfo, -1); - while (snd_seq_query_next_client(seq, cinfo) >= 0) { - int client = snd_seq_client_info_get_client(cinfo); - - snd_seq_port_info_set_client(pinfo, client); - snd_seq_port_info_set_port(pinfo, -1); - while (snd_seq_query_next_port(seq, pinfo) >= 0) { - /* port must understand MIDI messages */ - if (!(snd_seq_port_info_get_type(pinfo) - & SND_SEQ_PORT_TYPE_MIDI_GENERIC)) - continue; - /* we need both WRITE and SUBS_WRITE */ - if ((snd_seq_port_info_get_capability(pinfo) - & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) - != (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)) - continue; - printf("%3d:%-3d %-32.32s %s\n", - snd_seq_port_info_get_client(pinfo), - snd_seq_port_info_get_port(pinfo), - snd_seq_client_info_get_name(cinfo), - snd_seq_port_info_get_name(pinfo)); - } - } -} - -static void usage(const char *argv0) -{ - printf( - "Usage: %s -p client:port[,...] [-d delay] midifile ...\n" - "-h, --help this help\n" - "-V, --version print current version\n" - "-l, --list list all possible output ports\n" - "-p, --port=client:port,... set port(s) to play to\n" - "-d, --delay=seconds delay after song ends\n", - argv0); -} - -static void version(void) -{ - puts("aplaymidi version " SND_UTIL_VERSION_STR); -} - -int main(int argc, char *argv[]) -{ - static const char short_options[] = "hVlp:d:"; - static const struct option long_options[] = { - {"help", 0, NULL, 'h'}, - {"version", 0, NULL, 'V'}, - {"list", 0, NULL, 'l'}, - {"port", 1, NULL, 'p'}, - {"delay", 1, NULL, 'd'}, - {} - }; - int c; - int do_list = 0; - - init_seq(); - - while ((c = getopt_long(argc, argv, short_options, - long_options, NULL)) != -1) { - switch (c) { - case 'h': - usage(argv[0]); - return 0; - case 'V': - version(); - return 0; - case 'l': - do_list = 1; - break; - case 'p': - parse_ports(optarg); - break; - case 'd': - end_delay = atoi(optarg); - break; - default: - usage(argv[0]); - return 1; - } - } - - if (do_list) { - list_ports(); - } else { - if (port_count < 1) { - /* use env var for compatibility with pmidi */ - const char *ports_str = getenv("ALSA_OUTPUT_PORTS"); - if (ports_str) - parse_ports(ports_str); - if (port_count < 1) { - errormsg("Please specify at least one port with --port."); - return 1; - } - } - if (optind >= argc) { - errormsg("Please specify a file to play."); - return 1; - } - - create_source_port(); - create_queue(); - connect_ports(); - - for (; optind < argc; ++optind) { - file_name = argv[optind]; - play_file(); - } - } - snd_seq_close(seq); - return 0; -} |