summaryrefslogtreecommitdiffstats
path: root/alsa-lib/test/midifile.c
diff options
context:
space:
mode:
Diffstat (limited to 'alsa-lib/test/midifile.c')
-rw-r--r--alsa-lib/test/midifile.c1173
1 files changed, 1173 insertions, 0 deletions
diff --git a/alsa-lib/test/midifile.c b/alsa-lib/test/midifile.c
new file mode 100644
index 0000000..8d6ba90
--- /dev/null
+++ b/alsa-lib/test/midifile.c
@@ -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 available 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_exclusive, 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>
+
+#include <string.h>
+/*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 function 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 similar 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 < (int)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 < (int)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);
+}