-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
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
+
--- /dev/null
+.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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/* 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
--- /dev/null
+/*
+ * 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);
+}