#include "munga.h" #pragma hdrstop #include "audmidi.h" //############################################################################# //########################### MidiParse ################################# //############################################################################# #define NOTEOFF (0x80) #define NOTEON (0x90) #define PRESSURE (0xa0) #define CONTROLLER (0xb0) #define PITCHBEND (0xe0) #define PROGRAM (0xc0) #define CHANPRESSURE (0xd0) #define METATEXT "Text Event" #define METACOPYRIGHT "Copyright Notice" #define METASEQUENCE "Sequence/Track Name" #define METAINSTRUMENT "Instrument Name" #define METALYRIC "Lyric" #define METAMARKER "Marker" #define METACUE "Cue Point" #define METAUNRECOGNIZED "Unrecognized" #define CHAR_BUFF_SIZE (32) // //############################################################################# //############################################################################# // MidiParse::MidiParse() { Mf_nomerge = 0; // 1 => continue'ed system exclusives are not collapsed. Mf_currtime = 0L; // current time in delta-time units Mf_skipinit = 0; // 1 if initial garbage should be skipped Mf_toberead = 0L; Msgbuff = NULL; // message buffer Msgsize = 0; // Size of currently allocated Msg Msgindex = 0; // index of next available location in Msg } // //############################################################################# //############################################################################# // MidiParse::~MidiParse() { if (Msgbuff != NULL) { Unregister_Pointer(Msgbuff); #if 0 free(Msgbuff); #else delete[] Msgbuff; #endif Msgbuff = NULL; } } // //############################################################################# //############################################################################# // Logical MidiParse::TestInstance() const { Node::TestInstance(); return True; } // //############################################################################# //############################################################################# // MidiParse::SignedLongWord MidiParse::CurTime() { Check(this); return Mf_currtime; } // //############################################################################# //############################################################################# // void MidiParse::Parse() { Check(this); SignedWord ntrks; ntrks = readheader(); Verify(ntrks > 0); while (ntrks-- > 0) readtrack(); } // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::readmt( char *s, SignedWord skip ) { Check(this); SignedWord nread = 0; char b[4]; char buff[CHAR_BUFF_SIZE]; SignedWord c; char *errmsg = "expecting "; // read through the "MThd" or "MTrk" header string // if 1, we attempt to skip initial garbage. retry: while ( nread<4 ) { c = Mf_getc(); if ( c == EOF ) { errmsg = "EOF while expecting "; goto err; } b[nread++] = (char)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: Str_Copy(buff, errmsg, CHAR_BUFF_SIZE); Str_Cat(buff, s, CHAR_BUFF_SIZE); mferror(buff); return 0; } // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::egetc() { Check(this); // read a single character and abort on EOF SignedWord c = Mf_getc(); if ( c == EOF ) mferror("premature EOF"); Mf_toberead--; return c; } // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::readheader() { Check(this); // read a header chunk SignedWord format, ntrks, division; if ( readmt("MThd", Mf_skipinit) == EOF ) return 0; Mf_toberead = read32bit(); format = read16bit(); ntrks = read16bit(); division = read16bit(); Mf_header(format,ntrks,division); // flush any extra stuff, in case the length of header is not 6 while ( Mf_toberead > 0 ) egetc(); return ntrks; } // //############################################################################# //############################################################################# // void MidiParse::readtrack() { Check(this); // 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 SignedWord chantype[] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 through 0x70 2, 2, 2, 2, 1, 1, 2, 0 // 0x80 through 0xf0 }; SignedLongWord lookfor, lng; SignedWord c, c1, type; SignedWord sysexcontinue = 0; // 1 if last message was an unfinished sysex SignedWord running; // 1 when running status used SignedWord status = 0; // (possibly running) status byte SignedWord needed; if ( readmt("MTrk",0) == EOF ) return; Mf_toberead = read32bit(); Mf_currtime = 0; 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() : (SignedWord)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 ) { Mf_arbitrary(msgleng(),msg()); } else if ( c == 0xf7 ) { sysex(); sysexcontinue = 0; } break; default: badbyte(c); break; } } Mf_endtrack(); return; } // //############################################################################# //############################################################################# // void MidiParse::badbyte(SignedWord c) { Check(this); char buff[CHAR_BUFF_SIZE]; sprintf(buff,"unexpected byte: 0x%02x",c); mferror(buff); } // //############################################################################# //############################################################################# // void MidiParse::metaevent(SignedWord type) { Check(this); SignedWord leng = msgleng(); char *m = msg(); switch ( type ) { case 0x00: 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 Mf_text(type,leng,m); break; case 0x2f: // End of Track Mf_eot(); break; case 0x51: // Set tempo Mf_tempo(to32bit((SignedWord)0,m[0],m[1],m[2])); break; case 0x54: Mf_smpte(m[0],m[1],m[2],m[3],m[4]); break; case 0x58: Mf_timesig(m[0],m[1],m[2],m[3]); break; case 0x59: Mf_keysig(m[0],m[1]); break; case 0x7f: Mf_sqspecific(leng,m); break; default: Mf_metamisc(type,leng,m); } } // //############################################################################# //############################################################################# // void MidiParse::sysex() { Check(this); Mf_sysex(msgleng(),msg()); } // //############################################################################# //############################################################################# // void MidiParse::chanmessage( SignedWord status, SignedWord c1, SignedWord c2 ) { Check(this); SignedWord chan = status & (SignedWord)0xf; switch ( status & 0xf0 ) { case NOTEOFF: Mf_off(chan,c1,c2); break; case NOTEON: Mf_on(chan,c1,c2); break; case PRESSURE: Mf_pressure(chan,c1,c2); break; case CONTROLLER: Mf_controller(chan,c1,c2); break; case PITCHBEND: Mf_pitchbend(chan,c1,c2); break; case PROGRAM: Mf_program(chan,c1); break; case CHANPRESSURE: Mf_chanpressure(chan,c1); break; } } // //############################################################################# //############################################################################# // MidiParse::SignedLongWord MidiParse::readvarinum() { Check(this); // readvarinum - read a varying-length number, and return the // number of characters it took. SignedLongWord value; SignedWord c; c = egetc(); value = c; if (c & 0x80) { value &= 0x7f; do { c = egetc(); value <<= 7; value += (c & 0x7f); } while (c & 0x80); } return value; } // //############################################################################# //############################################################################# // MidiParse::SignedLongWord MidiParse::to32bit(SignedWord c1, SignedWord c2,SignedWord c3, SignedWord c4) { Check(this); SignedLongWord value; value = (c1 & 0xff); value = (value<<8) + (c2 & 0xff); value = (value<<8) + (c3 & 0xff); value = (value<<8) + (c4 & 0xff); return value; } // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::to16bit(SignedWord c1,SignedWord c2) { Check(this); SignedWord value; value = (c1 & (SignedWord)0xff); value = (SignedWord)((SignedWord)(value<<8) + (c2 & (SignedWord)0xff)); return value; } // //############################################################################# //############################################################################# // MidiParse::SignedLongWord MidiParse::read32bit() { Check(this); SignedWord c1, c2, c3, c4; c1 = egetc(); c2 = egetc(); c3 = egetc(); c4 = egetc(); return to32bit(c1,c2,c3,c4); } // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::read16bit() { Check(this); SignedWord c1, c2; c1 = egetc(); c2 = egetc(); return to16bit(c1,c2); } // //############################################################################# //############################################################################# // void MidiParse::mferror(char *s) { Check(this); Mf_error(s); } // 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 ((SignedWord)128) // //############################################################################# //############################################################################# // void MidiParse::msginit() { Check(this); Msgindex = 0; } // //############################################################################# //############################################################################# // char* MidiParse::msg() { Check(this); return Msgbuff; } // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::msgleng() { Check(this); return Msgindex; } // //############################################################################# //############################################################################# // void MidiParse::msgadd(SignedWord c) { Check(this); // If necessary, allocate larger message buffer. if ( Msgindex >= Msgsize ) msgenlarge(); Msgbuff[Msgindex++] = (char)c; } // //############################################################################# //############################################################################# // void MidiParse::msgenlarge() { Check(this); char *newmess; char *oldmess = Msgbuff; SignedWord oldleng = Msgsize; Msgsize += MSGINCREMENT; #if 0 newmess = (char*)malloc( sizeof(char)*Msgsize ); #else newmess = new char[Msgsize]; #endif Register_Pointer(newmess); // copy old message into larger new one if ( oldmess != 0 ) { char *p = newmess; char *q = oldmess; char *endq = &oldmess[oldleng]; for ( ; q!=endq ; p++,q++ ) *p = *q; Unregister_Pointer(oldmess); #if 0 free(oldmess); #else delete[] oldmess; #endif } Msgbuff = newmess; } // METHODS TO OVERRIDE // //############################################################################# //############################################################################# // MidiParse::SignedWord MidiParse::Mf_getc() { Fail("MidiParse::Mf_getc - Should never reach here"); return 0; } // //############################################################################# //############################################################################# // void MidiParse::Mf_error(char*) { Fail("MidiParse::Mf_error - Should never reach here"); }