/* * Copyright 1999 Kelvin W Sherlock * * $Id: midi_parse.cpp,v 1.3 1999/03/20 16:24:28 baron Exp baron $ * */ #include "midi_parse.h" #include #include #include #include /* * ToDo: Meta Event #21??? * other events?? * */ #ifdef __GNUC__ #define min(a,b) a b ? b : a; } #endif /* * Duplicate at most N chars from a string * * */ char *strndup(char const * src, int length) { char *ret; register int i; length = min (length, strlen(src)); ret = (char *)malloc(length + 1); assert(ret); for (i = 0; i < length; i++) ret[i] = src[i]; ret[i] = (char)0; //NULL terminate it. return ret; } /* * constructor * */ midi_parse::midi_parse(): currTime(0), deltaTime(0), format(-1), trackCount(0), qnDivision(0), headerParsed(false), currentTrack(-1), lastError(0), doneTrack(false) { } /* * * destructor * */ midi_parse::~midi_parse() { } int midi_parse::Run(void) { int Err; Err = ReadHeader(); currentTrack = 0; while (!Err && currentTrack < trackCount) { currentTrack++; //1st track is track 0 Err = ReadTrack(); } return Err == EXIT_OK ? 0 : Err; } /* * Returns 0 if ok, error code otherwise * * */ int midi_parse::ReadHeader(void) { uint32 magic; //uint16 i; try { /* * Check that it is a midi file */ magic = Read32(); if (magic != MTHD) return BAD_HEADER; magic = Read32(); if (magic != 6) return BAD_HEADER; format = Read16(); trackCount = Read16(); qnDivision = Read16(); // if (lastError) return EARLY_EOF; } catch(int err) { return EARLY_EOF; } M_header(format, trackCount, qnDivision); return 0; } int midi_parse::ReadTrack(void) { uint32 magic; uint32 trackLen; int i; int prevCode = 0; doneTrack = false; currTime = deltaTime = 0; /* make sure the magic keyword is there */ try { magic = 0; for (i = 0; i < 4; i++) { int ch = Read8(); magic <<= 8; magic |= ch; } } catch(int err) { if ( i == 0) return EXIT_OK; else return BAD_HEADER; } if (magic != MTRK) return BAD_HEADER; try{ trackLen = Read32(); //if (lastError) return EARLY_EOF; M_trackstart(trackLen); while (!doneTrack && !lastError) { currTime += ReadVarLength(); //if (lastError) return EARLY_EOF; int code = Read8(); //if (lastError) return EARLY_EOF; switch(code) { case META_EVENT: { int type = Read8(); int var = ReadVarLength(); uint8 *str = new uint8[var]; for (register int i =0; i < var; i++) { str[i] = Read8(); } MetaEvent(type, str, var); delete []str; } break; case SYSTEM_EXCLUSIVE: { long var = ReadVarLength(); char *cp; cp = new char[var+1]; // +1 to include the $f0 prepended. cp[0] = (char)0xf0; for (register int i = 0; i < var; i++) { cp[i+1] = Read8(); //if (lastError) return EARLY_EOF; } M_sysEx(var+1, cp); delete []cp; } break; default: //probably a channel event... // the problem here is that if the event type doesn't change, // 'code' will refer to the first parameter, not the event type int arg1; if (code & 0x80) { prevCode = code; //cache the code for next time arg1 = Read8(); } else { arg1 = code; code = prevCode; } int ch = code & 0x0f; switch (code & 0xf0) { case CONTROL_CHANGE: M_controlChange(ch, arg1, Read8()); //channel, controller, argument break; case PROGRAM_CHANGE: M_programChange(ch, arg1); //channel, instrument break; case POLY_AFTERTOUCH: M_polyAfterTouch(ch, arg1, Read8());//channel, pitch, pressure break; case CHANNEL_AFTERTOUCH: M_channelAfterTouch(ch, arg1);//channel, pressure break; case NOTE_ON: M_noteOn(ch, arg1, Read8()); //channel, note, velocity break; case NOTE_OFF: M_noteOff(ch, arg1, Read8());//channel, note, velocity break; case PITCH_BEND: //FIXME - should it be shifted 7???? { int val = arg1 + (Read8() << 7); M_pitchBend(ch, val);//channel, value } break; default: printf("unknown event (%x)", code); } } } } catch(int err) { return EARLY_EOF; } //if (lastError) return EARLY_EOF; return 0; } void midi_parse::MetaEvent(int type, uint8 const *data, int length) { switch(type) { case SEQUENCE_NUMBER: { assert(length == 2); int i = (data[0] << 8) | data[1]; M_seqnum(i); } break; /* these are all text events */ case TEXT_EVENT: case COPYRIGHT_NOTICE: case SEQUENCE_NAME: case INSTRUMENT_NAME: case LYRIC: case MARKER: case CUE_POINT: { //the text does NOT include a NULL character at the end char *cp = strndup((char *)data, length); M_text(type, length, cp); free(cp); } break; case CHANNEL_PREFIX: assert(length == 1); M_prefixChannel(data[0]); break; case END_OF_TRACK: doneTrack = true; M_trackEnd(); break; case SET_TEMPO: { int i; assert (length == 3); i = data[0] << 16 | data[1] << 8 | data[2]; M_tempo(i); } break; case SMPTE_OFFSET: assert(length == 5); M_smpte(data[0], data[1], data[2], data[3], data[4]); break; case TIME_SIGNATURE: assert(length == 4); M_timesig(data[0], /*1 << */data[1], data[2], data[3]); break; case KEY_SIGNATURE: assert (length == 2); M_keysig(data[0], data[1]); break; case SEQUENCER_SPECIFIC: { M_seqSpecific(length, (char *)data); } break; default: M_metaMisc(length, (char *)data); } } /** * WriteVarLength * * writes a variable length integer to a midifile * * * */ void midi_parse::WriteVarLength(unsigned long value) { unsigned long buffer; buffer = value & 0x7f; while((value >>= 7) > 0) { buffer <<= 8; buffer |= 0x80; buffer += (value & 0x7f); } while(1) { Write8((unsigned)(buffer & 0xff)); if(buffer & 0x80) buffer >>= 8; else return; } }/* end of WriteVarLen */ /** * * ReadVarLength * * read a variable length integer from a midi file */ uint32 midi_parse::ReadVarLength(void) { unsigned long value; int c; value = 0; //pre-initialize do { c = Read8(); value = (value << 7) + (c & 0x7f); } while (c & 0x80); return value; } uint16 midi_parse::Read16(void) { uint8 foo; uint16 result = 0; foo = Read8(); result = foo << 8; foo = Read8(); result |= foo; return result; } uint32 midi_parse::Read32(void) { uint8 foo; uint32 result = 0; int i; for (i = 0; i < 4; i ++) { foo = Read8(); result = result << 8; result |= foo; } return result; } void midi_parse::SetError(void) { lastError = 1; } /* * * * * * * * */ midiFILE::midiFILE(void): midi_parse(), file(NULL), err(0) { } midiFILE::midiFILE(const char *name): midi_parse(), err(0) { file = fopen(name, "rb"); if (!file) err++; } midiFILE::~midiFILE() { if (file) fclose(file); } int midiFILE::SetFile(const char *name) { if (file) fclose(file); err = 0; file = fopen(name, "rb"); if (!file) err++; return err; } uint8 midiFILE::Read8(void) { int i; i = fgetc(file); if (i == EOF) { SetError(); throw(-1); } return i & 0xff; #if 0 if (err) { SetError(); return -1; } i = fgetc(file); if (i == EOF) { SetError(); err++; return -1; } return i & 0xff; #endif } void midiFILE::Write8(uint8 val) { fputc(val, file); }