[comp.sources.misc] v17i002: midifile - A MIDI file parser, Part01/01

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.