/* * 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) #ifdef PROTOTYPES #define NOARGS void #else #define NOARGS #endif #ifdef NOVOID #define VOID int #else #define VOID void #endif /* 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; #ifdef PROTOTYPES 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; #else VOID (*Mf_error)() = 0; VOID (*Mf_header)() = 0; VOID (*Mf_on)() = 0; VOID (*Mf_off)() = 0; VOID (*Mf_pressure)() = 0; VOID (*Mf_controller)() = 0; VOID (*Mf_pitchbend)() = 0; VOID (*Mf_program)() = 0; VOID (*Mf_chanpressure)() = 0; VOID (*Mf_sysex)() = 0; VOID (*Mf_arbitrary)() = 0; VOID (*Mf_metamisc)() = 0; VOID (*Mf_seqnum)() = 0; VOID (*Mf_smpte)() = 0; VOID (*Mf_tempo)() = 0; VOID (*Mf_timesig)() = 0; VOID (*Mf_keysig)() = 0; VOID (*Mf_sqspecific)() = 0; VOID (*Mf_text)() = 0; #endif 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); #ifdef PROTOTYPES 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); #else static long to32bit(); static int to16bit(); static VOID mferror(); static VOID badbyte(); static VOID metaevent(); static VOID msgadd(); static VOID chanmessage(); #endif VOID midifile() /* The only non-static function in this file. */ { int ntrks; if ( Mf_getc == 0 ) mferror("mf.h() 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() /* read a single character and abort on EOF */ { int c = (*Mf_getc)(); if ( c == EOF ) mferror("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(); chanmessage( status, c1, (needed>1) ? egetc() : 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); 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, allocate 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; }