OSDN Git Service

Added playmidi1...
authorJaroslav Kysela <perex@perex.cz>
Sun, 24 Jan 1999 12:00:39 +0000 (12:00 +0000)
committerJaroslav Kysela <perex@perex.cz>
Sun, 24 Jan 1999 12:00:39 +0000 (12:00 +0000)
test/Makefile.am
test/midifile.3 [new file with mode: 0644]
test/midifile.c [new file with mode: 0644]
test/midifile.h [new file with mode: 0644]
test/playmidi1.c [new file with mode: 0644]

index e4a851a..3fde7b1 100644 (file)
@@ -1,4 +1,4 @@
-check_PROGRAMS=control mixer switches pause pcm latency seq
+check_PROGRAMS=control mixer switches pause pcm latency seq playmidi1
 
 control_LDADD=../src/libasound.la
 mixer_LDADD=../src/libasound.la
@@ -7,8 +7,10 @@ pause_LDADD=../src/libasound.la
 pcm_LDADD=../src/libasound.la
 latency_LDADD=../src/libasound.la
 seq_LDADD=../src/libasound.la
+playmidi1_LDADD=../src/libasound.la
 
 INCLUDES=-I$(top_srcdir)/include
 CFLAGS=-static -Wall -pipe -g
 
-EXTRA_DIST=seq-decoder.c seq-sender.c
+EXTRA_DIST=seq-decoder.c seq-sender.c midifile.h midifile.c midifile.3
+
diff --git a/test/midifile.3 b/test/midifile.3
new file mode 100644 (file)
index 0000000..3428c44
--- /dev/null
@@ -0,0 +1,336 @@
+.TH MIDIFILE 3
+.SH NAME
+mfread,mfwrite - read and write a standard MIDI file
+.SH SYNOPSIS
+\fC#include "mfread.h"
+
+mfread ()
+
+.nf
+int (*Mf_getc) ();
+int (*Mf_putc) ();
+int (*Mf_error) (char *msg);
+int (*Mf_header) (int format, int ntrks, int division);
+int (*Mf_trackstart) ();
+int (*Mf_trackend) ();
+int (*Mf_noteon) (int chan, int pitch, int vol);
+int (*Mf_noteoff) (int chan, int pitch, int vol);
+int (*Mf_pressure) (int chan, int pitch, int pressure);
+int (*Mf_parameter) (int chan, int control, int value);
+int (*Mf_pitchbend) (int chan, int msb, int lsb);
+int (*Mf_program) (int chan, int program);
+int (*Mf_chanpressure) (int chan, int pressure);
+int (*Mf_sysex) (int leng, char *msg);
+int (*Mf_metamisc) (int type, int leng, int msg);
+int (*Mf_seqspecific) (int type, int leng, int msg);
+int (*Mf_seqnum) (int num);
+int (*Mf_text) (int type, int leng, int msg);
+int (*Mf_eot) ();
+int (*Mf_timesig) (int numer, int denom, int clocks, int qnotes);
+int (*Mf_smpte) (int hour, int min, int sec, int frame, int fract);
+int (*Mf_tempo) (int microsecs);
+int (*Mf_keysig) (int sharpflat, int minor);
+int (*Mf_arbitrary) (int leng, int msg);
+int Mf_nomerge;
+long Mf_currtime;
+.fi
+.sp 1
+mfwrite(int format, int ntracks, int division, FILE *fp)
+.sp 1
+.nf
+int (*Mf_writetrack)(int track);
+int (*Mf_writetempotrack)();
+
+void mf_write_midi_event(delta, type, chan, data, size)
+unsigned long delta;
+unsigned int type,chan,size;
+char *data;
+
+void mf_write_meta_event(delta, type, data, size)
+unsigned long delta;
+unsigned int type,chan,size;
+char *data;
+
+void mf_write_tempo(tempo)
+unsigned long tempo;
+
+unsigned long mf_sec2ticks(float seconds, int division, int tempo)
+float seconds;
+int division;
+unsigned int tempo;
+
+float mf_ticks2sec(ticks, division, tempo)
+unsigned long ticks;
+int division;
+unsigned int tempo;
+.fi
+
+.SH DESCRIPTION
+The \fCmfread\fR function reads and inteprets a standard MIDI file.
+To use it you need to understand the general form of a
+MIDI file and the type of information it contains, but you don't
+need to know much, if anything, about the detailed format of the file
+and the mechanics of reading it reliably and portably.
+
+The \fCmfwrite\fR function writes a standard MIDI file making
+use of user-defined functions that access the program's
+data structure.  To use it you need to define your own Mf_writetrack
+routine and then make use of the write_* family of routines to
+write out the MIDI data.  The \fCmfwrite\fR routine takes
+care of the file format and writing the file and track chunk headers. 
+
+.SH READING STANDARD MIDI FILES
+A single call to \fCmfread\fR will read an entire MIDI file.
+The interface to \fCmfread\fR is a set of external variables
+named \fCMf_*\fR, most of which are function pointers to be called
+from within \fCmfread\fR during the process of parsing the MIDI file.
+Before calling \fCmfread\fR, the only
+requirement is that you assign a value
+to \fCMf_getc\fR - a pointer to a function that will return
+characters from the MIDI file, using -1 to indicate EOF.
+All the rest of the function
+pointers are initialized to NULL, and the default action for each
+is to do nothing.  The following is a complete program using \fCmfread\fR
+that could serve as a 'syntax checker' for MIDI files:
+
+.in +1i
+.ft C
+.nf
+#include <stdio.h>
+#include "midifile.h"
+
+mygetc()
+{
+       /* use standard input */
+       return(getchar());
+}
+
+main()
+{
+       Mf_getc = mygetc;
+       mfread();
+       exit(0);
+}
+.fi
+.ft R
+.in -1i
+
+This takes advantage of the default action when an error is detected, which
+is to exit silently with a return code of 1.  An error function of your
+own can be used by giving a value to \fCMf_error\fR; the function will be
+called with the error message as an argument.
+The other \fCMf_* variables can similarly be used to call arbitrary
+functions while parsing the MIDI file.  The descriptions below
+of the information passed to these functions is sparse; refer to
+the MIDI file standard for the complete descriptions.
+
+\fCMf_header\fR is the first function to be called, and its arguments
+contain information from the MIDI file's header; the format (0,1, or 2),
+the number of tracks, and the division of a quarter-note that defines
+the times units.
+\fCMf_trackstart\fR and
+\fCMf_trackend\fR are called at the beginning and end of each track.
+
+Once inside a track, each separate message causes a function to be called.
+For example, each note-on message causes \fCMf_noteon\fR to be called
+with the channel, pitch, and volume as arguments.  The time at which
+the message occurred is stored in \fCMf_currtime\fR - one of the few
+external variables that isn't a function pointer.  The other channel messages
+are handled in a similar and obvious fashion -
+\fCMf_noteoff\fR,
+\fCMf_pressure\fR,
+\fCMf_parameter\fR,
+\fCMf_pitchbend\fR,
+\fCMf_program\fR,
+and \fCMf_chanpressure\fR.  See the declarations above for the arguments
+that are passed to each.
+
+System exclusive messages are handled by calling \fCMf_sysex\fR, passing
+as arguments the message length and a pointer to a static buffer containing
+the entire message.
+The buffer is expanded when necessary; memory availability is the only limit
+to its size.  Normally, 'continued' system exclusives are automatically
+merged, and \fCMf_sysex\fR is only called once.  It you want to disable this
+you can set \fCMf_nomerge\fR to 1, causing \fCMf_sysex\fR to be called
+once for each part of the message.
+
+\fCMf_seqnum\fR is called by the \fImeta\fR message that provides
+a sequence number,
+which if present must appear at the beginning of a track.
+The tempo \fImeta\fR message causes \fCMf_tempo\fR to be called; its
+argument is the number of microseconds per MIDI quarter-note (24 MIDI clocks).
+The end-of-track \fImeta\fR message causes \fCMf_eot\fR to be called.
+The key signature \fImeta\fR message causes \fCMf_keysig\fR to be called;
+the first argument conveys the number of sharps or flats, the second
+argument is 1 if the key is minor.
+
+The \fCMf_timesig\fR and \fCMf_smpte\fR functions are called when the
+corresponding \fImeta\fR messages are seen.  See the MIDI file standard
+for a description of their arguments.
+
+The \fItext\fR messages in the MIDI file standard are of the following
+types:
+
+.in +1i
+.nf
+0x01           Text Event
+0x02           Copyright
+0x03           Sequence/Track Name
+0x04           Instrument
+0x05           Lyric
+0x06           Marker
+0x07           Cue Point
+0x08-0x0F      Reserverd but Undefined
+.fi
+.in -1i
+
+\fCMf_text\fR is called for each of these; the arguments are
+the type number, the message length, and a pointer to the message buffer.
+
+Misceallaneous \fImeta\fR messages are handled by \fCMf_metamisc\fR,
+sequencer-specific messages are handled by \fCMf_seqspecific\fR, and
+arbitrary "escape" messages (started with 0xF7) are handled by
+\fCMf_arbitrary\fR.
+.SH READING EXAMPLE
+The following is a \fCstrings\fR-like program for MIDI files:
+
+.in +1i
+.ft C
+.nf
+#include <stdio.h>
+#include <ctype.h>
+#include "midifile.h"
+
+FILE *F;
+
+mygetc() { return(getc(F)); }
+
+mytext(type,leng,msg)
+char *msg;
+{
+       char *p;
+       char *ep = msg + leng;
+
+       for ( p=msg; p<ep ; p++ )
+               putchar( isprint(*p) ? *p : '?' );
+       putchar('\n');
+}
+
+main(argc,argv)
+char **argv;
+{
+       if ( argc > 1 )
+               F = fopen(argv[1],"r");
+       else
+               F = stdin;
+
+       Mf_getc = mygetc;
+       Mf_text = mytext;
+
+       mfread();
+
+       exit(0);
+}
+.fi
+.ft R
+.in -1i
+.sp
+.SH WRITING STANDARD MIDI FILES
+A single call to \fCmfwrite\fR will write an entire MIDI file.  Before
+calling \fCmfwrite\fR, you must assign values to function pointers
+\fCMf_writetrack\fR and \fCMf_putc\fR.  The first is a routine to
+access your MIDI data structure, which can make use of other library
+routines to write the actual MIDI data.  The routine
+\fCMf_writetrack\fR will be passed a single parameter which is the
+number of the track to be written.  The pointer \fCMf_putc\fR should be
+set to point to a routine that accepts a charcter as input, writes that
+character to a file, and returns the value that was written.  In the
+case of a format 1 file, a routine has to be written to write a tempo
+map, and assigned to the function pointer \fCMf_writetempotrack\fR.
+This is because format 1 files assume the first track written is a
+tempo track.
+
+\fCmf_write_midi_event\fR and \fCmf_write_meta_event\fR are routines
+that should be called from your \fCMf_writetrack\fR routine to write
+out MIDI events.  The delta time param is the number of ticks since the
+last event.  The int "type" is the type of MIDI message. The int "chan"
+is the MIDI channel, which can be between 1 and 16.  The char pointer
+"data" points to an array containing the data bytes, if any exist. The
+int "size" is the number of data bytes.
+
+\fCmf_sec2ticks\fR and \fCmf_ticks2sec\fR are utility routines
+to help you convert between the MIDI file parameter of ticks
+and the more standard seconds. The int "division" is the same
+division parameter from the file header, and tempo is expressed
+in microseconds per MIDI quarter-note, or "24ths of a microsecond
+per MIDI clock". The division has two meanings, depending on
+whether bit 15 is set or not.  If bit 15 of division is zero,
+bits 14 through 0 represent the number of delta-time "ticks"
+which make up a quarter note.  If bit 15 of division is a one,
+delta-times in a file correspond to subdivisions of a second
+similiar to SMPTE and MIDI time code. In this format bits
+14 through 8 contain one of four values - 24, -25, -29, or -30,
+corresponding to the four standard SMPTE and MIDI time code
+frame per second formats, where -29 represents 30 drop frame.
+The second byte consisting of bits 7 through 0 corresponds
+the the resolution within a frame.  Refer the Standard MIDI Files 
+1.0 spec for more details.
+
+.SH WRITING EXAMPLE
+The following is a simple program to demonstrate writing MIDI files.
+The track would consist of a series of quarter notes from lowest to
+highest in pitch at constant velocity, each separted by a quarter-note
+rest.
+.sp
+.in +1i
+.ft C
+.nf
+#include <stdio.h>
+#include <ctype.h>
+#include "midifile.h"
+
+FILE *fp;
+myputc(c) { return(putc(c,fp));}
+
+int mywritetrack(track)
+int track;
+{
+    int i;
+    char data[2];
+
+    /* 120 beats/per/second */
+    mf_write_tempo((long)500000); 
+
+    for(i = 1 ; i < 128; i++){
+       data[0] = i; /* note number */
+       data[1] = 64; /* velocity */
+       if(!mf_write_midi_event(480,note_on,1,data,2)) 
+          return(-1);
+       if(!mf_write_midi_event(480,note_off,1,data,2)) 
+           return(-1);
+    }
+
+    return(1);
+} /* end of write_track() */
+
+main(argc,argv)
+char **argv;
+{
+    if((fp = fopen(argv[1],"w")) == 0L)
+       exit(1);
+
+    Mf_putc = myputc;
+    Mf_writetrack = mywritetrack;
+
+    /* write a single track */
+    mfwrite(0,1,480,fp);
+}
+.sp
+.fi
+.ft R
+.in -1i
+.sp
+.SH AUTHOR
+Tim Thompson (att!twitch!glimmer!tjt)
+.SH CONTRIBUTORS
+Michael Czeiszperger (mike@pan.com)
diff --git a/test/midifile.c b/test/midifile.c
new file mode 100644 (file)
index 0000000..4b959b4
--- /dev/null
@@ -0,0 +1,1173 @@
+/*
+ * midifile 1.11
+ *
+ * Read and write a MIDI file.  Externally-assigned function pointers are
+ * called upon recognizing things in the file.
+ *
+ * Original release ?
+ * June 1989 - Added writing capability, M. Czeiszperger.
+ *
+ *          The file format implemented here is called
+ *          Standard MIDI Files, and is part of the Musical
+ *          instrument Digital Interface specification.
+ *          The spec is avaiable from:
+ *
+ *               International MIDI Association
+ *               5316 West 57th Street
+ *               Los Angeles, CA 90056
+ *
+ *          An in-depth description of the spec can also be found
+ *          in the article "Introducing Standard MIDI Files", published
+ *          in Electronic Musician magazine, April, 1989.
+ *
+ * February 1993 - Minor adjustments, Greg Lee:
+ *     (1) can now set the global variable Mf_interactive to 1 to prevent the
+ *         reading functions from looking for file and track headers
+ *     (2) can now write system exclusive data with
+ *             mf_write_midi_event(delta_time, system_exlusive, 0, data, size)
+ *     (3) changed definition of 'sequencer_specific' in midifile.h to 0x7f
+ *     (4) changed mf_write_tempo to take additional delta_time as first argument
+ *         (since delta need not be zero)
+ *     (5) added function mf_write_seqnum(unsigned long delta_time, unsigned seqnum)
+ *     (6) changed mf_write_midi_event to use running status
+ *     (7) removed the code to write an end of track meta event automatically
+ *             -- this must now be done by the user of the library (I changed
+ *             it because I need to be able to control the time delta of this
+ *              meta event)
+ *     (8) added global variables Mf_division, Mf_currtempo, Mf_realtime, which
+ *             are updated by the reading functions.  Mf_realtime is useful,
+ *             because Mf_currtime does not really measure time at all, since
+ *             its units change value at every tempo change.  Mf_realtime is
+ *             the midi-time elapsed in units of 1/16 of a centisecond (but it
+ *             does not handle smpte times)
+ *     (9) maintains a history of tempo settings to update Mf_currtempo,
+ *             to handle tempo tracks.
+ *     (10) if there is an Mf_error function, the error routine no longer
+ *             exits, leaving it to the application to do this.
+ *     (11) chanmessage skips over invalid c1 command bytes > 127 and
+ *             adjusts invalid c2 argument byte > 127 to 127.
+ *     (12) readmt returns EOF when it encounters a 0 or 0x1a byte instead of an expected
+ *             header string (some midi files have padding at end).
+ */
+#define NO_LC_DEFINES
+#include "midifile.h"
+#ifdef NO_LC_DEFINES
+#define system_exclusive       0xf0
+#define        meta_event              0xFF
+#define        set_tempo               0x51
+#define lowerbyte(x) ((unsigned char)(x & 0xff))
+#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8))
+#endif
+
+#define NULLFUNC 0
+#if 0
+#define NULL 0
+#endif
+
+#define THINK
+
+#ifdef THINK
+#include <stdlib.h>
+#endif
+
+#include <stdio.h>
+#include <values.h>
+
+char *strcpy (), *strcat ();
+/*void exit(), free();*/
+
+/* public stuff */
+
+/* Functions to be called while processing the MIDI file. */
+int (*Mf_getc) () = NULLFUNC;
+void (*Mf_error) () = NULLFUNC;
+void (*Mf_header) () = NULLFUNC;
+void (*Mf_trackstart) () = NULLFUNC;
+void (*Mf_trackend) () = NULLFUNC;
+void (*Mf_noteon) () = NULLFUNC;
+void (*Mf_noteoff) () = NULLFUNC;
+void (*Mf_pressure) () = NULLFUNC;
+void (*Mf_parameter) () = NULLFUNC;
+void (*Mf_pitchbend) () = NULLFUNC;
+void (*Mf_program) () = NULLFUNC;
+void (*Mf_chanpressure) () = NULLFUNC;
+void (*Mf_sysex) () = NULLFUNC;
+void (*Mf_arbitrary) () = NULLFUNC;
+void (*Mf_metamisc) () = NULLFUNC;
+void (*Mf_seqnum) () = NULLFUNC;
+void (*Mf_eot) () = NULLFUNC;
+void (*Mf_smpte) () = NULLFUNC;
+void (*Mf_tempo) () = NULLFUNC;
+void (*Mf_timesig) () = NULLFUNC;
+void (*Mf_keysig) () = NULLFUNC;
+void (*Mf_seqspecific) () = NULLFUNC;
+void (*Mf_text) () = NULLFUNC;
+
+/* Functions to implement in order to write a MIDI file */
+int (*Mf_putc) () = NULLFUNC;
+int (*Mf_writetrack) () = NULLFUNC;
+int (*Mf_writetempotrack) () = NULLFUNC;
+
+int Mf_nomerge = 0;            /* 1 => continue'ed system exclusives are */
+ /* not collapsed. */
+int Mf_interactive = 0;                /* 1 => file and track headers are not required */
+unsigned long Mf_currtime = 0L;        /* current time in delta-time units */
+unsigned long Mf_realtime = 0L;        /* current time in 1/16 centisecond-time units */
+static double Mf_f_realtime = 0;/* as above, floating */
+static double old_f_realtime = 0;
+int Mf_division = 96;
+unsigned long Mf_currtempo = 500000;
+static unsigned long old_currtempo = 500000;
+static unsigned long old_realtime = 0;
+static unsigned long old_currtime = 0;
+static unsigned long revised_time = 0;
+static unsigned long tempo_change_time = 0;
+
+#define MAX_HISTORY 512
+static unsigned long tempo_history[MAX_HISTORY];
+static unsigned long tempo_history_time[MAX_HISTORY];
+static int tempo_history_count = 0;
+
+/* private stuff */
+static long Mf_toberead = 0L;
+static long Mf_numbyteswritten = 0L;
+
+static long readvarinum ();
+static long read32bit ();
+static long to32bit ();
+static int read16bit ();
+static int to16bit ();
+static char *msg ();
+static void readheader ();
+static int readtrack ();
+static void badbyte ();
+static void metaevent ();
+static void sysex ();
+static void chanmessage ();
+static void msginit ();
+static int msgleng ();
+static void msgadd ();
+static void biggermsg ();
+static int eputc (unsigned char c);
+
+double mf_ticks2sec (unsigned long ticks, int division, unsigned long tempo);
+int mf_write_meta_event ();
+void mf_write_tempo ();
+void mf_write_seqnum ();
+void WriteVarLen ();
+
+#ifdef READ_MODS
+#include "mp_mod.c"
+static int mod_file_flag = 0;
+#endif /* READ_MODS */
+static int force_exit;
+
+void
+mfread ()
+{
+  force_exit = 0;
+  if (Mf_getc == NULLFUNC)
+    mferror ("mfread() called without setting Mf_getc");
+
+  readheader ();
+#ifdef READ_MODS
+  if (mod_file_flag)
+    do_module();
+  else
+#endif
+    while (readtrack () && !force_exit)
+      ;
+}
+
+/* for backward compatibility with the original lib */
+void
+midifile ()
+{
+  mfread ();
+}
+
+static
+int 
+readmt (s)                     /* read through the "MThd" or "MTrk" header string */
+     char *s;
+{
+  int n = 0;
+  char *p = s;
+  int c;
+
+  while (n++ < 4 && (c = (*Mf_getc) ()) != EOF)
+    {
+      if (c != *p++)
+       {
+         char buff[32];
+         if (!c) return(EOF);
+         if (c == 0x1a) return(EOF);
+         (void) strcpy (buff, "expecting ");
+         (void) strcat (buff, s);
+         mferror (buff);
+         break;
+       }
+    }
+  return (c);
+}
+
+static
+int
+egetc ()                       /* read a single character and abort on EOF */
+{
+  int c = (*Mf_getc) ();
+
+  if (c == EOF) {
+    mferror ("premature EOF");
+    force_exit = 1;
+  }
+  Mf_toberead--;
+  return (c);
+}
+
+static
+void 
+readheader ()                  /* read a header chunk */
+{
+  int format, ntrks, division;
+
+
+  Mf_division = 96;
+  Mf_currtempo = 500000;
+  old_currtempo = 500000;
+  tempo_history_count = 0;
+  tempo_history[tempo_history_count] = Mf_currtempo;
+  tempo_history_time[tempo_history_count] = 0;
+
+  if (Mf_interactive)
+    {
+      Mf_toberead = 0;
+      format = 0;
+      ntrks = 1;
+      division = 96;
+    }
+  else
+#ifdef READ_MODS
+    if (!strncmp(Mf_file_contents, "MThd", 4))
+#endif
+    {
+      if (readmt ("MThd") == EOF)
+       return;
+
+      Mf_toberead = read32bit ();
+      format = read16bit ();
+      ntrks = read16bit ();
+      Mf_division = division = read16bit ();
+    }
+#ifdef READ_MODS
+  else
+    {
+      format = 0;
+      ntrks = 1;
+      division = Mf_division;
+      Mf_toberead = 0;
+      mod_file_flag = 1;
+    }
+#endif
+
+  if (Mf_header)
+    (*Mf_header) (format, ntrks, division);
+
+  /* flush any extra stuff, in case the length of header is not 6 */
+  while (Mf_toberead > 0 && !force_exit)
+    (void) egetc ();
+}
+
+
+/*#define DEBUG_TIMES*/
+static
+unsigned long
+find_tempo()
+{
+  int i;
+  unsigned long old_tempo = Mf_currtempo;
+  unsigned long new_tempo = Mf_currtempo;
+
+  for (i = 0; i <= tempo_history_count; i++) {
+    if (tempo_history_time[i] <= Mf_currtime) old_tempo = tempo_history[i];
+    new_tempo = tempo_history[i];
+    if (tempo_history_time[i] > revised_time) break;
+  }
+  if (i > tempo_history_count || tempo_history_time[i] > Mf_currtime) {
+#ifdef DEBUG_TIMES
+printf("[past %d, old_tempo %d]\n", tempo_history_time[i], old_tempo);
+#endif
+    revised_time = Mf_currtime;
+    return(old_tempo);
+  }
+  tempo_change_time = revised_time = tempo_history_time[i];
+#ifdef DEBUG_TIMES
+printf("[revised_time %d, new_tempo %d]\n", revised_time, new_tempo);
+#endif
+  return(new_tempo);
+}
+
+static
+int 
+readtrack ()                   /* read a track chunk */
+{
+  /* This array is indexed by the high half of a status byte.  It's */
+  /* value is either the number of bytes needed (1 or 2) for a channel */
+  /* message, or 0 (meaning it's not  a channel message). */
+  static int chantype[] =
+  {
+    0, 0, 0, 0, 0, 0, 0, 0,    /* 0x00 through 0x70 */
+    2, 2, 2, 2, 1, 1, 2, 0     /* 0x80 through 0xf0 */
+  };
+  long lookfor;
+  int c, c1, type;
+  int sysexcontinue = 0;       /* 1 if last message was an unfinished sysex */
+  int running = 0;             /* 1 when running status used */
+  int status = 0;              /* status value (e.g. 0x90==note-on) */
+  int needed;
+
+  if (Mf_interactive)
+    {
+      Mf_toberead = MAXINT;
+    }
+  else
+    {
+      if (readmt ("MTrk") == EOF)
+       return (0);
+
+      Mf_toberead = read32bit ();
+    }
+  Mf_currtime = Mf_realtime = 0;
+  Mf_f_realtime = old_f_realtime = 0;
+  old_currtime = old_realtime = 0;
+  Mf_currtempo = find_tempo();
+
+  if (Mf_trackstart)
+    (*Mf_trackstart) ();
+
+  while (!force_exit && (Mf_interactive || Mf_toberead > 0))
+    {
+
+      if (Mf_interactive)
+       Mf_currtime += 1;
+      else
+       {
+         double delta_secs;
+         unsigned long delta_ticks = readvarinum ();
+         revised_time = Mf_currtime;
+         Mf_currtime += delta_ticks;   /* delta time */
+
+/*
+ * Step through each tempo change from old_currtime up to now,
+ * revising Mf_realtime after each change.
+ */
+
+         while (revised_time < Mf_currtime) {
+           unsigned long save_time = revised_time;
+           unsigned long save_tempo = Mf_currtempo;
+           Mf_currtempo = find_tempo();
+
+           if (Mf_currtempo != old_currtempo) {
+             old_currtempo = Mf_currtempo;
+             old_realtime = Mf_realtime;
+             if (revised_time != tempo_change_time) {
+               old_f_realtime = Mf_f_realtime;
+               old_currtime = save_time;
+             }
+           delta_secs = mf_ticks2sec (revised_time-old_currtime, Mf_division, save_tempo);
+#ifdef DEBUG_TIMES
+printf("d(rev %d - old %d, div %d, tempo %d) = %.3f\n",
+revised_time, old_currtime, Mf_division, save_tempo, delta_secs * 1600.0);
+#endif
+           Mf_f_realtime = old_f_realtime + delta_secs * 1600.0;
+           Mf_realtime = (unsigned long)(0.5 + Mf_f_realtime);
+#ifdef DEBUG_TIMES
+printf("\tt=%d ticks ( = %d csec/16 < old %.2f + %.2f)\n", Mf_currtime, Mf_realtime, 
+old_f_realtime, delta_secs * 1600.0);
+#endif
+             if (revised_time == tempo_change_time) {
+               old_currtime = revised_time;
+             old_f_realtime = Mf_f_realtime;
+             }
+           }
+           else {
+           delta_secs = mf_ticks2sec (revised_time-old_currtime, Mf_division, Mf_currtempo);
+#ifdef DEBUG_TIMES
+printf("d(rev %d - old %d, div %d, tempo %d) = %.3f\n",
+revised_time, old_currtime, Mf_division, Mf_currtempo, delta_secs * 1600.0);
+#endif
+           Mf_f_realtime = old_f_realtime + delta_secs * 1600.0;
+           Mf_realtime = (unsigned long)(0.5 + Mf_f_realtime);
+#ifdef DEBUG_TIMES
+printf("\tt=%d ticks ( = %d csec/16 < old %.2f + %.2f)\n", Mf_currtime, Mf_realtime, 
+old_f_realtime, delta_secs * 1600.0);
+#endif
+           }
+
+
+         }
+       }
+
+      c = egetc ();
+
+      if (sysexcontinue && c != 0xf7)
+       mferror ("didn't find expected continuation of a sysex");
+
+      if ((c & 0x80) == 0)
+       {                       /* running status? */
+         if (status == 0)
+           mferror ("unexpected running status");
+         running = 1;
+       }
+      else
+       {
+         status = c;
+         running = 0;
+       }
+
+      needed = chantype[(status >> 4) & 0xf];
+
+      if (needed)
+       {                       /* ie. is it a channel message? */
+
+         if (running)
+           c1 = c;
+         else
+           c1 = egetc ();
+         chanmessage (status, c1, (needed > 1) ? egetc () : 0);
+         continue;;
+       }
+
+      switch (c)
+       {
+
+       case 0xff:              /* meta event */
+
+         type = egetc ();
+         lookfor = Mf_toberead - readvarinum ();
+         msginit ();
+
+         while (Mf_toberead > lookfor)
+           msgadd (egetc ());
+
+         metaevent (type);
+         break;
+
+       case 0xf0:              /* start of system exclusive */
+
+         lookfor = Mf_toberead - readvarinum ();
+         msginit ();
+         msgadd (0xf0);
+
+         while (Mf_toberead > lookfor)
+           msgadd (c = egetc ());
+
+         if (c == 0xf7 || Mf_nomerge == 0)
+           sysex ();
+         else
+           sysexcontinue = 1;  /* merge into next msg */
+         break;
+
+       case 0xf7:              /* sysex continuation or arbitrary stuff */
+
+         lookfor = Mf_toberead - readvarinum ();
+
+         if (!sysexcontinue)
+           msginit ();
+
+         while (Mf_toberead > lookfor)
+           msgadd (c = egetc ());
+
+         if (!sysexcontinue)
+           {
+             if (Mf_arbitrary)
+               (*Mf_arbitrary) (msgleng (), msg ());
+           }
+         else if (c == 0xf7)
+           {
+             sysex ();
+             sysexcontinue = 0;
+           }
+         break;
+       default:
+         badbyte (c);
+         break;
+       }
+    }
+  if (Mf_trackend)
+    (*Mf_trackend) ();
+  return (1);
+}
+
+static
+void 
+badbyte (c)
+     int c;
+{
+  char buff[32];
+
+  (void) sprintf (buff, "unexpected byte: 0x%02x", c);
+  mferror (buff);
+}
+
+static
+void 
+metaevent (int type)
+{
+  int leng = msgleng ();
+  char *m = msg ();
+
+  switch (type)
+    {
+    case 0x00:
+      if (Mf_seqnum)
+       (*Mf_seqnum) (to16bit (m[0], m[1]));
+      break;
+    case 0x01:                 /* Text event */
+    case 0x02:                 /* Copyright notice */
+    case 0x03:                 /* Sequence/Track name */
+    case 0x04:                 /* Instrument name */
+    case 0x05:                 /* Lyric */
+    case 0x06:                 /* Marker */
+    case 0x07:                 /* Cue point */
+    case 0x08:
+    case 0x09:
+    case 0x0a:
+    case 0x0b:
+    case 0x0c:
+    case 0x0d:
+    case 0x0e:
+    case 0x0f:
+      /* These are all text events */
+      if (Mf_text)
+       (*Mf_text) (type, leng, m);
+      break;
+    case 0x2f:                 /* End of Track */
+      if (Mf_eot)
+       (*Mf_eot) ();
+      break;
+    case 0x51:                 /* Set tempo */
+      if (Mf_tempo)
+       (*Mf_tempo) (Mf_currtempo = to32bit (0, m[0], m[1], m[2]));
+      if (tempo_history[tempo_history_count] == Mf_currtempo) break;
+      if (tempo_history_time[tempo_history_count] > Mf_currtime) break;
+      if (tempo_history_count < MAX_HISTORY - 1) tempo_history_count++;
+      tempo_history[tempo_history_count] = Mf_currtempo;
+      tempo_history_time[tempo_history_count] = Mf_currtime;
+      break;
+    case 0x54:
+      if (Mf_smpte)
+       (*Mf_smpte) (m[0], m[1], m[2], m[3], m[4]);
+      break;
+    case 0x58:
+      if (Mf_timesig)
+       (*Mf_timesig) (m[0], m[1], m[2], m[3]);
+      break;
+    case 0x59:
+      if (Mf_keysig)
+       (*Mf_keysig) (m[0], m[1]);
+      break;
+    case 0x7f:
+      if (Mf_seqspecific)
+       (*Mf_seqspecific) (leng, m);
+      break;
+    default:
+      if (Mf_metamisc)
+       (*Mf_metamisc) (type, leng, m);
+    }
+}
+
+static
+void 
+sysex ()
+{
+  if (Mf_sysex)
+    (*Mf_sysex) (msgleng (), msg ());
+}
+
+static
+void 
+chanmessage (status, c1, c2)
+     int status;
+     int c1, c2;
+{
+  int chan = status & 0xf;
+
+  /* I found a midi file with Mod Wheel values 128. --gl */
+
+  if (c1 > 127) /*mferror("chanmessage: bad c1") ??*/ return;
+  if (c2 > 127) c2 = 127;
+
+  switch (status & 0xf0)
+    {
+    case 0x80:
+      if (Mf_noteoff)
+       (*Mf_noteoff) (chan, c1, c2);
+      break;
+    case 0x90:
+      if (Mf_noteon)
+       (*Mf_noteon) (chan, c1, c2);
+      break;
+    case 0xa0:
+      if (Mf_pressure)
+       (*Mf_pressure) (chan, c1, c2);
+      break;
+    case 0xb0:
+      if (Mf_parameter)
+       (*Mf_parameter) (chan, c1, c2);
+      break;
+    case 0xe0:
+      if (Mf_pitchbend)
+       (*Mf_pitchbend) (chan, c1, c2);
+      break;
+    case 0xc0:
+      if (Mf_program)
+       (*Mf_program) (chan, c1);
+      break;
+    case 0xd0:
+      if (Mf_chanpressure)
+       (*Mf_chanpressure) (chan, c1);
+      break;
+    }
+}
+
+/* readvarinum - read a varying-length number, and return the */
+/* number of characters it took. */
+
+static long
+readvarinum ()
+{
+  long value;
+  int c;
+
+  c = egetc ();
+  value = c;
+  if (c & 0x80)
+    {
+      value &= 0x7f;
+      do
+       {
+         c = egetc ();
+         value = (value << 7) + (c & 0x7f);
+       }
+      while (c & 0x80);
+    }
+  return (value);
+}
+
+static long
+to32bit (int c1, int c2, int c3, int c4)
+{
+  long value = 0L;
+
+  value = (c1 & 0xff);
+  value = (value << 8) + (c2 & 0xff);
+  value = (value << 8) + (c3 & 0xff);
+  value = (value << 8) + (c4 & 0xff);
+  return (value);
+}
+
+static int
+to16bit (c1, c2)
+     int c1, c2;
+{
+  return ((c1 & 0xff) << 8) + (c2 & 0xff);
+}
+
+static long
+read32bit ()
+{
+  int c1, c2, c3, c4;
+
+  c1 = egetc ();
+  c2 = egetc ();
+  c3 = egetc ();
+  c4 = egetc ();
+  return to32bit (c1, c2, c3, c4);
+}
+
+static int
+read16bit ()
+{
+  int c1, c2;
+  c1 = egetc ();
+  c2 = egetc ();
+  return to16bit (c1, c2);
+}
+
+/* static */
+void
+mferror (s)
+     char *s;
+{
+  if (Mf_error)
+    (*Mf_error) (s);
+  else exit (1);
+}
+
+/* The code below allows collection of a system exclusive message of */
+/* arbitrary length.  The Msgbuff is expanded as necessary.  The only */
+/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */
+
+#define MSGINCREMENT 128
+static char *Msgbuff = NULL;   /* message buffer */
+static int Msgsize = 0;                /* Size of currently allocated Msg */
+static int Msgindex = 0;       /* index of next available location in Msg */
+
+static
+void 
+msginit ()
+{
+  Msgindex = 0;
+}
+
+static char *
+msg ()
+{
+  return (Msgbuff);
+}
+
+static
+int 
+msgleng ()
+{
+  return (Msgindex);
+}
+
+static
+void 
+msgadd (c)
+     int c;
+{
+  /* If necessary, allocate larger message buffer. */
+  if (Msgindex >= Msgsize)
+    biggermsg ();
+  Msgbuff[Msgindex++] = c;
+}
+
+static
+void 
+biggermsg ()
+{
+/*     char *malloc(); */
+  char *newmess;
+  char *oldmess = Msgbuff;
+  int oldleng = Msgsize;
+
+  Msgsize += MSGINCREMENT;
+  newmess = (char *) malloc ((unsigned) (sizeof (char) * Msgsize));
+
+  if (newmess == NULL)
+    mferror ("malloc error!");
+
+  /* copy old message into larger new one */
+  if (oldmess != NULL)
+    {
+      register char *p = newmess;
+      register char *q = oldmess;
+      register char *endq = &oldmess[oldleng];
+
+      for (; q != endq; p++, q++)
+       *p = *q;
+      free (oldmess);
+    }
+  Msgbuff = newmess;
+}
+
+static int laststatus = 0;
+
+/*
+ * mfwrite() - The only fuction you'll need to call to write out
+ *             a midi file.
+ *
+ * format      0 - Single multi-channel track
+ *             1 - Multiple simultaneous tracks
+ *             2 - One or more sequentially independent
+ *                 single track patterns
+ * ntracks     The number of tracks in the file.
+ * division    This is kind of tricky, it can represent two
+ *             things, depending on whether it is positive or negative
+ *             (bit 15 set or not).  If  bit  15  of division  is zero,
+ *             bits 14 through 0 represent the number of delta-time
+ *             "ticks" which make up a quarter note.  If bit  15 of
+ *             division  is  a one, delta-times in a file correspond to
+ *             subdivisions of a second similiar to  SMPTE  and  MIDI
+ *             time code.  In  this format bits 14 through 8 contain
+ *             one of four values - 24, -25, -29, or -30,
+ *             corresponding  to  the  four standard  SMPTE and MIDI
+ *             time code frame per second formats, where  -29
+ *             represents  30  drop  frame.   The  second  byte
+ *             consisting  of  bits 7 through 0 corresponds the the
+ *             resolution within a frame.  Refer the Standard MIDI
+ *             Files 1.0 spec for more details.
+ * fp          This should be the open file pointer to the file you
+ *             want to write.  It will have be a global in order
+ *             to work with Mf_putc.
+ */
+void
+mfwrite (format, ntracks, division, fp)
+     int format, ntracks, division;
+     FILE *fp;
+{
+  int i;
+  void mf_write_track_chunk (), mf_write_header_chunk ();
+
+  if (Mf_putc == NULLFUNC)
+    mferror ("mfmf_write() called without setting Mf_putc");
+
+  if (Mf_writetrack == NULLFUNC)
+    mferror ("mfmf_write() called without setting Mf_mf_writetrack");
+
+  laststatus = 0;
+
+  /* every MIDI file starts with a header */
+  mf_write_header_chunk (format, ntracks, division);
+
+  laststatus = 0;
+
+  /* In format 1 files, the first track is a tempo map */
+  if (format == 1 && (Mf_writetempotrack))
+    {
+      (*Mf_writetempotrack) ();
+    }
+
+  /* The rest of the file is a series of tracks */
+  for (i = 0; i < ntracks; i++)
+    mf_write_track_chunk (i, fp);
+}
+
+void
+mf_write_track_chunk (which_track, fp)
+     int which_track;
+     FILE *fp;
+{
+  unsigned long trkhdr, trklength;
+  long offset, place_marker;
+  void write16bit (), write32bit ();
+
+
+  laststatus = 0;
+
+  trkhdr = MTrk;
+  trklength = 0;
+
+  /* Remember where the length was written, because we don't
+          know how long it will be until we've finished writing */
+  offset = ftell (fp);
+
+#ifdef DEBUG
+  printf ("offset = %d\n", (int) offset);
+#endif
+
+  /* Write the track chunk header */
+  write32bit (trkhdr);
+  write32bit (trklength);
+
+  Mf_numbyteswritten = 0L;     /* the header's length doesn't count */
+
+  if (Mf_writetrack)
+    {
+      (*Mf_writetrack) (which_track);
+    }
+
+  /* mf_write End of track meta event */
+/* but this does not necessarily have a delta of 0, so
+ * I don't want to do it -- leave it up to the user of the
+ * library functions to do
+ *     --gl
+       eputc(0);
+       eputc(laststatus = meta_event);
+       eputc(end_of_track);
+
+       eputc(0);
+ */
+
+  /* It's impossible to know how long the track chunk will be beforehand,
+           so the position of the track length data is kept so that it can
+           be written after the chunk has been generated */
+  place_marker = ftell (fp);
+
+  /* This method turned out not to be portable because the
+           parameter returned from ftell is not guaranteed to be
+           in bytes on every machine */
+  /* track.length = place_marker - offset - (long) sizeof(track); */
+
+#ifdef DEBUG
+  printf ("length = %d\n", (int) trklength);
+#endif
+
+  if (fseek (fp, offset, 0) < 0)
+    mferror ("error seeking during final stage of write");
+
+  trklength = Mf_numbyteswritten;
+
+  /* Re-mf_write the track chunk header with right length */
+  write32bit (trkhdr);
+  write32bit (trklength);
+
+  fseek (fp, place_marker, 0);
+}                              /* End gen_track_chunk() */
+
+
+void
+mf_write_header_chunk (format, ntracks, division)
+     int format, ntracks, division;
+{
+  unsigned long ident, length;
+  void write16bit (), write32bit ();
+
+  ident = MThd;                        /* Head chunk identifier                    */
+  length = 6;                  /* Chunk length                             */
+
+  /* individual bytes of the header must be written separately
+       to preserve byte order across cpu types :-( */
+  write32bit (ident);
+  write32bit (length);
+  write16bit (format);
+  write16bit (ntracks);
+  write16bit (division);
+}                              /* end gen_header_chunk() */
+
+
+/*
+ * mf_write_midi_event()
+ *
+ * Library routine to mf_write a single MIDI track event in the standard MIDI
+ * file format. The format is:
+ *
+ *                    <delta-time><event>
+ *
+ * In this case, event can be any multi-byte midi message, such as
+ * "note on", "note off", etc.
+ *
+ * delta_time - the time in ticks since the last event.
+ * type - the type of meta event.
+ * chan - The midi channel.
+ * data - A pointer to a block of chars containing the META EVENT,
+ *        data.
+ * size - The length of the meta-event data.
+ */
+int
+mf_write_midi_event (delta_time, type, chan, data, size)
+     unsigned long delta_time;
+     int chan, type;
+     unsigned long size;
+     char *data;
+{
+  int i;
+  unsigned char c;
+
+  WriteVarLen (delta_time);
+
+  /* all MIDI events start with the type in the first four bits,
+       and the channel in the lower four bits */
+  if (type == system_exclusive || type == 0xf7)
+    {
+      c = type;
+      laststatus = 0;
+    }
+  else
+    c = type | chan;
+
+  if (chan > 15)
+    perror ("error: MIDI channel greater than 16\n");
+
+  if (laststatus != c)
+    eputc (laststatus = c);
+
+  if (type == system_exclusive || type == 0xf7)
+    WriteVarLen (size);
+
+  /* write out the data bytes */
+  for (i = 0; i < size; i++)
+    eputc (data[i]);
+
+  return (size);
+}                              /* end mf_write MIDI event */
+
+/*
+ * mf_write_meta_event()
+ *
+ * Library routine to mf_write a single meta event in the standard MIDI
+ * file format. The format of a meta event is:
+ *
+ *          <delta-time><FF><type><length><bytes>
+ *
+ * delta_time - the time in ticks since the last event.
+ * type - the type of meta event.
+ * data - A pointer to a block of chars containing the META EVENT,
+ *        data.
+ * size - The length of the meta-event data.
+ */
+int 
+mf_write_meta_event (delta_time, type, data, size)
+     unsigned long delta_time;
+     unsigned char *data, type;
+     unsigned long size;
+{
+  int i;
+
+  WriteVarLen (delta_time);
+
+  /* This marks the fact we're writing a meta-event */
+  eputc (laststatus = meta_event);
+
+  /* The type of meta event */
+  eputc (type);
+
+  /* The length of the data bytes to follow */
+  WriteVarLen (size);
+
+  for (i = 0; i < size; i++)
+    {
+      if (eputc (data[i]) != data[i])
+       return (-1);
+    }
+  return (size);
+}                              /* end mf_write_meta_event */
+
+void
+mf_write_tempo (delta_time, tempo)
+     unsigned long delta_time;
+     unsigned long tempo;
+{
+  /* Write tempo */
+  /* all tempos are written as 120 beats/minute, */
+  /* expressed in microseconds/quarter note     */
+
+  WriteVarLen (delta_time);
+  eputc (laststatus = meta_event);
+  eputc (set_tempo);
+
+  eputc (3);
+  eputc ((unsigned) (0xff & (tempo >> 16)));
+  eputc ((unsigned) (0xff & (tempo >> 8)));
+  eputc ((unsigned) (0xff & tempo));
+}
+
+void
+mf_write_seqnum (delta_time, seqnum)
+     unsigned long delta_time;
+     unsigned seqnum;
+{
+
+  WriteVarLen (delta_time);
+  eputc (laststatus = meta_event);
+  eputc (0);
+
+  eputc ((unsigned) (0xff & (seqnum >> 8)));
+  eputc ((unsigned) (0xff & seqnum));
+}
+
+unsigned long
+mf_sec2ticks (secs, division, tempo)
+     int division;
+     unsigned long tempo;
+     double secs;
+{
+  return (unsigned long) (((secs * 1000.0) / 4.0 * division) / tempo);
+}
+
+/*
+ * Write multi-length bytes to MIDI format files
+ */
+void
+WriteVarLen (value)
+     unsigned long value;
+{
+  unsigned long buffer;
+
+  buffer = value & 0x7f;
+  while ((value >>= 7) > 0)
+    {
+      buffer <<= 8;
+      buffer |= 0x80;
+      buffer += (value & 0x7f);
+    }
+  while (1)
+    {
+      eputc ((unsigned) (buffer & 0xff));
+
+      if (buffer & 0x80)
+       buffer >>= 8;
+      else
+       return;
+    }
+}                              /* end of WriteVarLen */
+
+/*
+ * This routine converts delta times in ticks into seconds. The
+ * else statement is needed because the formula is different for tracks
+ * based on notes and tracks based on SMPTE times.
+ *
+ */
+double
+mf_ticks2sec (ticks, division, tempo)
+     int division;
+     unsigned long tempo;
+     unsigned long ticks;
+{
+  double smpte_format, smpte_resolution;
+
+  if (division > 0)
+    return ((double) (((double) (ticks) * (double) (tempo)) / ((double) (division) * 1000000.0)));
+  else
+    {
+      smpte_format = upperbyte (division);
+      smpte_resolution = lowerbyte (division);
+      return (double) ((double) ticks / (smpte_format * smpte_resolution * 1000000.0));
+    }
+}                              /* end of ticks2sec() */
+
+
+/*
+ * write32bit()
+ * write16bit()
+ *
+ * These routines are used to make sure that the byte order of
+ * the various data types remains constant between machines. This
+ * helps make sure that the code will be portable from one system
+ * to the next.  It is slightly dangerous that it assumes that longs
+ * have at least 32 bits and ints have at least 16 bits, but this
+ * has been true at least on PCs, UNIX machines, and Macintosh's.
+ *
+ */
+void
+write32bit (data)
+     unsigned long data;
+{
+  eputc ((unsigned) ((data >> 24) & 0xff));
+  eputc ((unsigned) ((data >> 16) & 0xff));
+  eputc ((unsigned) ((data >> 8) & 0xff));
+  eputc ((unsigned) (data & 0xff));
+}
+
+void
+write16bit (data)
+     int data;
+{
+  eputc ((unsigned) ((data & 0xff00) >> 8));
+  eputc ((unsigned) (data & 0xff));
+}
+
+/* write a single character and abort on error */
+static int
+eputc (c)
+     unsigned char c;
+{
+  int return_val;
+
+  if ((Mf_putc) == NULLFUNC)
+    {
+      mferror ("Mf_putc undefined");
+      return (-1);
+    }
+
+  return_val = (*Mf_putc) (c);
+
+  if (return_val == EOF)
+    mferror ("error writing");
+
+  Mf_numbyteswritten++;
+  return (return_val);
+}
diff --git a/test/midifile.h b/test/midifile.h
new file mode 100644 (file)
index 0000000..189bd82
--- /dev/null
@@ -0,0 +1,132 @@
+/* definitions for MIDI file parsing code */
+extern int (*Mf_getc)();
+extern void (*Mf_header)();
+extern void (*Mf_trackstart)();
+extern void (*Mf_trackend)();
+extern void (*Mf_noteon)();
+extern void (*Mf_noteoff)();
+extern void (*Mf_pressure)();
+extern void (*Mf_parameter)();
+extern void (*Mf_pitchbend)();
+extern void (*Mf_program)();
+extern void (*Mf_chanpressure)();
+extern void (*Mf_sysex)();
+extern void (*Mf_metamisc)();
+extern void (*Mf_seqspecific)();
+extern void (*Mf_seqnum)();
+extern void (*Mf_text)();
+extern void (*Mf_eot)();
+extern void (*Mf_timesig)();
+extern void (*Mf_smpte)();
+extern void (*Mf_tempo)();
+extern void (*Mf_keysig)();
+extern void (*Mf_arbitrary)();
+extern void (*Mf_error)();
+extern unsigned long Mf_currtime;
+extern unsigned long Mf_realtime;
+extern unsigned long Mf_currtempo;
+extern int Mf_division;
+extern int Mf_nomerge;
+#ifdef READ_MODS
+extern unsigned char *Mf_file_contents;
+extern int Mf_file_size;
+#endif
+
+/* definitions for MIDI file writing code */
+extern int (*Mf_putc)();
+extern int (*Mf_writetrack)();
+extern int (*Mf_writetempotrack)();
+
+extern void midifile();
+extern unsigned long mf_sec2ticks();
+extern void mfwrite();
+extern int mf_write_meta_event();
+extern int mf_write_midi_event(unsigned long delta_time, int type,
+       int chan, char *data, unsigned long size);
+extern double mf_ticks2sec(unsigned long ticks,int division,unsigned long tempo);
+extern void mf_write_tempo();
+extern void mf_write_seqnum();
+extern void mfread();
+extern void mferror(char *s);
+
+#ifndef NO_LC_DEFINES
+/* MIDI status commands most significant bit is 1 */
+#define note_off               0x80
+#define note_on                0x90
+#define poly_aftertouch        0xa0
+#define control_change         0xb0
+#define program_chng           0xc0
+#define channel_aftertouch      0xd0
+#define pitch_wheel            0xe0
+#define system_exclusive       0xf0
+#define delay_packet           (1111)
+
+/* 7 bit controllers */
+#define damper_pedal            0x40
+#define portamento             0x41    
+#define sostenuto              0x42
+#define soft_pedal             0x43
+#define general_4               0x44
+#define        hold_2                  0x45
+#define        general_5               0x50
+#define        general_6               0x51
+#define general_7              0x52
+#define general_8              0x53
+#ifndef PLAYMIDI
+#define tremolo_depth          0x5c
+#define ctrl_chorus_depth       0x5d
+#define        detune                  0x5e
+#define phaser_depth           0x5f
+#endif
+
+/* parameter values */
+#define data_inc               0x60
+#define data_dec               0x61
+
+/* parameter selection */
+#define non_reg_lsb            0x62
+#define non_reg_msb            0x63
+#define reg_lsb                        0x64
+#define reg_msb                        0x65
+
+/* Standard MIDI Files meta event definitions */
+#define        meta_event              0xFF
+#define        sequence_number         0x00
+#define        text_event              0x01
+#define copyright_notice       0x02
+#define sequence_name          0x03
+#define instrument_name        0x04
+#define lyric                  0x05
+#define marker                 0x06
+#define        cue_point               0x07
+#define channel_prefix         0x20
+#define        end_of_track            0x2f
+#define        set_tempo               0x51
+#define        smpte_offset            0x54
+#define        time_signature          0x58
+#define        key_signature           0x59
+#define        sequencer_specific      0x74
+
+/* Manufacturer's ID number */
+#define Seq_Circuits (0x01) /* Sequential Circuits Inc. */
+#define Big_Briar    (0x02) /* Big Briar Inc.           */
+#define Octave       (0x03) /* Octave/Plateau           */
+#define Moog         (0x04) /* Moog Music               */
+#define Passport     (0x05) /* Passport Designs         */
+#define Lexicon      (0x06) /* Lexicon                         */
+#define Tempi        (0x20) /* Bon Tempi                */
+#define Siel         (0x21) /* S.I.E.L.                 */
+#define Kawai        (0x41) 
+#define Roland       (0x42)
+#define Korg         (0x42)
+#define Yamaha       (0x43)
+#endif
+
+/* miscellaneous definitions */
+#define MThd 0x4d546864
+#define MTrk 0x4d54726b
+
+#ifndef NO_LC_DEFINES
+#define lowerbyte(x) ((unsigned char)(x & 0xff))
+#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8))
+#endif
diff --git a/test/playmidi1.c b/test/playmidi1.c
new file mode 100644 (file)
index 0000000..8246301
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ *   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>
+ *
+ *
+ *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/* define this if you want to send real-time time stamps instead of midi ticks to the ALSA sequencer */
+/*#define USE_REALTIME */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "midifile.h"          /* SMF library header */
+#include "midifile.c"          /* SMF library code */
+
+#include "sys/asoundlib.h"
+
+//#define DEST_QUEUE_NUMBER 0
+#define DEST_QUEUE_NUMBER 7
+#define DEST_CLIENT_NUMBER 64
+//#define DEST_CLIENT_NUMBER 72
+//#define DEST_CLIENT_NUMBER 128
+//#define DEST_CLIENT_NUMBER 255
+//#define DEST_CLIENT_NUMBER SND_SEQ_ADDRESS_BROADCAST
+
+FILE *F;
+void* seq_handle = NULL;
+int ppq = 96;
+
+double local_secs = 0;
+int local_ticks = 0;
+int local_tempo = 500000;
+
+static int dest_queue = DEST_QUEUE_NUMBER;
+static int dest_client = DEST_CLIENT_NUMBER;
+static int dest_port = 0;
+//static int dest_port = 1;
+static int source_channel = 0;
+static int source_port = 0;
+
+
+extern void alsa_start_timer(void);
+extern void alsa_stop_timer(void);
+
+
+static inline double tick2time_dbl(int tick)
+{
+       return local_secs + ((double) (tick - local_ticks) * (double) local_tempo * 1.0E-9 / (double) ppq);
+}
+
+#ifdef USE_REALTIME
+static void tick2time(snd_seq_real_time_t * tm, int tick)
+{
+       double secs = tick2time_dbl(tick);
+
+       //double secs = ((double) tick * (double) local_tempo * 1.0E-6 / (double) ppq);
+
+       tm->tv_sec = secs;
+       tm->tv_nsec = (secs - tm->tv_sec) * 1.0E9;
+
+       //printf("secs = %lf  = %d.%09d\n", secs, tm->tv_sec, tm->tv_nsec);
+}
+
+#endif
+
+/* sleep until sequencer has reached specified timestamp, to guard that we play too much events ahead */
+void sleep_seq(int tick)
+{
+#if 0
+  snd_seq_queue_info_t queue_info;
+  const int COUNT_MAX = 500;
+  const int COUNT_MIN = 50;
+  static int count = 0;
+  count++;
+  if (count >= COUNT_MAX)
+    {
+      while (snd_seq_flush_output(seq_handle) > COUNT_MIN)
+       sched_yield ();
+      count = 0;
+    }
+#endif
+}
+
+
+/* write event to ALSA sequencer */
+void write_ev_im(snd_seq_event_t * ev)
+{
+       int written;
+
+       ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
+       ev->flags |= SND_SEQ_EVENT_LENGTH_FIXED;
+       
+
+       written = -ENOMEM;
+       while (written<0) {
+         written = snd_seq_event_output (seq_handle, ev);
+               if (written<0) {
+                 printf("written = %i (%s)\n", written, strerror(-written));
+                 sleep(1);
+               }
+       }
+}
+
+/* write event to ALSA sequencer */
+void write_ev(snd_seq_event_t * ev)
+{
+  sleep_seq(ev->time.tick-ppq);
+  write_ev_im(ev);
+}
+
+/* write variable length event to ALSA sequencer */
+void write_ev_var(snd_seq_event_t * ev, int len, void *ptr)
+{
+       int bytes;
+       int written;
+       unsigned char *buf;
+
+       sleep_seq(ev->time.tick+ppq);
+
+       ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK;
+       ev->flags |= SND_SEQ_EVENT_LENGTH_VARIABLE;
+       ev->data.ext.len = len;
+       ev->data.ext.ptr = ptr;
+
+       written = 0;
+       while (!written) {
+                 written = snd_seq_event_output (seq_handle, ev);
+               if (!written)
+                       sleep(1);
+       }
+}
+
+
+int mygetc(void)
+{
+       return (getc(F));
+}
+
+void mytext(int type, int leng, char *msg)
+{
+       char *p;
+       char *ep = msg + leng;
+
+       for (p = msg; p < ep; p++)
+               putchar(isprint(*p) ? *p : '?');
+       putchar('\n');
+}
+
+void do_header(int format, int ntracks, int division)
+{
+       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");
+               alsa_stop_timer();
+               exit(1);
+       }
+       /* set ppq */
+       {
+               snd_seq_queue_info_t queue_info;
+
+               queue_info.queue = 1;   /* queue we're using */
+               queue_info.ppq = ppq;
+               //queue_info.tempo = -1;        /* don't change */
+               queue_info.tempo = 500000;      /* don't change */
+               if (snd_seq_set_queue_info (seq_handle, dest_queue, &queue_info) < 0) {
+                       perror("ioctl");
+                       exit(1);
+               }
+               printf("ALSA Timer updated, PPQ = %d\n", queue_info.ppq);
+       }
+
+       /* start playing... */
+       alsa_start_timer();
+}
+
+void do_tempo(int us)
+{
+       double bpm;
+       snd_seq_event_t ev;
+
+       bpm = 60.0E6 / (double) us;
+
+       printf("tempo = %d us/beat\n", us);
+       printf("tempo = %.2f bpm\n", bpm);
+
+       /* store new tempo and timestamp of tempo change */
+       local_secs = tick2time_dbl(Mf_currtime);
+       local_ticks = Mf_currtime;
+       local_tempo = us;
+
+
+       /* and send tempo change event to the sequencer.... */
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       //ev.dest.client = dest_client; /* broadcast */
+       ev.dest.client = 255;   /* broadcast */
+       ev.dest.port = 0;
+       ev.dest.channel = 0;    /* don't care */
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_TEMPO;
+       ev.data.control.value = us;
+
+       write_ev_im(&ev);
+
+}
+
+void do_noteon(int chan, int pitch, int vol)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_NOTEON;
+       ev.data.note.note = pitch;
+       ev.data.note.velocity = vol;
+
+       write_ev(&ev);
+
+}
+
+
+void do_noteoff(int chan, int pitch, int vol)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_NOTEOFF;
+       ev.data.note.note = pitch;
+       ev.data.note.velocity = vol;
+
+       write_ev(&ev);
+}
+
+
+void do_program(int chan, int program)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_PGMCHANGE;
+       ev.data.control.value = program;
+
+       write_ev_im(&ev);
+}
+
+
+void do_parameter(int chan, int control, int value)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_CONTROLLER;
+       ev.data.control.param = control;
+       ev.data.control.value = value;
+
+       write_ev(&ev);
+}
+
+
+void do_pitchbend(int chan, int lsb, int msb)
+{                              /* !@#$% lsb & msb are in wrong order in docs */
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_PITCHBEND;
+       ev.data.control.value = (lsb + (msb << 7)) - 8192;
+
+       write_ev(&ev);
+}
+
+void do_pressure(int chan, int pitch, int pressure)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_KEYPRESS;
+       ev.data.control.param = pitch;
+       ev.data.control.value = pressure;
+
+       write_ev(&ev);
+}
+
+void do_chanpressure(int chan, int pressure)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = chan;
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_CHANPRESS;
+       ev.data.control.value = pressure;
+
+       write_ev(&ev);
+}
+
+void do_sysex(int len, char *msg)
+{
+       snd_seq_event_t ev;
+
+#if 0
+       int c;
+
+       printf("Sysex, len=%d\n", len);
+       for (c = 0; c < len; c++) {
+               printf("    %3d : %02x\n", c, (unsigned char) msg[c]);
+       }
+#endif
+
+
+       ev.source.port = dest_port;
+       ev.source.channel = source_channel;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = dest_client;
+       ev.dest.port = dest_port;
+       ev.dest.channel = 0;    /* don't care */
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_SYSEX;
+
+       write_ev_var(&ev, len, msg);
+}
+
+/**/
+void alsa_sync ()
+{
+  //dump buffer
+  int left = snd_seq_flush_output (seq_handle);
+  printf ("alsa_sync syncing\n");
+  while (left > 0)
+    {
+      sched_yield ();
+      left = snd_seq_flush_output (seq_handle);
+      if (left < 0)
+       {
+         printf ("alsa_sync error!:%s\n", snd_strerror (left));
+         return;
+       }
+    }
+
+  
+  for (;;)
+    {
+      snd_seq_queue_info_t queue_info;
+      int tmp = snd_seq_get_queue_info (seq_handle, dest_queue, &queue_info);
+      if (tmp < 0)
+        {
+          printf ("AlsaClient::sync snd_seq_get_queue_info:%s\n",
+                 snd_strerror (tmp));
+          break;
+       }
+      
+      if (Mf_currtime < queue_info.tick)
+       break;
+      sched_yield ();
+    }
+}
+
+
+/* start timer */
+void alsa_start_timer(void)
+{
+       snd_seq_event_t ev;
+
+       ev.source.port = SND_SEQ_PORT_SYSTEM_TIMER;
+       ev.source.channel = 0;
+
+       ev.dest.queue = dest_queue;
+       //ev.dest.client = 0;   /* system */
+       ev.dest.client = SND_SEQ_CLIENT_SYSTEM; /* system */
+       //ev.dest.client = dest_client; /* broadcast */
+       ev.dest.client = 255;   /* broadcast */
+       ev.dest.port = 0;       /* timer */
+       ev.dest.channel = 0;    /* don't care */
+
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
+       //ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL;
+       ev.time.real.tv_sec = 0;
+       ev.time.real.tv_nsec = 0;
+
+       ev.type = SND_SEQ_EVENT_START;
+
+       write_ev_im(&ev);
+       usleep(0.1E6);
+}
+
+/* stop timer */
+void alsa_stop_timer(void)
+{
+
+       snd_seq_event_t ev;
+
+       ev.source.port = 0;
+       ev.source.channel = 0;
+
+       ev.dest.queue = dest_queue;
+       ev.dest.client = 0;     /* system */
+       ev.dest.client = dest_client;   /* broadcast */
+       ev.dest.port = 0;       /* timer */
+       ev.dest.channel = 0;    /* don't care */
+
+#ifdef USE_REALTIME
+       ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS;
+       tick2time(&ev.time.real, Mf_currtime);
+#else
+       ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS;
+       ev.time.tick = Mf_currtime;
+#endif
+
+       ev.type = SND_SEQ_EVENT_STOP;
+
+       write_ev_im(&ev);
+}
+
+int main(int argc, char *argv[])
+{
+       char *name;
+       snd_seq_client_info_t inf;
+       snd_seq_port_info_t src_port_info;
+       snd_seq_queue_client_t queue_info;
+       snd_seq_port_subscribe_t subscribe;
+       int tmp;
+
+#ifdef USE_REALTIME
+       printf("ALSA MIDI Player, feeding events to real-time queue\n");
+#else
+       printf("ALSA MIDI Player, feeding events to song queue\n");
+#endif
+
+       /* open sequencer device */
+       //tmp = snd_seq_open (&seq_handle, SND_SEQ_OPEN_OUT);
+       tmp = snd_seq_open (&seq_handle, SND_SEQ_OPEN);
+       if (tmp < 0) {
+               perror("open /dev/snd/seq");
+               exit(1);
+       }
+       
+       tmp = snd_seq_block_mode (seq_handle, 0);
+       if (tmp < 0)
+         {
+           perror ("block_mode");
+           exit (1);
+         }
+       
+               
+       /* set name */
+       memset(&inf, 0, sizeof(snd_seq_client_info_t));
+       strcpy(inf.name, "MIDI file player");
+       if (snd_seq_set_client_info (seq_handle, &inf) < 0) {
+               perror("ioctl");
+               exit(1);
+       }
+
+       //create port
+       memset (&src_port_info, 0, sizeof (snd_seq_port_info_t));
+       src_port_info.capability = SND_SEQ_PORT_CAP_OUT | SND_SEQ_PORT_CAP_SUBSCRIPTION;
+       src_port_info.type = SND_SEQ_PORT_TYPE_MIDI_GENERIC;
+       src_port_info.midi_channels = 16;
+       src_port_info.synth_voices = 0;
+       src_port_info.use = 0;
+       src_port_info.kernel = NULL;
+       tmp = snd_seq_create_port (seq_handle, &src_port_info);
+       if (tmp < 0)
+         {
+           perror ("creat port");
+           exit (1);
+         }
+       
+       //setup queue
+       queue_info.used = 1;
+       //queue_info.low = 100;//???
+       //queue_info.low = SND_SEQ_MAX_EVENTS-1;//???
+       //queue_info.low = 500-1;//???
+       //queue_info.low = 1;//???
+       queue_info.low = 0;
+       //queue_info.low = 0x7ffffff;//???
+       //queue_info.high = 0x7ffffff;//???
+       queue_info.high = 500-100;//???
+       //queue_info.high = 50;//???
+       //queue_info.high = 0;//???
+       //queue_info.high = 1;//???
+       tmp = snd_seq_set_queue_client (seq_handle, dest_queue,
+                                       &queue_info);
+       if (tmp < 0)
+         {
+           perror ("queue_client");
+           exit (1);
+         }
+#if (DEST_CLIENT_NUMBER == 64) || (DEST_CLIENT_NUMBER == 72)
+       //setup subscriber
+       printf ("debug subscribe src_port_info.client=%d\n",
+               src_port_info.client);
+       subscribe.sender.client = snd_seq_client_id (seq_handle);
+       subscribe.sender.queue = dest_queue;
+       subscribe.sender.port = src_port_info.port;
+       subscribe.dest.client = dest_client;
+       subscribe.dest.port = dest_port;
+       subscribe.dest.queue = dest_queue;
+       subscribe.realtime = 1;
+       subscribe.exclusive = 0;
+       tmp = snd_seq_subscribe_port (seq_handle, &subscribe);
+       if (tmp < 0)
+         {
+           perror ("subscribe");
+           exit (1);
+         }
+#endif
+       
+       if (argc > 1)
+               F = fopen(argv[1], "r");
+       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;
+
+
+       /* stop timer in case it was left running by a previous client */
+       {
+               snd_seq_event_t ev;
+
+               ev.source.port = 0;
+               ev.source.channel = 0;
+
+               ev.dest.queue = dest_queue;
+               ev.dest.client = 0;     /* system */
+               //ev.dest.client = dest_client; /* broadcast */
+               ev.dest.client = 255;   /* broadcast */
+               ev.dest.port = 0;       /* timer */
+               ev.dest.channel = 0;    /* don't care */
+
+               ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL;
+               //ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL;
+               ev.time.real.tv_sec = 0;
+               ev.time.real.tv_nsec = 0;
+               
+               //ev.type = SND_SEQ_EVENT_STOP;
+               ev.type = SND_SEQ_EVENT_START;
+
+               write_ev(&ev);
+       }
+       alsa_start_timer ();
+       
+       /* go.. go.. go.. */
+       mfread();
+
+       alsa_sync ();
+       alsa_stop_timer();
+
+       snd_seq_close (seq_handle);
+
+       printf("Stopping at %lf s,  tick %d\n", tick2time_dbl(Mf_currtime + 1), Mf_currtime + 1);
+
+       exit(0);
+}