| /* |
| * 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); |
| } |