
/*
 * Read a Standard MIDI File.  Externally-assigned function pointers are
 * called upon recognizing things in the file.  See midifile(3).
 */

#include "midi.h"
#include "midifile.h"

#define EOF (-1)
#define NOARGS VOID
#define VOID void

/* public stuff */

/* Functions to be called while processing the MIDI file. */
VOID (*Mf_starttrack)(NOARGS) = 0;
VOID (*Mf_endtrack)(NOARGS) = 0;
int (*Mf_getc)(NOARGS) = 0;
VOID (*Mf_eot)(NOARGS) = 0;


VOID (*Mf_error)(char *) = 0;
VOID (*Mf_header)(int,int,int) = 0;
VOID (*Mf_on)(int,int,int) = 0;
VOID (*Mf_off)(int,int,int) = 0;
VOID (*Mf_pressure)(int,int,int) = 0;
VOID (*Mf_controller)(int,int,int) = 0;
VOID (*Mf_pitchbend)(int,int,int) = 0;
VOID (*Mf_program)(int,int) = 0;
VOID (*Mf_chanpressure)(int,int) = 0;
VOID (*Mf_sysex)(int,char*) = 0;
VOID (*Mf_arbitrary)(int,char*) = 0;
VOID (*Mf_metamisc)(int,int,char*) = 0;
VOID (*Mf_seqnum)(int) = 0;
VOID (*Mf_smpte)(int,int,int,int,int) = 0;
VOID (*Mf_timesig)(int,int,int,int) = 0;
VOID (*Mf_tempo)(int) = 0;
VOID (*Mf_keysig)(int,int) = 0;
VOID (*Mf_sqspecific)(int,char*) = 0;
VOID (*Mf_text)(int,int,char*) = 0;

int Mf_nomerge = 0;		/* 1 => continue'ed system exclusives are */
				/* not collapsed. */
long Mf_currtime = 0L;		/* current time in delta-time units */
int Mf_skipinit = 0;		/* 1 if initial garbage should be skipped */

/* private stuff */

static long Mf_toberead = 0L;

static long readvarinum(NOARGS);
static long read32bit(NOARGS);
static int read16bit(NOARGS);
static VOID msgenlarge(NOARGS);
static char *msg(NOARGS);
static int readheader(NOARGS);
static VOID readtrack(NOARGS);
static VOID sysex(NOARGS), msginit(NOARGS);
static int egetc(NOARGS);
static int msgleng(NOARGS);

static int readmt(char*,int);
static long to32bit(int,int,int,int);
static int to16bit(int,int);
static VOID mferror(char *);
static VOID badbyte(int);
static VOID metaevent(int);
static VOID msgadd(int);
static VOID chanmessage(int,int,int);


/*----- midifile -----*/

VOID midifile()	 	/* The only non-static function in this file. */
{
	int ntrks;

	if ( Mf_getc == 0 )
		mferror("midifile() called without setting Mf_getc"); 

	ntrks = readheader();
	if ( ntrks <= 0 )
		mferror("No tracks!");
	while ( ntrks-- > 0 )
		readtrack();
}

static int
readmt(s,skip)		/* read through the "MThd" or "MTrk" header string */
char *s;
int skip;		/* if 1, we attempt to skip initial garbage */
{
	int nread = 0;
	char b[4];
	char buff[32];
	int c;
	char *errmsg = "expecting ";
retry:
	while ( nread<4 ) {
		c = (*Mf_getc)();
		if ( c == EOF ) {
			errmsg = "EOF while expecting ";
			goto err;
		}
		b[nread++] = c;
	}
	/* See if we found the 4 characters we're looking for */
	if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] )
		return(0);
	if ( skip ) {
		/* If we are supposed to skip initial garbage, */
		/* try again with the next character. */
		b[0]=b[1];
		b[1]=b[2];
		b[2]=b[3];
		nread = 3;
		goto retry;
	}
    err:
	(VOID) strcpy(buff,errmsg);
	(VOID) strcat(buff,s);
	mferror(buff);
	return(0);
}

static int
egetc()	/* to read a single character and abort on EOF */
{
	int c = (*Mf_getc)();

	if ( c == EOF )
		error("premature EOF");
	Mf_toberead--;
	return(c);
}

static int
readheader()		/* read a header chunk */
{
	int format, ntrks, division;

	if ( readmt("MThd",Mf_skipinit) == EOF )
		return(0);

	Mf_toberead = read32bit();
	format = read16bit();
	ntrks = read16bit();
	division = read16bit();

	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 )
		(VOID) egetc();
	return(ntrks);
}

static VOID
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, lng;
	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;		/* (possibly running) status byte */
	int needed;

	if ( readmt("MTrk",0) == EOF )
		return;

	Mf_toberead = read32bit();
	Mf_currtime = 0;

	if ( Mf_starttrack )
		(*Mf_starttrack)();

	while ( Mf_toberead > 0 ) {

		Mf_currtime += readvarinum();	/* delta time */

		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() & 0x7f;

 			/* The &0x7f here may seem unnecessary, but I've seen */
 			/* 'bad' midi files that had, some, volume bytes */
 			/* with the upper bit set.  This code should not harm */
 			/* proper data. */

			chanmessage( status, c1, (needed>1) ? (egetc()&0x7f) : 0 );
			continue;
		}

		switch ( c ) {

		case 0xff:			/* meta event */

			type = egetc();
			/* watch out - Don't combine the next 2 statements */
			lng = readvarinum();
			lookfor = Mf_toberead - lng;
			msginit();

			while ( Mf_toberead > lookfor )
				msgadd(egetc());

			metaevent(type);
			break;

		case 0xf0:		/* start of system exclusive */

			/* watch out - Don't combine the next 2 statements */
			lng = readvarinum();
			lookfor = Mf_toberead - lng;
			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 */

			/* watch out - Don't combine the next 2 statements */
			lng = readvarinum();
			lookfor = Mf_toberead - lng;

			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_endtrack )
		(*Mf_endtrack)();
	return;
}

static VOID
badbyte(c)
int c;
{
	char buff[32];

	(VOID) sprintf(buff,"unexpected byte: 0x%02x",c);
	mferror(buff);
}

static VOID
metaevent(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)(to32bit(0,m[0],m[1],m[2]));
		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_sqspecific )
			(*Mf_sqspecific)(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;

	switch ( status & 0xf0 ) {
	case NOTEOFF:
		if ( Mf_off )
			(*Mf_off)(chan,c1,c2);
		break;
	case NOTEON:
		if ( Mf_on )
			(*Mf_on)(chan,c1,c2);
		break;
	case PRESSURE:
		if ( Mf_pressure )
			(*Mf_pressure)(chan,c1,c2);
		break;
	case CONTROLLER:
		if ( Mf_controller )
			(*Mf_controller)(chan,c1,c2);
		break;
	case PITCHBEND:
		if ( Mf_pitchbend )
			(*Mf_pitchbend)(chan,c1,c2);
		break;
	case PROGRAM:
		if ( Mf_program )
			(*Mf_program)(chan,c1);
		break;
	case CHANPRESSURE:
		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(c1,c2,c3,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
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 = 0;	/* 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,relocate larger message buffer. */
	if ( Msgindex >= Msgsize )
		msgenlarge();
	Msgbuff[Msgindex++] = c;
}

static VOID
msgenlarge()
{
	char *newmess;
	char *oldmess = Msgbuff;
	int oldleng = Msgsize;
	char *malloc();

	Msgsize += MSGINCREMENT;
	newmess = malloc( (unsigned)(sizeof(char)*Msgsize) );

	/* copy old message into larger new one */
	if ( oldmess != 0 ) {
		register char *p = newmess;
		register char *q = oldmess;
		register char *endq = &oldmess[oldleng];

		for ( ; q!=endq ; p++,q++ )
			*p = *q;
		free(oldmess);
	}
	Msgbuff = newmess;
}

