Logo Search packages:      
Sourcecode: alsa-lib version File versions  Download package

playmidi1.c

/*
 *   MIDI file player for ALSA sequencer 
 *   (type 0 only!, the library that is used doesn't support merging of tracks)
 *
 *   Copyright (c) 1998 by Frank van de Pol <F.K.W.van.de.Pol@inter.nl.net>
 *
 *   Modified so that this uses alsa-lib
 *   1999 Jan. by Isaku Yamahata <yamahata@kusm.kyoto-u.ac.jp>
 *
 *   19990604     Takashi Iwai <iwai@ww.uni-erlangen.de>
 *    - use blocking mode
 *    - fix tempo event bug
 *    - add command line options
 *
 *   19990827     Takashi Iwai <iwai@ww.uni-erlangen.de>
 *    - use snd_seq_alloc_queue()
 *
 *   19990916     Takashi Iwai <iwai@ww.uni-erlangen.de>
 *    - use middle-level sequencer routines and macros
 *
 *   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
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include "midifile.h"         /* SMF library header */
#include "midifile.c"         /* SMF library code */

#include "../include/asoundlib.h"

/* send the real-time time stamps (instead of midi ticks) to the ALSA sequencer */
static int use_realtime = 0;

/* control the event buffering by using a blocking mode */
static int use_blocking_mode = 1;

/* default destination queue, client and port numbers */
#define DEST_CLIENT_NUMBER    65
#define DEST_PORT_NUMBER      0

/* event pool size */
#define WRITE_POOL_SIZE       200
#define WRITE_POOL_SPACE      10
#define READ_POOL_SIZE        10    /* we need to read the pool only for echoing */

static FILE *F;
static snd_seq_t *seq_handle = NULL;
static int ppq = 96;
static int slave_ppq = 96;

static double local_secs = 0;
static int local_ticks = 0;
static int local_tempo = 500000;

static int dest_queue = -1;
static int shared_queue = 0;
static int tick_offset = 0;
static int dest_client = DEST_CLIENT_NUMBER;
static int dest_port = DEST_PORT_NUMBER;
static int my_port = 0;

static int verbose = 0;
static int slave   = 0;       /* allow external sync */

#define VERB_INFO 1
#define VERB_MUCH 2
#define VERB_EVENT      3

static void alsa_start_timer(void);
static void alsa_stop_timer(void);
static void wait_start(void);


static inline double tick2time_dbl(int tick)
{
      return local_secs + ((double) (tick - local_ticks) * (double) local_tempo * 1.0E-6 / (double) ppq);
}

static void tick2time(snd_seq_real_time_t * tm, int tick)
{
      double secs = tick2time_dbl(tick);
      tm->tv_sec = secs;
      tm->tv_nsec = (secs - tm->tv_sec) * 1.0E9;
}

static void write_ev(snd_seq_event_t *ev)
{
      int rc;

      if (use_blocking_mode) {
            rc = snd_seq_event_output(seq_handle, ev);
            if (rc < 0) {
                  printf("written = %i (%s)\n", rc, snd_strerror(rc));
                  exit(1);
            }
            return;
      }
      while ((rc = snd_seq_event_output(seq_handle, ev)) < 0) {
            int npfds = snd_seq_poll_descriptors_count(seq_handle, POLLOUT);
            struct pollfd *pfds = alloca(sizeof(*pfds) * npfds);
            snd_seq_poll_descriptors(seq_handle, pfds, npfds, POLLOUT);
            if ((rc = poll(pfds, npfds, -1)) < 0) {
                  printf("poll error = %i (%s)\n", rc, snd_strerror(errno));
                  exit(1);
            }
      }
}

/* read the byte */
static int mygetc(void)
{
      return getc(F);
}

/* print out the text */
static void mytext(int type ATTRIBUTE_UNUSED, int leng, char *msg)
{
      char *p;
      char *ep = msg + leng;

      if (verbose >= VERB_INFO) {
            for (p = msg; p < ep; p++)
                  putchar(isprint(*p) ? *p : '?');
            putchar('\n');
      }
}

static void do_header(int format, int ntracks, int division)
{
      snd_seq_queue_tempo_t *tempo;

      if (verbose >= VERB_INFO)
            printf("smf format %d, %d tracks, %d ppq\n", format, ntracks, division);
      ppq = division;

      if (format != 0 || ntracks != 1) {
            printf("This player does not support merging of tracks.\n");
            if (! shared_queue)
                  alsa_stop_timer();
            exit(1);
      }
      /* set the ppq */
      snd_seq_queue_tempo_alloca(&tempo);
      /* ppq must be set before starting the timer */
      if (snd_seq_get_queue_tempo(seq_handle, dest_queue, tempo) < 0) {
            perror("get_queue_tempo");
            exit(1);
      }
      if ((slave_ppq = snd_seq_queue_tempo_get_ppq(tempo)) != ppq) {
            snd_seq_queue_tempo_set_ppq(tempo, ppq);
            if (snd_seq_set_queue_tempo(seq_handle, dest_queue, tempo) < 0) {
                  perror("set_queue_tempo");
                  if (!slave && !shared_queue)
                        exit(1);
                  else
                        printf("different PPQ %d in SMF from queue PPQ %d\n", ppq, slave_ppq);
            } else
                  slave_ppq = ppq;
            if (verbose >= VERB_INFO)
                  printf("ALSA Timer updated, PPQ = %d\n", snd_seq_queue_tempo_get_ppq(tempo));
      }

      /* start playing... */
      if (slave) {
            if (verbose >= VERB_INFO)
                  printf("Wait till timer starts...\n");    
            wait_start();
            if (verbose >= VERB_INFO)
                  printf("Go!\n");  
      } else if (shared_queue) {
            snd_seq_queue_status_t *stat;
            snd_seq_queue_status_alloca(&stat);
            snd_seq_get_queue_status(seq_handle, dest_queue, stat);
            tick_offset = snd_seq_queue_status_get_tick_time(stat);
            fprintf(stderr, "tick offset = %d\n", tick_offset);
      } else {
            alsa_start_timer();
            tick_offset = 0;
      }
}

/* fill the event time */
static void set_event_time(snd_seq_event_t *ev, unsigned int currtime)
{
      if (use_realtime) {
            snd_seq_real_time_t rtime;
            if (ppq != slave_ppq)
                  currtime = (currtime * slave_ppq) / ppq;
            tick2time(&rtime, currtime);
            snd_seq_ev_schedule_real(ev, dest_queue, 0, &rtime);
      } else {
            if (ppq != slave_ppq)
                  currtime = (currtime * slave_ppq) / ppq;
            currtime += tick_offset;
            snd_seq_ev_schedule_tick(ev, dest_queue, 0, currtime);
      }
}

/* fill the normal event header */
static void set_event_header(snd_seq_event_t *ev)
{
      snd_seq_ev_clear(ev);
      snd_seq_ev_set_dest(ev, dest_client, dest_port);
      snd_seq_ev_set_source(ev, my_port);
      set_event_time(ev, Mf_currtime);
}

/* start the timer */
static void alsa_start_timer(void)
{
      snd_seq_start_queue(seq_handle, dest_queue, NULL);
}

/* stop the timer */
static void alsa_stop_timer(void)
{
      snd_seq_event_t ev;
      set_event_header(&ev);
      snd_seq_stop_queue(seq_handle, dest_queue, &ev);
}

/* change the tempo */
static void do_tempo(int us)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_MUCH) {
            double bpm;
            bpm = 60.0E6 / (double) us;
            printf("Tempo %d us/beat, %.2f bpm\n", us, bpm);
      }

      /* store the new tempo and timestamp of the tempo change */
      local_secs = tick2time_dbl(Mf_currtime);
      local_ticks = Mf_currtime;
      local_tempo = us;

      set_event_header(&ev);
      if (!slave)
            snd_seq_change_queue_tempo(seq_handle, dest_queue, us, &ev);
}

static void do_noteon(int chan, int pitch, int vol)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: NoteOn (%d) %d %d\n", Mf_currtime, chan, pitch, vol);
      set_event_header(&ev);
      snd_seq_ev_set_noteon(&ev, chan, pitch, vol);
      write_ev(&ev);
}


static void do_noteoff(int chan, int pitch, int vol)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: NoteOff (%d) %d %d\n", Mf_currtime, chan, pitch, vol);
      set_event_header(&ev);
      snd_seq_ev_set_noteoff(&ev, chan, pitch, vol);
      write_ev(&ev);
}


static void do_program(int chan, int program)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: Program (%d) %d\n", Mf_currtime, chan, program);
      set_event_header(&ev);
      snd_seq_ev_set_pgmchange(&ev, chan, program);
      write_ev(&ev);
}


static void do_parameter(int chan, int control, int value)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: Control (%d) %d %d\n", Mf_currtime, chan, control, value);
      set_event_header(&ev);
      snd_seq_ev_set_controller(&ev, chan, control, value);
      write_ev(&ev);
}


static void do_pitchbend(int chan, int lsb, int msb)
{     /* !@#$% lsb & msb are in the wrong order in docs */
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: Pitchbend (%d) %d %d\n", Mf_currtime, chan, lsb, msb);
      set_event_header(&ev);
      snd_seq_ev_set_pitchbend(&ev, chan, (lsb + (msb << 7)) - 8192);
      write_ev(&ev);
}

static void do_pressure(int chan, int pitch, int pressure)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: KeyPress (%d) %d %d\n", Mf_currtime, chan, pitch, pressure);
      set_event_header(&ev);
      snd_seq_ev_set_keypress(&ev, chan, pitch, pressure);
      write_ev(&ev);
}

static void do_chanpressure(int chan, int pressure)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_EVENT)
            printf("%ld: ChanPress (%d) %d\n", Mf_currtime, chan, pressure);
      set_event_header(&ev);
      snd_seq_ev_set_chanpress(&ev, chan, pressure);
      write_ev(&ev);
}

static void do_sysex(int len, char *msg)
{
      snd_seq_event_t ev;

      if (verbose >= VERB_MUCH) {
            int c;
            printf("%ld: Sysex, len=%d\n", Mf_currtime, len);
            for (c = 0; c < len; c++) {
                  printf(" %02x", (unsigned char)msg[c]);
                  if (c % 16 == 15)
                        putchar('\n');
            }
            if (c % 16 != 15)
                  putchar('\n');
      }

      set_event_header(&ev);
      snd_seq_ev_set_sysex(&ev, len, msg);
      write_ev(&ev);
}

static snd_seq_event_t *wait_for_event(void)
{
      int left;
      snd_seq_event_t *input_event;
  
      if (use_blocking_mode) {
            /* read the event - blocked until any event is read */
            left = snd_seq_event_input(seq_handle, &input_event);
      } else {
            /* read the event - using select syscall */
            while ((left = snd_seq_event_input(seq_handle, &input_event)) >= 0 &&
                   input_event == NULL) {
                  int npfds = snd_seq_poll_descriptors_count(seq_handle, POLLIN);
                  struct pollfd *pfds = alloca(sizeof(*pfds) * npfds);
                  snd_seq_poll_descriptors(seq_handle, pfds, npfds, POLLIN);
                  if ((left = poll(pfds, npfds, -1)) < 0) {
                        printf("poll error = %i (%s)\n", errno, snd_strerror(errno));
                        exit(1);
                  }
            }
      }

      if (left < 0) {
            printf("alsa_sync error!:%s\n", snd_strerror(left));
            return NULL;
      }

      return input_event;
}

/* synchronize to the end of the event */
static void alsa_sync(void)
{
      /* send the echo event to the self client. */
      if (verbose >= VERB_MUCH)
            printf("alsa_sync syncing...\n");
      /* dump the buffer */
      snd_seq_drain_output(seq_handle);
      snd_seq_sync_output_queue(seq_handle);
      if (verbose >= VERB_MUCH)
            printf("alsa_sync synced\n");
      sleep(1); /* give a time for note releasing.. */
}


/* wait for the start of the queue */
static void wait_start(void)
{
      snd_seq_event_t *input_event;

      /* wait for the start event from the system timer */
      for (;;) {
            input_event = wait_for_event();
            if (input_event) {
                  if (verbose >= VERB_MUCH)
                        printf("wait_start got event. type=%d, flags=%d\n",
                               input_event->type, input_event->flags);
                  if (input_event->type == SND_SEQ_EVENT_START &&
                      input_event->data.queue.queue == dest_queue) {
                        snd_seq_free_event(input_event);
                        break;
                  }
                  snd_seq_free_event(input_event);
            }
      }
      if (verbose >= VERB_MUCH)
            printf("start received\n");
}


/* print the usage */
static void usage(void)
{
      fprintf(stderr, "usage: playmidi1 [options] [file]\n");
      fprintf(stderr, "  options:\n");
      fprintf(stderr, "  -v: verbose mode\n");
      fprintf(stderr, "  -a client:port : set destination address (default=%d:%d)\n",
            DEST_CLIENT_NUMBER, DEST_PORT_NUMBER);
      fprintf(stderr, "  -q queue: use the specified queue\n");
      fprintf(stderr, "  -s queue: slave mode (allow external clock synchronization)\n");
      fprintf(stderr, "  -r : play on real-time mode\n");
      fprintf(stderr, "  -b : play on non-blocking mode\n");
}

int main(int argc, char *argv[])
{
      int tmp;
      int c;
      snd_seq_addr_t dest_addr;
      const char *addr = "65:0";

      while ((c = getopt(argc, argv, "s:a:p:q:vrb")) != -1) {
            switch (c) {
            case 'v':
                  verbose++;
                  break;
            case 'a':
            case 'p':
                  addr = optarg;
                  break;
            case 'q':
                  dest_queue = atoi(optarg);
                  if (dest_queue < 0) {
                        fprintf(stderr, "invalid queue number %d\n", dest_queue);
                        exit(1);
                  }
                  break;
            case 's':
                  slave = 1;
                  dest_queue = atoi(optarg);
                  if (dest_queue < 0) {
                        fprintf(stderr, "invalid queue number %d\n", dest_queue);
                        exit(1);
                  }
                  break;
            case 'r':
                  use_realtime = 1;
                  break;
            case 'b':
                  use_blocking_mode = 0;
                  break;
            default:
                  usage();
                  exit(1);
            }
      }

      if (verbose >= VERB_INFO) {
            if (use_realtime)
                  printf("ALSA MIDI Player, feeding events to real-time queue\n");
            else
                  printf("ALSA MIDI Player, feeding events to song queue\n");
      }

      /* open the sequencer device */
      /* Here we open the device in read/write for slave mode. */
      tmp = snd_seq_open(&seq_handle, "hw", slave ? SND_SEQ_OPEN_DUPLEX : SND_SEQ_OPEN_OUTPUT, 0);
      if (tmp < 0) {
            perror("open /dev/snd/seq");
            exit(1);
      }
      
      tmp = snd_seq_nonblock(seq_handle, !use_blocking_mode);
      if (tmp < 0) {
            perror("block_mode");
            exit(1);
      }
                  
      /* set the name */
      /* set the event filter to receive only the echo event */
      /* if running in slave mode, also listen for a START event */
      if (slave)
            snd_seq_set_client_event_filter(seq_handle, SND_SEQ_EVENT_START);
      snd_seq_set_client_name(seq_handle, "MIDI file player");

      /* create the port */
      my_port = snd_seq_create_simple_port(seq_handle, "Port 0",
                                   SND_SEQ_PORT_CAP_WRITE |
                                   SND_SEQ_PORT_CAP_READ,
                                   SND_SEQ_PORT_TYPE_MIDI_GENERIC);
      if (my_port < 0) {
            perror("create port");
            exit(1);
      }
      
      if (snd_seq_parse_address(seq_handle, &dest_addr, addr) < 0) {
            perror("invalid destination address");
            exit(1);
      }
      dest_client = dest_addr.client;
      dest_port = dest_addr.port;

      /* set the queue */
      if (dest_queue >= 0) {
            shared_queue = 1;
            if (snd_seq_set_queue_usage(seq_handle, dest_queue, 1) < 0) {
                  perror("use queue");
                  exit(1);
            }
      } else {
            shared_queue = 0;
            dest_queue = snd_seq_alloc_queue(seq_handle);
            if (dest_queue < 0) {
                  perror("alloc queue");
                  exit(1);
            }
      }

      /* set the subscriber */
      tmp = snd_seq_connect_to(seq_handle, my_port, dest_client, dest_port);
      if (tmp < 0) {
            perror("subscribe");
            exit(1);
      }

      /* subscribe for the timer START event */ 
      if (slave) {      
            tmp = snd_seq_connect_from(seq_handle, my_port,
                                 SND_SEQ_CLIENT_SYSTEM,
                                 dest_queue + 16 /*snd_seq_queue_sync_port(dest_queue)*/);
            if (tmp < 0) {
                  perror("subscribe");
                  exit(1);
            }     
      }
      
      /* change the pool size */
      if (snd_seq_set_client_pool_output(seq_handle, WRITE_POOL_SIZE) < 0 ||
          snd_seq_set_client_pool_input(seq_handle, READ_POOL_SIZE) < 0 ||
          snd_seq_set_client_pool_output_room(seq_handle, WRITE_POOL_SPACE) < 0) {
            perror("pool");
            exit(1);
      }
      
      if (optind < argc) {
            F = fopen(argv[optind], "r");
            if (F == NULL) {
                  fprintf(stderr, "playmidi1: can't open file %s\n", argv[optind]);
                  exit(1);
            }
      } else
            F = stdin;

      Mf_header = do_header;
      Mf_tempo = do_tempo;
      Mf_getc = mygetc;
      Mf_text = mytext;

      Mf_noteon = do_noteon;
      Mf_noteoff = do_noteoff;
      Mf_program = do_program;
      Mf_parameter = do_parameter;
      Mf_pitchbend = do_pitchbend;
      Mf_pressure = do_pressure;
      Mf_chanpressure = do_chanpressure;
      Mf_sysex = do_sysex;

      /* go.. go.. go.. */
      mfread();

      alsa_sync();
      if (! shared_queue)
            alsa_stop_timer();

      snd_seq_close(seq_handle);

      if (verbose >= VERB_INFO) {
            printf("Stopping at %f s,  tick %f\n",
                   tick2time_dbl(Mf_currtime + 1), (double) (Mf_currtime + 1));
      }

      exit(0);
}

Generated by  Doxygen 1.6.0   Back to index