arensb@alv.umd.edu (Andrew Arensburger) (02/19/91)
Submitted-by: Andrew Arensburger <arensb@alv.umd.edu> Posting-number: Volume 17, Issue 2 Archive-name: midifile/part01 This is 'midifile()', a parser for MIDI files. This is functionally identical to the 'midifile()' written by Tim Thompson, and recently posted to the net. I wasn't quite satisfied with his implementation, so I rewrote it. midifile() reads in a MIDI format file, and calls user-defined routines each time it encounters an event. You can ignore any events you're not interested in, and concentrate on the ones you want. In fact, you can even ignore the tracks you're not interested in. Why should you use my version? This midifile() deals less traumatically with errors: by default, it returns an error code to the calling routine, instead of exit()ing the program (so you can do that with the original, too. Big deal). Also, this version allows you to return an error code if and when you do interrupt the routine's execution. I've added a new variable: Mf_deltatime, which gives the time elapsed since the previous event. A complete and (hopefully) accurate man page is also supplied. Why should you use the original version (Classic midifile())? Because you have system-exclusive events in your MIDI file. I haven't implemented these, but will do so in the next version, scheduled to come out RSN. Andrew Arensburger arensb@cvl.umd.edu ----- CUT HERE ------------------------------------------------------- : Run this shell script with "sh" not "csh" PATH=:/bin:/usr/bin:/usr/ucb export PATH all=FALSE if [ x$1 = x-a ]; then all=TRUE fi /bin/echo 'Making directory "midifile"' mkdir midifile /bin/echo 'Extracting midifile/Makefile' sed 's/^X//' <<'//go.sysin dd *' >midifile/Makefile # Makefile for 'midifile()'. It's pretty trivial, really. all: midifile midifile: mf.c mf.h cc -c mf.c //go.sysin dd * if [ `wc -c < midifile/Makefile` != 106 ]; then made=FALSE /bin/echo 'error transmitting "midifile/Makefile" --' /bin/echo 'length should be 106, not' `wc -c < midifile/Makefile` else made=TRUE fi if [ $made = TRUE ]; then /bin/chmod 664 midifile/Makefile /bin/echo -n ' '; /bin/ls -ld midifile/Makefile fi /bin/echo 'Extracting midifile/mf.c' sed 's/^X//' <<'//go.sysin dd *' >midifile/mf.c X/* MF.C v.1.2 * A MIDI file analyzer. The routine 'midifile()' reads in a MIDI file, * calling external routines as it goes along, processing it. 'midifile()' * returns 0 if all went well, and an error code otherwise. * This program is based on Tim Thompson's 'midifile()'. I rewrote * it because I didn't like the way he did it. This one should be 100% * compatible with his. * The differences between this 'midifile()' and Thompson's are as * follows: this one does not 'exit()' from the program as soon as it finds * an error. Instead, it causes the main routine, 'midifile()' to return * a negative error code. * In addition to 'Mf_currtime', which gives the time elapsed since * the beginning of the track, I have added another variable: 'Mf_deltatime', * which gives the time elapsed since the last event. This is because the * MPU-401 requires delta-times, and not absolute times. * * IMPLEMENTATION NOTES: * About 'bailout()': this macro calls the user-defined error routine, * passing it an error message. * a) DO NOT PUT A SEMICOLON AFTER IT, i.e. * bailout(MFE_EOF) * b) It should be called as soon as the error is detected, and * not at lower levels, otherwise, the error routine will be called * several times for the same error. A routine is expected to return * a negative value in case of error, so test for that first. */ #include <stdio.h> #include "mf.h" extern char *malloc(); typedef unsigned char uchar; typedef unsigned long ulong; typedef unsigned short ushort; long Mf_currtime; /* Current time */ long Mf_deltatime; /* Time since last event */ int eot_called; /* Flag: has 'Mf_eot()' been called? */ X/* NUM_PARAMS * Returns the number of parameters for a given status byte, or -1 for * SysEx and meta-messages, which have a variable number of arguments. */ static int num_params(stat) uchar stat; { if (stat >= MIDI_SYSEX) return(-1); /* SysEx or meta */ if (stat < MIDI_PROGRAM || stat >= MIDI_PITCHB) return(2); else return(1); } X/* READ_LONG * Reads a 32-bit unsigned number and returns it. MSB first. * Returns an error code upon error. */ static long read_long() { ulong retval; int i; int c; retval = 0L; /* Set 'retval to 0 initially */ for (i = 0; i < 4; i++) /* Read in 4 bytes into 'retval' */ { if ((c = (*Mf_getc)()) == -1) bailout(MFE_EOF) else { retval <<= 8; retval |= (uchar) c; } } return(retval); /* All went well */ } X/* READ_SHORT * Reads a 16-bit number, MSB first, and returns it. * Returns an error code upon error. */ static short read_short() { ushort retval; int i; int c; retval = 0x00; /* Set 'retval to 0 initially */ for (i = 0; i < 2; i++) /* Read in 2 bytes into 'retval' */ { if ((c = (*Mf_getc)()) == -1) bailout(MFE_EOF) else { retval <<= 8; retval |= (uchar) c; } } return(retval); /* All went well */ } X/* STOL * Converts a string of 'len' bytes, MSB first, into a long int. This is * not to be confused with 'atoi()': 'atoi()' converts "abcdef12" into * '0xabcdef12L', whereas 'stol()' converts '0xab,0xcd,0xef,0x12' into * '0xabcdef12L'. */ ulong stol(str,len) uchar *str; int len; { int i; long retval; retval = 0L; for (i = 0; i < len; i++) { retval <<= 8; retval |= str[i]; } return(retval); } X/* READ_VARLEN * Reads a variable-length number, and returns it. * Returns an error code upon error. * This routine is copied almost verbatim from the MIDI 0.6 file specs. */ static long read_varlen(toberead) long *toberead; { long value; int c; if ((value = (*Mf_getc)()) < 0) bailout(value) /* Return error code */ else { if (--*toberead < 0) bailout(MFE_EOF); /* Error: number expected */ if (value & 0x80) { value &= 0x7f; do { if ((c = (*Mf_getc)()) < 0) bailout(c) /* Return error code */ else if (--*toberead < 0) bailout(MFE_EOF); value = (value << 7) + (c & 0x7f); } while (c & 0x80); } } return(value); } X/* READ_HEADER * Reads in the header from the input file, using the function 'Mf_getc()'. * Returns the number of tracks in the file if all goes well, or an error * code otherwise (including if the user disapproves). */ static int read_header() { int i; static char kw_buff[4]; /* Buffer for input keyword "MThd" */ long header_len; /* Header length */ short format; /* File format */ short ntrks; /* Number of tracks in the file */ short division; /* Quarter-note division */ int retval = 0; /* Read in the first four characters into 'kw_buff' */ for (i = 0; i < 4; i++) { int c; /* Input character */ if ((c = (*Mf_getc)()) == -1) bailout(MFE_EOF) else kw_buff[i] = (char) c; } /* See if 'kw_buff' contains "MThd" */ if (strncmp(kw_buff,"MThd",4) != 0) bailout(MFE_HKWEXP) /* Read the number 6 (header length) from the header */ if ((header_len = read_long()) < 0) return(header_len); /* Return error code */ else if (header_len != 6L) bailout(MFE_ILLHLEN) /* Incorrect header length */ /* Read the file format, number of tracks, and quarter-note division */ if ((format = read_short()) < 0) bailout(format) if (format > 2) /* Illegal file format */ bailout(MFE_ILLFORM) if ((ntrks = read_short()) < 0) return(ntrks); /* Return error code */ if ((division = read_short()) < 0) return(division); /* Return error code */ /* Pass on the header info to the user */ if (Mf_header == NULL || (retval = (*Mf_header)(format,ntrks,division)) >= 0) return(ntrks); else return(retval); /* User already knows about error */ } X/* READ_SYSEX * Read a system-exclusive/meta message, and pass it on to the user. * Warning: the buffer in which the SysEx data is stored will be reclaimed, * so the user is responsible for storing it if it is needed. * BTW, I'm doing some funky stuff with 'Mf_deltatime', so read the * comments at the beginning of 'read_event()'. */ static int read_sysex(stat,toberead) uchar stat; /* Type of SysEx message */ long *toberead; /* # bytes remaining to be read in track */ { char *data; /* Where the data is kept */ long length; /* Length of message */ long i; if (stat == 0xff) /* Meta message */ { int msg_type; /* Type of meta-event */ /* Read type of meta-event */ if ((msg_type = (*Mf_getc)()) < 0 || --*toberead < 0) bailout(MFE_EOF) /* Event type expected */ /* Read length of meta-event */ if ((length = read_varlen(toberead)) < 0) return((int) length); /* Allocate memory for meta-event */ if (length != 0L && (data = malloc((unsigned) length)) == NULL) bailout(MFE_MEM) else if (length == 0L) data = NULL; /* Read in the meta-event */ for (i = 0; i < length; i++) { int c; /* Input character */ if ((c = (*Mf_getc)()) < 0 || --*toberead < 0) { free(data); /* Free up the memory */ bailout(MFE_EOF) } data[i] = (char) c; } /* Take action according to the type of meta-event. * In particular, make sure that the length of the * meta-event is correct (where applicable). */ switch(msg_type) { case META_SEQNUM: /* Sequence number */ { short seqnum; int retval; if (length != 2) /* Check length */ bailout(MFE_ILLMLEN) seqnum = ((short) data[0] << 8) | data[1]; if (Mf_seqnum != NULL && (retval = (*Mf_seqnum)(seqnum), Mf_deltatime = 0L, retval) < 0) { free(data); return(retval); } break; } case META_TEXT: /* Text event */ case META_COPYRIGHT: /* Copyright notice */ case META_SEQNAME: /* Sequence name */ case META_INSTNAME: /* Instrument name */ case META_LYRIC: /* Lyric */ case META_MARKER: /* Marker */ case META_CUEPT: /* Cue point */ { int retval; if (Mf_text != NULL && (retval = (*Mf_text)((int) msg_type, length,data), Mf_deltatime = 0L, retval) < 0) { if (data != NULL) free(data); return(retval); } break; } case META_EOT: /* End of track */ { int retval; if (length != 0L) /* Check length */ bailout(MFE_ILLMLEN) if (Mf_eot != NULL && (retval = (*Mf_eot)(), Mf_deltatime = 0L, retval) < 0) { free(data); return(retval); } eot_called = 1; break; } case META_TEMPO: /* Set tempo */ { ulong tempo; int retval; if (length != 3) /* Check length */ bailout(MFE_ILLMLEN) tempo = /**(ushort *) data;*/ stol(data,3); if (Mf_tempo != NULL && (retval = (*Mf_tempo)(tempo), Mf_deltatime = 0L, retval) < 0) { free(data); return(retval); } break; } case META_SMPTE: /* SMPTE offset */ { int retval; int hour,min,sec,frame,fract; if (length != 5) bailout(MFE_ILLMLEN) hour = (int) data[0]; min = (int) data[1]; sec = (int) data[2]; frame = (int) data[3]; fract = (int) data[4]; if (Mf_smpte != NULL && (retval = (*Mf_smpte)(hour,min, sec,frame,fract), Mf_deltatime = 0L, retval) < 0) { free(data); return(retval); } break; } case META_TIMESIG: /* Time signature */ { int retval; int numer,denom,clocks,qnotes; if (length != 4) bailout(MFE_ILLMLEN) numer = (int) data[0]; denom = (int) data[1]; clocks = (int) data[2]; qnotes = (int) data[3]; if (Mf_timesig != NULL && (retval = (*Mf_timesig)(numer, denom,clocks,qnotes), Mf_deltatime = 0L, retval) < 0) { free(data); return(retval); } break; } case META_KEYSIG: /* Key signature */ { int retval; int sharpflat,minor; if (length != 2) bailout(MFE_ILLMLEN) sharpflat = (int) data[0]; minor = (int) data[1]; if (sharpflat > 7 || sharpflat < -7 || (minor != 0 && minor != 1)) bailout(MFE_ILLMVAL) if (Mf_keysig != NULL && (retval = (*Mf_keysig)(sharpflat, minor), Mf_deltatime = 0L, retval) < 0) { free(data); return(retval); } break; } case META_SEQSPEC: /* Sequencer-specific */ { int retval; if (Mf_sqspecific != NULL && (retval = (*Mf_sqspecific)(0, length,data), Mf_deltatime = 0L, retval) < 0) { if (data != NULL) free(data); return(retval); } break; } default: { int retval; if (Mf_error != NULL && (retval = (*Mf_error) (err_msgs[MFE_UKMETA])) < 0) { if (data != NULL) free(data); return(retval); } break; } } /* All went well. Free 'data' if necessary, and return */ if (data != NULL) free(data); return(MFE_OK); } if (stat == 0xf0) /* Syntactically complete message */ { fprintf(stderr, "Sorry, no complete SysEx messages\n"); } if (stat == 0xf7) /* Part of a complete SysEx message */ { fprintf(stderr, "Sorry, no incomplete SysEx messages\n"); } bailout(MFE_ILLSETYPE) } X/* READ_EVENT * Read an event, parse it by type, and pass the data to the user's * routines. * Returns MFE_OK if all goes well, or an error code otherwise. * A comment about the funky stuff I'm doing with 'Mf_deltatime': * it turned out that if the user didn't check for _every_ event, * then 'Mf_deltatime' got screwed up. In other words, if you didn't * trap pitch-bends, then any pitch-bends in the file, >>> and their * associated deltas <<< would be ignored. This is obviously a badism. * To solve this, 'Mf_deltatime' is always incremented by the current * delta, in 'read_event()'. Later, when the event is processed, * 'Mf_deltatime' gets reset to 0 iff the handling routine has been * called. * The specific mechanism is a bit funky, so here's an example: * if (Mf_off != NULL && * (retval = (*Mf_off)(run_stat & 0x07, * params[0], * params[1]), * Mf_deltatime = 0, * retval) < 0) * return(retval); * This uses two particularities of C: lazy evaluation, and statements * separated by commas. If 'Mf_off' is NULL, then the event is not * important to the user, and 'Mf_deltatime' is kept intact for the next * event. Otherwise, the following sequence of events occurs: * - The user's routine is called, and the return value is stored * in 'retval'. * - 'Mf_deltatime' gets reset to 0. * - 'retval' is called to the forefront, just in time to see * whether the user's routine returned an error value. */ static int read_event(toberead) long *toberead; /* # bytes left to be read in the track */ { static uchar run_stat = 0x00; /* Running status */ int stat; /* Current status */ long delta_time; /* Time since last event */ static uchar params[2]; /* Event parameters */ int cur_param; /* Parameter being currently read */ int i; /* Read delta-time */ if ((delta_time = read_varlen(toberead)) < 0) return((int) delta_time); /* Return error code */ /* Update 'Mf_currtime' and 'Mf_deltatime' */ Mf_currtime += Mf_deltatime += delta_time; /* Read event type */ if ((stat = (*Mf_getc)()) < 0 || --*toberead < 0) bailout(MFE_EOF) /* Error: event type expected */ if (stat & 0x80) /* Is it a new event type? */ { run_stat = (uchar) stat; /* Set new running status */ cur_param = 0; /* Start reading at 0th param */ } else { params[0] = (uchar) stat; /* Record 1st parameter */ cur_param = 1; /* Start reading at 1st param */ } /* Read the parameters corresponding to the status byte */ for (i = num_params(run_stat)-cur_param; i > 0; i--, cur_param++) { int c; /* Input character */ if ((c = (*Mf_getc)()) < 0 || --*toberead < 0) bailout(MFE_EOF); /* Premature end of file */ params[cur_param] = (uchar) c; /* Record parameter */ } /* Report event and parameters to user, depending on type of * event. */ switch(run_stat & 0xf0) { int retval; case MIDI_NOTEOFF: /* Note off */ if (Mf_off != NULL && (retval = (*Mf_off)(run_stat & 0x07, params[0], params[1]), Mf_deltatime = 0, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_NOTEON: /* Note on */ if (Mf_on != NULL && (retval = (*Mf_on)(run_stat & 0x07, params[0], params[1]), Mf_deltatime = 0L, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_PRESSURE: /* Polyphonic key pressure */ if (Mf_pressure != NULL && (retval = (*Mf_pressure)(run_stat & 0x07, params[0], params[1]), Mf_deltatime = 0L, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_CONTROL: /* Control change */ if (Mf_controller != NULL && (retval = (*Mf_controller)(run_stat & 0x07, params[0], params[1]), Mf_deltatime = 0L, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_PROGRAM: /* Program change */ if (Mf_program != NULL && (retval = (*Mf_program)(run_stat & 0x07, params[0]), Mf_deltatime = 0L, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_CHANPRES: /* Channel pressure */ if (Mf_chanpressure != NULL && (retval = (*Mf_chanpressure)(run_stat & 0x07, params[0]), Mf_deltatime = 0L, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_PITCHB: /* Pitch wheel change */ if (Mf_pitchbend != NULL && (retval = (*Mf_pitchbend)(run_stat & 0x07, params[0], params[1]), Mf_deltatime = 0L, retval) < 0) return(retval); /* Return error code */ return(MFE_OK); case MIDI_SYSEX: /* System-exclusive/meta */ return(read_sysex(run_stat,toberead)); default: bailout(MFE_ILLSTAT) } } X/* READ_TRACK * Reads in a track, parses it, and passes the various parts on to the * user's routines. * Returns MFE_OK if all goes well, or an error code if anything goes wrong. */ static int read_track() { int i; static char kw_buff[4]; /* Buffer for input keyword "MTrk" */ int error; long tklen; /* Track length */ /* Reset 'Mf_currtime'. If the file format is 2, then it probably * doesn't matter anyway, but for the other types (single and * multiple tracks), 'Mf_currtime' should measure the time elapsed * since the beginning of the track. */ Mf_currtime = 0L; eot_called = 0; /* Indicate that 'Mf_eot()' hasn't been called yet */ /* Read in the first four characters into 'kw_buff' */ for (i = 0; i < 4; i++) { int c; /* Input character */ if ((c = (*Mf_getc)()) == -1) bailout(MFE_EOF) else kw_buff[i] = (char) c; } /* See if 'kw_buff' contains "MTrk". In some future version, * if the chunk type is unknown, then this section of code * should just ignore it. */ if (strncmp(kw_buff,"MTrk",4) != 0) bailout(MFE_TKWEXP) /* Tell the user that we're starting a new track */ if (Mf_starttrack != NULL && (error = (*Mf_starttrack)()) < 0) return(error); /* User already knows about error */ /* Read the length of the track */ if ((tklen = read_long()) < 0) return(tklen); /* Return error code */ /* Read the data in the track */ error = MFE_OK; while (tklen > 0 && error == MFE_OK) error = read_event(&tklen); if (error != MFE_OK) return(error); if (!eot_called) /* Has 'Mf_eot()' been called? */ bailout(MFE_NOEOT) return(MFE_OK); /* All went well */ } X/* MIDIFILE * This is the main routine of the whole thing, and the only one which * should be accessible to the user. It reads in a MIDI format file, * parses it, and passes the various parts on to user-supplied routines. * Although it is very similar to Tim Thompson's 'midifile()', and performs * the same function, it does _not_ cravenly exit the program as soon as * an error occurs, and therefore can safely be used inside another * application. * Since 'midifile()' does not care where the input file is coming from, * the user is expected to do all of the grunt work of opening files and * all that. * Returns MFE_OK if all goes well, or an error code if anything goes wrong, * including user disapproval. */ int midifile() { int i; int ntrks; /* Initialize 'Mf_currtime' and 'Mf_deltatime' */ Mf_currtime = Mf_deltatime = 0L; if (Mf_getc == NULL) bailout(MFE_NOGETC) /* Read the file header, and return an error code if * anything wrong occurs. */ if ((ntrks = read_header()) < 0) return(ntrks); /* Return error value */ /* Read the tracks, and return an error code if anything * wrong occurs. */ for (i = 0; i < ntrks; i++) { int error; if ((error = read_track()) != MFE_OK) return(error); } return(MFE_OK); /* No errors */ } //go.sysin dd * if [ `wc -c < midifile/mf.c` != 19018 ]; then made=FALSE /bin/echo 'error transmitting "midifile/mf.c" --' /bin/echo 'length should be 19018, not' `wc -c < midifile/mf.c` else made=TRUE fi if [ $made = TRUE ]; then /bin/chmod 664 midifile/mf.c /bin/echo -n ' '; /bin/ls -ld midifile/mf.c fi /bin/echo 'Extracting midifile/mf.h' sed 's/^X//' <<'//go.sysin dd *' >midifile/mf.h X/* MF.H * Definitions etc. for MIDI file analyzer. */ int (*Mf_getc)(); /* Returns -1 on EOF */ int (*Mf_error)(); int (*Mf_header)(); int (*Mf_starttrack)(); int (*Mf_endtrack)(); int (*Mf_on)(); int (*Mf_off)(); int (*Mf_pressure)(); int (*Mf_controller)(); int (*Mf_pitchbend)(); int (*Mf_program)(); int (*Mf_chanpressure)(); int (*Mf_sysex)(); int (*Mf_metamisc)(); int (*Mf_sqspecific)(); int (*Mf_seqnum)(); int (*Mf_text)(); int (*Mf_eot)(); int (*Mf_timesig)(); int (*Mf_smpte)(); int (*Mf_tempo)(); int (*Mf_keysig)(); int (*Mf_arbitrary)(); X/* ERROR CODES * These are the error codes returned by the various routines. */ #define MFE_OK 0 /* No error */ #define MFE_EOF -1 /* Premature end of file */ #define MFE_HKWEXP -2 /* Header keyword expected */ #define MFE_NOGETC -3 /* 'Mf_getc' is not defined */ #define MFE_ILLHLEN -4 /* Illegal header length */ #define MFE_USER -5 /* User disapproves of something */ #define MFE_ILLFORM -6 /* Illegal file format */ #define MFE_TKWEXP -7 /* Track keyword expected */ #define MFE_ILLSETYPE -8 /* Illegal SysEx message type */ #define MFE_MEM -9 /* Insufficient memory */ #define MFE_ILLSTAT -10 /* Illegal status byte */ #define MFE_ILLMLEN -11 /* Illegal meta-event length */ #define MFE_ILLMVAL -12 /* Illegal value in meta-event */ #define MFE_UKMETA -13 /* Unknown meta-event */ #define MFE_NOEOT -14 /* No end-of-track meta-event */ X/* ERR_MSGS * Error messages corresponding to the various errors that might occur. */ char *err_msgs[] = { "No error", "Premature end of file", "'MThd' expected", "'Mf_getc' undefined", "Illegal header length", "User disapproval", "Illegal file format", "'MTrk' expected", "Illegal system-exclusive message type", "Insufficient memory", "Illegal status byte", "Illegal meta-event length", "Illegal value in meta-event", "Unknown meta-event", "Missing end-of-track meta-event", NULL }; X/* BAILOUT * Call 'Mf_error()' with the error code 'code', then return that code. */ #define bailout(code) {if(Mf_error!=NULL)(*Mf_error)(err_msgs[-(code)]);\ return(code);} X/* MIDI COMMANDS */ #define MIDI_NOTEOFF 0x80 /* Note off */ #define MIDI_NOTEON 0x90 /* Note on */ #define MIDI_PRESSURE 0xa0 /* Polyphonic key pressure */ #define MIDI_CONTROL 0xb0 /* Control change */ #define MIDI_PROGRAM 0xc0 /* Program change */ #define MIDI_CHANPRES 0xd0 /* Channel pressure */ #define MIDI_PITCHB 0xe0 /* Pitch wheel change */ #define MIDI_SYSEX 0xf0 /* System exclusive data */ X/* META-EVENT MESSAGE TYPES */ #define META_SEQNUM 0x00 /* Sequence number */ #define META_TEXT 0x01 /* Text event */ #define META_COPYRIGHT 0x02 /* Copyright notice */ #define META_SEQNAME 0x03 /* Sequence/track name */ #define META_INSTNAME 0x04 /* Instrument name */ #define META_LYRIC 0x05 /* Lyric */ #define META_MARKER 0x06 /* Marker */ #define META_CUEPT 0x07 /* Cue point */ #define META_EOT 0x2f /* End of track */ #define META_TEMPO 0x51 /* Set tempo */ #define META_SMPTE 0x54 /* SMPTE offset */ #define META_TIMESIG 0x58 /* Time signature */ #define META_KEYSIG 0x59 /* Key signature */ #define META_SEQSPEC 0x7f /* Sequencer-specific event */ //go.sysin dd * if [ `wc -c < midifile/mf.h` != 3174 ]; then made=FALSE /bin/echo 'error transmitting "midifile/mf.h" --' /bin/echo 'length should be 3174, not' `wc -c < midifile/mf.h` else made=TRUE fi if [ $made = TRUE ]; then /bin/chmod 664 midifile/mf.h /bin/echo -n ' '; /bin/ls -ld midifile/mf.h fi /bin/echo 'Extracting midifile/midifile.3' sed 's/^X//' <<'//go.sysin dd *' >midifile/midifile.3 X.TH MIDIFILE.3 "May 12, 1990" X.AT 3 X.SH NAME midifile \- a MIDI file parser X.SH SYNOPSIS X.B #include "midifile.h" X.PP X.B int midifile() X.PP X.B int (*Mf_getc)() X.PP X.B int (*Mf_error)(str) X.br X.B char *str; X.PP X.B int (*Mf_header)(format,ntrks,division) X.br X.B short format, ntrks, division; X.PP X.B int (*Mf_starttrack)() X.PP X.B int (*Mf_on)(chan,note,vol) X.br X.B unsigned char chan, note, vol; X.PP X.B int (*Mf_off)(chan,note,vol) X.br X.B unsigned char chan, note, vol; X.PP X.B int (*Mf_pressure)(chan,note,pressure) X.br X.B unsigned char chan, note, pressure; X.PP X.B int (*Mf_controller)(chan,control,value) X.br X.B unsigned char chan, control, value; X.PP X.B int (*Mf_pitchbend)(chan,lsb,msb) X.br X.B unsigned char chan, lsb, msb; X.PP X.B int (*Mf_program)(chan,prog) X.br X.B unsigned char chan, prog; X.PP X.B int (*Mf_chanpressure)(chan,pressure) X.br X.B unsigned char chan, pressure; X.PP X.B int (*Mf_sysex)() X.PP X.B int (*Mf_metamisc)() X.PP X.B int (*Mf_sqspecific)(chan,length,data) X.br X.B unsigned char chan; X.br X.B long length; X.br X.B char *data; X.PP X.B int (*Mf_seqnum)(seqnum) X.br X.B short seqnum X.PP X.B int (*Mf_text)(msg_type,length,data) X.br X.B int msg_type; X.br X.B long length; X.br X.B char *data; X.PP X.B int (*Mf_eot)() X.PP X.B int (*Mf_timesig)(numer,denom,clocks,qnotes) X.br X.B int numer, denom, clocks, qnotes; X.PP X.B int (*Mf_smpte)(hour,min,sec,frame,fract) X.br X.B int hour, min, sec, frame, fract; X.PP X.B int (*Mf_tempo)(tempo) X.br X.B unsigned long tempo; X.PP X.B int (*Mf_keysig)(sharpflat,minor) X.br X.B int sharpflat, minor; X.PP X.B int (*Mf_arbitrary)() X.PP X.B long Mf_currtime X.PP X.B long Mf_deltatime X.SH DESCRIPTION X.I midifile is a routine that parses an arbitrary MIDI format file. It contains hooks for every type of event, for the user to add his own event-handling functions. Each time X.I midifile reads an event, it calls a user function (if one is defined, i.e., if it is non-NULL) with a description of the event. X.PP Not all functions must be supplied at all times: X.I midifile always checks for the existence of a user function before calling it. Thus, if you're only interested in the third track, you can have X.B Mf_starttrack assign values to the appropriate functions at the beginning of the third track, and X.B Mf_eot reset them to NULL. X.PP The only function which must be supplied is X.I Mf_getc, which should read a byte from the MIDI file and return it; it should return -1 in case of end of file, and a negative error code in case of error. X.PP X.I Mf_error is called in case of error. It is passed a string describing the error. If X.I Mf_error returns a negative value, then X.I midifile aborts and returns that value. X.PP Actually, any user function can abort the execution of X.I midifile: anytime a function returns a negative value, X.I midifile returns that value to the calling routine. X.PP X.I Mf_header is called whene the file header has been read. Its parameters are the file format (0, 1 or 2), the number of tracks in the file, and the division of quarter-notes, as described by the deltas in the file. X.PP X.I Mf_starttrack is called at the beginning of each track. X.PP X.I Mf_on and X.I Mf_off are called each time X.I midifile reads a MIDI ON and MIDI OFF event, respectively. The parameters are the MIDI channel, the note number, and the note-on (or off) velocity. X.PP X.I Mf_pressure is called for polyphonic key pressure (after-touch) events. It gives the channel, the note number, and the pressure value. Similarly, X.I Mf_chanpressure is called for channel pressure events. X.PP X.I Mf_controller is called for controller changes. It gives the MIDI channel, the controller number, and the controller value. X.PP X.I Mf_pitchbend is called for pitch wheel events. It gives the MIDI channel, the least-significant and most-significant bytes of the pitch-bend value. X.PP X.I Mf_program is called for program (patch) changes. It gives the MIDI channel and the program number. X.PP X.I Mf_sysex is called for system-exclusive messages. This function is not yet implemented. X.PP X.I midifile includes a series of functions designed to deal with the various meta-events described by the MIDI 1.0 file spec. In each case, the X.I length parameter, when given, includes the end-of-system-exclusive marker. X.PP X.I Mf_metamisc is called for miscellaneous meta-events, as described in the MIDI 1.0 file spec. This function is not yet implemented. X.PP X.I Mf_sqspecific is called for sequencer-specific meta-events. It gives the MIDI channel number, the length of the event, and a data string containing the data. X.PP X.I Mf_seqnum specifies the number of a sequence. X.PP X.I Mf_text covers the entire class of text meta-events. It gives the message type (miscellaneous, copyright notice, track name, etc.), the text length, and a string containing the text. X.PP X.I Mf_eot is called for the end-of-track meta-event. X.PP X.I Mf_timesig gives the time signature of a piece. The time signature, as given at the beginning of a piece is given by X.B numer/(2^-denom). X.B clocks represents the number of MIDI clocks per metronome click. X.B qnotes gives the number of notated 32nd-notes in a MIDI quarter-note. X.PP X.I Mf_smpte specifies a SMPTE offset for a track. It gives the number of hours, minutes, seconds, frames and 100ths of a frame. X.PP X.I Mf_tempo sets the tempo, in microseconds per MIDI quarter-note. X.PP X.I Mf_keysig gives the key signature of the piece. X.B sharpflat gives the number of sharps or flats in the signature: 0 means the piece is in the key of C, -1 means there is one flat in the signature, 1 means there is one sharp, and so forth. X.I minor is set to 0 if the piece is in a major key, and 1 if it is in a minor key. X.PP There are two user-accessible variables used by X.I midifile: X.I Mf_currtime and X.I Mf_deltatime X.I Mf_currtime measures the time elapsed between the beginning of the track and the current event. X.I Mf_deltatime is the event's delta, i.e., it measures the time elapsed between the previous event and the current event (or the beginning of the track, if the current event is the first one). X.SH FILES X.B midifile.h X.SH BUGS X.I midifile does not handle system-exclusive messages, except for the meta-events described in the X.I MIDI 1.0 File Specification. X.SH AUTHOR Original author: Tim Thompson. This version was rewritten and enhanced by Andrew Arensburger. X.SH "SEE ALSO" X.IR "MIDI 1.0 specification" X.br X.I "MIDI 1.0 file specification" //go.sysin dd * if [ `wc -c < midifile/midifile.3` != 6394 ]; then made=FALSE /bin/echo 'error transmitting "midifile/midifile.3" --' /bin/echo 'length should be 6394, not' `wc -c < midifile/midifile.3` else made=TRUE fi if [ $made = TRUE ]; then /bin/chmod 664 midifile/midifile.3 /bin/echo -n ' '; /bin/ls -ld midifile/midifile.3 fi /bin/echo 'Extracting midifile/midifile.h' sed 's/^X//' <<'//go.sysin dd *' >midifile/midifile.h X/* MIDI.H * Definitions etc. for MIDI file analyzer. */ extern int (*Mf_getc)(); extern int (*Mf_error)(); extern int (*Mf_header)(); extern int (*Mf_starttrack)(); extern int (*Mf_endtrack)(); extern int (*Mf_on)(); extern int (*Mf_off)(); extern int (*Mf_pressure)(); extern int (*Mf_controller)(); extern int (*Mf_pitchbend)(); extern int (*Mf_program)(); extern int (*Mf_chanpressure)(); extern int (*Mf_sysex)(); extern int (*Mf_metamisc)(); extern int (*Mf_sqspecific)(); extern int (*Mf_seqnum)(); extern int (*Mf_text)(); extern int (*Mf_eot)(); extern int (*Mf_timesig)(); extern int (*Mf_smpte)(); extern int (*Mf_tempo)(); extern int (*Mf_keysig)(); extern int (*Mf_arbitrary)(); extern int midifile(); extern long Mf_currtime; extern long Mf_deltatime; X/* ERROR CODES * These are the error codes returned by 'midifile()'. */ #define MFE_OK 0 /* No error */ #define MFE_EOF -1 /* Premature end of file */ #define MFE_HKWEXP -2 /* Header keyword expected */ #define MFE_NOGETC -3 /* 'Mf_getc' is not defined */ #define MFE_ILLHLEN -4 /* Illegal header length */ #define MFE_USER -5 /* User disapproves of something */ #define MFE_ILLFORM -6 /* Illegal file format */ #define MFE_TKWEXP -7 /* Track keyword expected */ #define MFE_ILLSETYPE -8 /* Illegal SysEx message type */ #define MFE_MEM -9 /* Insufficient memory */ #define MFE_ILLSTAT -10 /* Illegal status byte */ #define MFE_ILLMLEN -11 /* Illegal meta-event length */ #define MFE_ILLMVAL -12 /* Illegal value in meta-event */ #define MFE_UKMETA -13 /* Unknown meta-event */ #define MFE_NOEOT -14 /* No end-of-track meta-event */ //go.sysin dd * if [ `wc -c < midifile/midifile.h` != 1621 ]; then made=FALSE /bin/echo 'error transmitting "midifile/midifile.h" --' /bin/echo 'length should be 1621, not' `wc -c < midifile/midifile.h` else made=TRUE fi if [ $made = TRUE ]; then /bin/chmod 664 midifile/midifile.h /bin/echo -n ' '; /bin/ls -ld midifile/midifile.h fi made=TRUE if [ $made = TRUE ]; then /bin/chmod 775 midifile /bin/echo -n ' '; /bin/ls -ld midifile fi exit 0 # Just in case... -- Kent Landfield INTERNET: kent@sparky.IMD.Sterling.COM Sterling Software, IMD UUCP: uunet!sparky!kent Phone: (402) 291-8300 FAX: (402) 291-4362 Please send comp.sources.misc-related mail to kent@uunet.uu.net.