//------------------------------------------------------------------------- // Filename: readmidi.cc // Version: $Id: readmidi.cc,v 1.1.4.1 1999/05/24 22:54:50 erik Exp $ // Copyright: Copyright (C) 1999, Erik Mouw (J.A.K.Mouw@its.tudelft.nl) // Author: Erik Mouw // Description: MIDI reading functions // Created at: Tue Mar 9 21:58:30 1999 // Modified by: Erik Mouw // Modified at: Mon May 24 14:07:13 1999 //------------------------------------------------------------------------- // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #ident "$Id: readmidi.cc,v 1.1.4.1 1999/05/24 22:54:50 erik Exp $" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include using namespace std; #include #include #ifdef STDC_HEADERS # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifndef HAVE_STRERROR extern "C" char *strerror(int errnum); #endif #ifndef HAVE_MEMCMP_CLEAN extern "C" int memcmp(const void *s1, const void *s2, size_t n); #endif #include "readmidi.hh" #include "sequence.hh" #include "track.hh" #include "events.hh" static const char headerChunkMagic[] = { 'M', 'T', 'h', 'd', 0, 0, 0, 6 }; static const char trackChunkMagic[] = { 'M', 'T', 'r', 'k' }; static int format; static int tracks; static int division; static bool readMidiHeader(FILE *file, midiSequence *sequence); static bool readMidiTrack(FILE *file, midiTrack *track); static int readMtrkEvent(unsigned char *buf, midiTrack *track); static int readMetaEvent(unsigned char *buf, midiTrack *track, int deltaTime); static int readSysexEvent(unsigned char *buf, midiTrack *track, int deltaTime); static int readMidiEvent(unsigned char *buf, midiTrack *track, int deltaTime); static int readVarLengthInt(unsigned char *buf, int &data); bool readMidi(const char *filename, midiSequence *sequence) { FILE *file; int i; midiTrack *track; #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": reading midi file: '"<< filename<< "'"<< endl; #endif file = fopen(filename, "r"); if(file == NULL) { cerr<< __PRETTY_FUNCTION__<< ": can't open file: "<< strerror(errno)<< endl; return(false); } // read header if(!readMidiHeader(file, sequence)) { fclose(file); return(false); } // read tracks for(i = 0; i < tracks; i++) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": reading track "<< i + 1<< "/"<< tracks<< endl; #endif track = new midiTrack; if(!readMidiTrack(file, track)) { fclose(file); return(false); } sequence->addTrack(track); } #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": midi file read"<< endl; #endif fclose(file); return(true); } static bool readMidiHeader(FILE *file, midiSequence *sequence) { char magic[8]; char buf[4]; // read magic value if(fread(magic, sizeof(char), 8, file) != 8) { cerr<< __PRETTY_FUNCTION__<< ": can't read magic value: "<< strerror(errno)<< endl; return(false); } // compare if(memcmp(magic, headerChunkMagic, 8) != 0) { cerr<< __PRETTY_FUNCTION__<< ": magic value fails"<< endl; return(false); } else { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": magic value correct"<< endl; #endif } // read format if(fread(buf, sizeof(char), 2, file) != 2) { cerr<< __PRETTY_FUNCTION__<< ": can't read format: "<< strerror(errno)<< endl; return(false); } format = buf[0] * 256 + buf[1]; cerr<< __PRETTY_FUNCTION__<< ": format: "; switch(format) { case 0: cerr<< "single multi-channel track"<< endl; break; case 1: cerr<< "simultaneous tracks"<< endl; break; case 2: cerr<< "sequentially independent single-tracks"<< endl; break; default: cerr<< " unknown: "<< format<< endl; return(false); break; } // read number of tracks if(fread(buf, sizeof(char), 2, file) != 2) { cerr<< __PRETTY_FUNCTION__<< ": can't read number of tracks: "<< strerror(errno)<< endl; return(false); } else { tracks = buf[0] * 256 + buf[1]; #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": " << tracks<< " tracks"<< endl; #endif } // read division if(fread(buf, sizeof(char), 2, file) != 2) { cerr<< __PRETTY_FUNCTION__<< ": can't read division: "<< strerror(errno)<< endl; return(false); } else { division = (int)buf[0] * 256 + (int)buf[1]; if(division > 0) { sequence->setDivision(division); #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": division: "<< division<< " delta-times/quarter-note"<< endl; #endif } else { sequence->setDivision((int)-buf[0], (int)buf[1]); #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": "<< (int)-buf[0]<< " frames/second, "<< (int)buf[1]<< " units/frame"<< endl; #endif } } return(true); } static bool readMidiTrack(FILE *file, midiTrack *track) { char magic[4]; unsigned char buf[4]; int len; unsigned char *trackbuf; int current; // read magic value if(fread(magic, sizeof(char), 4, file) != 4) { cerr<< __PRETTY_FUNCTION__<< ": can't read magic value: "<< strerror(errno)<< endl; return(false); } // compare if(memcmp(magic, trackChunkMagic, 4) != 0) { cerr<< __PRETTY_FUNCTION__<< ": magic value fails"<< endl; return(false); } else { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": magic value correct"<< endl; #endif } // read length of the track if(fread(buf, sizeof(char), 4, file) != 4) { cerr<< __PRETTY_FUNCTION__<< ": can't read track length: "<< strerror(errno)<< endl; return(false); } else { len = buf[0] * 256 * 256 * 256 + buf[1] * 256 * 256 + buf[2] * 256 + buf[3]; #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": track length: "<< len<< " bytes"<< endl; #endif } trackbuf = new unsigned char[len]; if(fread(trackbuf, sizeof(unsigned char), len, file) != (size_t)len) { cerr<< __PRETTY_FUNCTION__<< ": can't read "<< len<< " bytes of track data: "<< strerror(errno)<< endl; delete[] trackbuf; return(false); } current = 0; // a track is a number of MTrk events, so read the events while(current < len) { current += readMtrkEvent(&trackbuf[current], track); } delete[] trackbuf; return(true); } static int readMtrkEvent(unsigned char *buf, midiTrack *track) { int current = 0; int deltaTime; // first read the delta time current += readVarLengthInt(&buf[current], deltaTime); // look to the event type and call appropriate function switch(buf[current++]) { case 0xff: current += readMetaEvent(&buf[current], track, deltaTime); break; case 0xf0: case 0xf7: current += readSysexEvent(&buf[current], track, deltaTime); break; default: current --; current += readMidiEvent(&buf[current], track, deltaTime); break; } return(current); } static int readMetaEvent(unsigned char *buf, midiTrack *track, int deltaTime) { midiEvent *event = 0; int current = 0; int type; int len; int number; int tempo; int hours, minutes, seconds, frames, fraction; int numerator, denominator, clocks, bb; int signature, key; char *string; type = buf[current++]; switch(type) { case 0x00: // sequence number len = buf[current++]; if(len != 2) { cerr<< " len = "<< len<< ", MIDI file is corrupted!"<< endl; } else { number = buf[current++]; number = (number << 8 ) + buf[current++]; event = new midiSequenceNumberEvent(deltaTime, 0, number); } break; case 0x01: // text len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiTextEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x02: // copyright notice len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiCopyrightNoticeEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x03: // track name len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiTrackNameEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x04: // instrument name len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiIntrumentNameEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x05: // lyric len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiLyricEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x06: // marker len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiMarkerEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x07: // cue point len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiCuePointEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x08: // reserved text case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: len = buf[current++]; string = new char[len + 1]; strncpy(string, (char *)&buf[current], len); string[len] = '\0'; event = new midiReservedTextEvent(deltaTime, 0, string); delete[] string; current += len; break; case 0x21: len = buf[current++]; // FIXME: change port event, no idea what to do with it #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": change port event ignored"<< endl; #endif event = new midiUnknownEvent(deltaTime, 0); current += len; break; case 0x2f: len = buf[current++]; // len should be 0 event = new midiEndOfTrackEvent(deltaTime, 0); break; case 0x51: // tempo len = buf[current++]; tempo = buf[current++]; tempo = (tempo << 8) + buf[current++]; tempo = (tempo << 8) + buf[current++]; event = new midiTempoEvent(deltaTime, 0, tempo); //cerr<< tempo<< " microseconds / MIDI quarter-note"<< endl; break; case 0x54: // SMPTE offset len = buf[current++]; // len should be 5 hours = buf[current++]; minutes = buf[current++]; seconds = buf[current++]; frames = buf[current++]; fraction = buf[current++]; event = new midiSmpteOffsetEvent(deltaTime, 0, hours, minutes, seconds, frames, fraction); break; case 0x58: // time signature len = buf[current++]; // len should be 4 numerator = buf[current++]; denominator = buf[current++]; clocks = buf[current++]; bb = buf[current++]; event = new midiTimeSignatureEvent(deltaTime, 0, numerator, denominator, clocks, bb); // cerr<< numerator<< "/"<< denominator<< ", "<< // clocks<< " MIDI clocks/metrome tick, "<< // bb<< " 32nd notes/MIDI quarter note"<< endl; break; case 0x59: // key signature len = buf[current++]; // len should be 2 signature = buf[current++]; key = buf[current++]; event = new midiKeySignatureEvent(deltaTime, 0, key, signature); // if(signature < 0) // cerr<< -signature<< " flats, "; // else if(signature > 0) // cerr<< signature<< " sharps, "; // else // signature == 0 // cerr<< " key of C, "; // if(key == 0) // cerr<< "major key"<< endl; // else // key == 1 // cerr<< "minor key"<< endl; break; case 0x7f: len = buf[current++]; event = new midiSequencerSpecificEvent(deltaTime, 0, &buf[current], len); current += len; break; default: #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": unknown meta event type: "<< type; #endif len = buf[current++]; #ifdef MUSICA_DEBUG cerr<< ", ignoring "<< len<< " bytes of data"<< endl; #endif event = new midiUnknownEvent(deltaTime, 0); current += len; break; } // add freshly made event if(event != 0) track->addEvent(event); return(current); } static int readSysexEvent(unsigned char *buf, midiTrack *track, int deltaTime) { int current = 0; return(current); } static int readMidiEvent(unsigned char *buf, midiTrack *track, int deltaTime) { midiEvent *event = 0; static int status = 0; int current = 0; int command; int channel, key, velocity; int pressure; int wheelfine, wheelcoarse; int controller, value; int program; command = buf[current++]; if((command & 0x80) == 0x80) { // this was indeed a status byte, update it status = command; } else { // this was not a status byte, use the running status current --; } switch(status & 0xf0) { case 0x90: channel = status & 0x0f; key = buf[current++]; velocity = buf[current++]; event = new midiNoteOnEvent(deltaTime, 0, channel, key, velocity); break; case 0x80: channel = status & 0x0f; key = buf[current++]; velocity = buf[current++]; event = new midiNoteOffEvent(deltaTime, 0, channel, key, velocity); break; case 0xa0: channel = status & 0x0f; key = buf[current++]; pressure = buf[current++]; event = new midiKeyPressureEvent(deltaTime, 0, channel, key, pressure); break; case 0xd0: channel = status & 0x0f; pressure = buf[current++]; event = new midiChannelPressureEvent(deltaTime, 0, channel, pressure); break; case 0xe0: channel = status & 0x0f; wheelfine = buf[current++]; wheelcoarse = buf[current++]; event = new midiPitchWheelEvent(deltaTime, 0, channel, wheelfine, wheelcoarse); break; case 0xb0: channel = status & 0x0f; controller = buf[current++]; value = buf[current++]; event = new midiControlChangeEvent(deltaTime, 0, channel, controller, value); break; case 0xc0: channel = status & 0x0f; program = buf[current++]; event = new midiProgramChangeEvent(deltaTime, 0, channel, program); break; default: #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< ": unknown MIDI channel event: "<< (status & 0x7f)<< ", skipping one byte"<< endl; #endif current++; event = new midiUnknownEvent(deltaTime, 0); break; } // add freshly made event if(event != 0) track->addEvent(event); return(current); } static int readVarLengthInt(unsigned char *buf, int &data) { int numRead = 0; data = 0; while(*buf & (unsigned char)0x80) { data = (data << 7) + (int)(*buf & (unsigned char)0x7f); buf++; numRead++; } data = (data << 7) + (int)(*buf & (unsigned char)0x7f); numRead++; return(numRead); }