#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "midifile.h"


#define NULLFUNC 0


/* public stuff */

/* Functions to be called while processing the MIDI file. */
int (*Mf_getc)() = NULLFUNC;
int (*Mf_error)() = NULLFUNC;
int (*Mf_header)() = NULLFUNC;
int (*Mf_trackstart)() = NULLFUNC;
int (*Mf_trackend)() = NULLFUNC;
int (*Mf_noteon)() = NULLFUNC;
int (*Mf_noteoff)() = NULLFUNC;
int (*Mf_pressure)() = NULLFUNC;
int (*Mf_parameter)() = NULLFUNC;
int (*Mf_pitchbend)() = NULLFUNC;
int (*Mf_program)() = NULLFUNC;
int (*Mf_chanpressure)() = NULLFUNC;
int (*Mf_sysex)() = NULLFUNC;
int (*Mf_arbitrary)() = NULLFUNC;
int (*Mf_metamisc)() = NULLFUNC;
int (*Mf_seqnum)() = NULLFUNC;
int (*Mf_eot)() = NULLFUNC;
int (*Mf_smpte)() = NULLFUNC;
int (*Mf_tempo)() = NULLFUNC;
int (*Mf_timesig)() = NULLFUNC;
int (*Mf_keysig)() = NULLFUNC;
int (*Mf_seqspecific)() = NULLFUNC;
int (*Mf_text)() = NULLFUNC;

int Mf_nomerge = 0;             /* 1 => continue'ed system exclusives are */
                                                       /* not collapsed. */
long Mf_currtime = 0L;          /* current time in delta-time units */

/* 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 int format, ntrks, division;


static
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];
                       (void) strcpy(buff,"expecting ");
                       (void) strcat(buff,s);
                       mferror(buff);
               }
       }
       return(c);
}

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

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

static
readheader()            /* read a header chunk */
{
       if ( readmt("MThd") == EOF )
               return;

       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 > 0L )
               (void) egetc();
}

static
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 ( readmt("MTrk") == EOF )
               return(0);

       Mf_toberead = read32bit();
       Mf_currtime = 0L;

       if ( Mf_trackstart )
               (*Mf_trackstart)();

       while ( Mf_toberead > 0L ) {

               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();
                       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
badbyte(c)
int c;
{
       char buff[32];

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

static
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_seqspecific )
                       (*Mf_seqspecific)(leng,m);
               break;
       default:
               if ( Mf_metamisc )
                       (*Mf_metamisc)(type,leng,m);
       }
}

static
sysex()
{
       if ( Mf_sysex )
               (*Mf_sysex)(msgleng(),msg());
}

static
chanmessage(status,c1,c2)
int status;
int c1, c2;
{
       int chan = status & 0xf;

       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 = (long)c;
       if ( c & 0x80 ) {
               value &= 0x7FL;
               do {
                       c = egetc();
                       value = (value << 7) + (long)(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
read16bit()
{
       int c1, c2;
       c1 = egetc();
       c2 = egetc();
       return to16bit(c1,c2);
}

/* static */
mferror(s)
char *s;
{
       if ( Mf_error )
               (*Mf_error)(s);

/*      freemem(bufsegp); */
       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
msginit()
{
       Msgindex = 0;
}

static char *
msg()
{
       return(Msgbuff);
}

static
msgleng()
{
       return(Msgindex);
}

static
msgadd(c)
int c;
{
       /* If necessary, allocate larger message buffer. */
       if ( Msgindex >= Msgsize )
               biggermsg();
       Msgbuff[Msgindex++] = c;
}

static
biggermsg()
{
       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;
}


/*
* This routine converts delta times in ticks into milliseconds.
*/

unsigned long mf_ticks2msec(unsigned long ticks,int division,unsigned long tempo)
{
       return( (unsigned long)( (((float)tempo/(float)1000 )/(float)division) * (float)ticks ));
}


mfread()                /* The only non-static function in this file. */
{
       int i;

       if ( Mf_getc == NULLFUNC )
               mferror("mfread() called without setting Mf_getc");

       readheader();

       for(i = 0; i < ntrks; i++)
               readtrack();
}