Logo Search packages:      
Sourcecode: alsa-utils version File versions

aplaymidi.c

/*
 * aplaymidi.c - play Standard MIDI Files to sequencer port(s)
 *
 * Copyright (c) 2004 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"

/*
 * 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;
      snd_seq_client_info_t *info;

      /* 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 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 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);
                  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) {
                  /* 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)
{
      fprintf(stderr,
            "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)
{
      fputs("aplaymidi version " SND_UTIL_VERSION_STR "\n", stderr);
}

int main(int argc, char *argv[])
{
      static char short_options[] = "hVlp:d:";
      static 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;
}

Generated by  Doxygen 1.6.0   Back to index