// // Programmer: Craig Stuart Sapp // Creation Date: Fri Nov 26 14:12:01 PST 1999 // Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; // Filename: midifile/src-library/MidiFile.cpp // Website: http://midifile.sapp.org // Syntax: C++11 // vim: ts=3 noexpandtab // // Description: A class which can read/write Standard MIDI files. // MIDI data is stored by track in an array. This // class is used for example in the MidiPerform class. // #include "MidiFile.h" #include "Binasc.h" #include #include #include #include #include #include #include #include namespace smf { ////////////////////////////// // // MidiFile::MidiFile -- Constuctor. // MidiFile::MidiFile(void) { m_events.resize(m_trackCount); for (int i=0; iMidiEventList* { return new MidiEventList(**it++); } ); m_ticksPerQuarterNote = other.m_ticksPerQuarterNote; m_trackCount = other.m_trackCount; m_theTrackState = other.m_theTrackState; m_theTimeState = other.m_theTimeState; m_readFileName = other.m_readFileName; m_timemapvalid = other.m_timemapvalid; m_timemap = other.m_timemap; m_rwstatus = other.m_rwstatus; if (other.m_linkedEventsQ) { linkEventPairs(); } return *this; } MidiFile& MidiFile::operator=(MidiFile&& other) { m_events = std::move(other.m_events); m_linkedEventsQ = other.m_linkedEventsQ; other.m_linkedEventsQ = false; other.m_events.clear(); other.m_events.emplace_back(new MidiEventList); m_ticksPerQuarterNote = other.m_ticksPerQuarterNote; m_trackCount = other.m_trackCount; m_theTrackState = other.m_theTrackState; m_theTimeState = other.m_theTimeState; m_readFileName = other.m_readFileName; m_timemapvalid = other.m_timemapvalid; m_timemap = other.m_timemap; m_rwstatus = other.m_rwstatus; return *this; } /////////////////////////////////////////////////////////////////////////// // // reading/writing functions -- // ////////////////////////////// // // MidiFile::read -- Parse a Standard MIDI File and store its contents // in the object. // bool MidiFile::read(const std::string& filename) { m_timemapvalid = 0; setFilename(filename); m_rwstatus = true; std::fstream input; input.open(filename.c_str(), std::ios::binary | std::ios::in); if (!input.is_open()) { m_rwstatus = false; return m_rwstatus; } m_rwstatus = read(input); return m_rwstatus; } // // istream version of read(). // bool MidiFile::read(std::istream& input) { m_rwstatus = true; if (input.peek() != 'M') { // If the first byte in the input stream is not 'M', then presume that // the MIDI file is in the binasc format which is an ASCII representation // of the MIDI file. Convert the binasc content into binary content and // then continue reading with this function. std::stringstream binarydata; Binasc binasc; binasc.writeToBinary(binarydata, input); binarydata.seekg(0, std::ios_base::beg); if (binarydata.peek() != 'M') { std::cerr << "Bad MIDI data input" << std::endl; m_rwstatus = false; return m_rwstatus; } else { m_rwstatus = read(binarydata); return m_rwstatus; } } std::string filename = getFilename(); int character; // uchar buffer[123456] = {0}; ulong longdata; ushort shortdata; // Read the MIDI header (4 bytes of ID, 4 byte data size, // anticipated 6 bytes of data. character = input.get(); if (character == EOF) { std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; std::cerr << "Expecting 'M' at first byte, but found nothing." << std::endl; m_rwstatus = false; return m_rwstatus; } else if (character != 'M') { std::cerr << "File " << filename << " is not a MIDI file" << std::endl; std::cerr << "Expecting 'M' at first byte but got '" << (char)character << "'" << std::endl; m_rwstatus = false; return m_rwstatus; } character = input.get(); if (character == EOF) { std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; std::cerr << "Expecting 'T' at second byte, but found nothing." << std::endl; m_rwstatus = false; return m_rwstatus; } else if (character != 'T') { std::cerr << "File " << filename << " is not a MIDI file" << std::endl; std::cerr << "Expecting 'T' at second byte but got '" << (char)character << "'" << std::endl; m_rwstatus = false; return m_rwstatus; } character = input.get(); if (character == EOF) { std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; std::cerr << "Expecting 'h' at third byte, but found nothing." << std::endl; m_rwstatus = false; return m_rwstatus; } else if (character != 'h') { std::cerr << "File " << filename << " is not a MIDI file" << std::endl; std::cerr << "Expecting 'h' at third byte but got '" << (char)character << "'" << std::endl; m_rwstatus = false; return m_rwstatus; } character = input.get(); if (character == EOF) { std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; std::cerr << "Expecting 'd' at fourth byte, but found nothing." << std::endl; m_rwstatus = false; return m_rwstatus; } else if (character != 'd') { std::cerr << "File " << filename << " is not a MIDI file" << std::endl; std::cerr << "Expecting 'd' at fourth byte but got '" << (char)character << "'" << std::endl; m_rwstatus = false; return m_rwstatus; } // read header size (allow larger header size?) longdata = readLittleEndian4Bytes(input); if (longdata != 6) { std::cerr << "File " << filename << " is not a MIDI 1.0 Standard MIDI file." << std::endl; std::cerr << "The header size is " << longdata << " bytes." << std::endl; m_rwstatus = false; return m_rwstatus; } // Header parameter #1: format type int type; shortdata = readLittleEndian2Bytes(input); switch (shortdata) { case 0: type = 0; break; case 1: type = 1; break; case 2: // Type-2 MIDI files should probably be allowed as well, // but I have never seen one in the wild to test with. default: std::cerr << "Error: cannot handle a type-" << shortdata << " MIDI file" << std::endl; m_rwstatus = false; return m_rwstatus; } // Header parameter #2: track count int tracks; shortdata = readLittleEndian2Bytes(input); if (type == 0 && shortdata != 1) { std::cerr << "Error: Type 0 MIDI file can only contain one track" << std::endl; std::cerr << "Instead track count is: " << shortdata << std::endl; m_rwstatus = false; return m_rwstatus; } else { tracks = shortdata; } clear(); if (m_events[0] != NULL) { delete m_events[0]; } m_events.resize(tracks); for (int z=0; zreserve(10000); // Initialize with 10,000 event storage. m_events[z]->clear(); } // Header parameter #3: Ticks per quarter note shortdata = readLittleEndian2Bytes(input); if (shortdata >= 0x8000) { int framespersecond = 255 - ((shortdata >> 8) & 0x00ff) + 1; int subframes = shortdata & 0x00ff; switch (framespersecond) { case 25: framespersecond = 25; break; case 24: framespersecond = 24; break; case 29: framespersecond = 29; break; // really 29.97 for color television case 30: framespersecond = 30; break; default: std::cerr << "Warning: unknown FPS: " << framespersecond << std::endl; std::cerr << "Using non-standard FPS: " << framespersecond << std::endl; } m_ticksPerQuarterNote = framespersecond * subframes; // std::cerr << "SMPTE ticks: " << m_ticksPerQuarterNote << " ticks/sec" << std::endl; // std::cerr << "SMPTE frames per second: " << framespersecond << std::endl; // std::cerr << "SMPTE subframes per frame: " << subframes << std::endl; } else { m_ticksPerQuarterNote = shortdata; } ////////////////////////////////////////////////// // // now read individual tracks: // uchar runningCommand; MidiEvent event; std::vector bytes; int xstatus; // int barline; for (int i=0; ireserve((int)longdata/2); m_events[i]->clear(); // process the track int absticks = 0; // barline = 1; while (!input.eof()) { longdata = readVLValue(input); //std::cout << "ticks = " << longdata << std::endl; absticks += longdata; xstatus = extractMidiData(input, bytes, runningCommand); if (xstatus == 0) { m_rwstatus = false; return m_rwstatus; } event.setMessage(bytes); //std::cout << "command = " << std::hex << (int)event.data[0] << std::dec << std::endl; if (bytes[0] == 0xff && (bytes[1] == 1 || bytes[1] == 2 || bytes[1] == 3 || bytes[1] == 4)) { // mididata.push_back('\0'); // std::cout << '\t'; // for (int m=0; mpush_back(event); break; } if (bytes[0] != 0xff && bytes[0] != 0xf0) { event.tick = absticks; event.track = i; m_events[i]->push_back(event); } else { event.tick = absticks; event.track = i; m_events[i]->push_back(event); } } } m_theTimeState = TIME_STATE_ABSOLUTE; markSequence(); return m_rwstatus; } ////////////////////////////// // // MidiFile::write -- write a standard MIDI file to a file or an output // stream. // bool MidiFile::write(const std::string& filename) { std::fstream output(filename.c_str(), std::ios::binary | std::ios::out); if (!output.is_open()) { std::cerr << "Error: could not write: " << filename << std::endl; return false; } m_rwstatus = write(output); output.close(); return m_rwstatus; } // // ostream version of MidiFile::write(). // bool MidiFile::write(std::ostream& out) { int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_ABSOLUTE) { makeDeltaTicks(); } // write the header of the Standard MIDI File char ch; // 1. The characters "MThd" ch = 'M'; out << ch; ch = 'T'; out << ch; ch = 'h'; out << ch; ch = 'd'; out << ch; // 2. write the size of the header (always a "6" stored in unsigned long // (4 bytes). ulong longdata = 6; writeBigEndianULong(out, longdata); // 3. MIDI file format, type 0, 1, or 2 ushort shortdata; shortdata = (getNumTracks() == 1) ? 0 : 1; writeBigEndianUShort(out,shortdata); // 4. write out the number of tracks. shortdata = getNumTracks(); writeBigEndianUShort(out, shortdata); // 5. write out the number of ticks per quarternote. (avoiding SMTPE for now) shortdata = getTicksPerQuarterNote(); writeBigEndianUShort(out, shortdata); // now write each track. std::vector trackdata; uchar endoftrack[4] = {0, 0xff, 0x2f, 0x00}; int i, j, k; int size; for (i=0; isize(); j++) { if ((*m_events[i])[j].empty()) { // Don't write empty m_events (probably a delete message). continue; } if ((*m_events[i])[j].isEndOfTrack()) { // Suppress end-of-track meta messages (one will be added // automatically after all track data has been written). continue; } writeVLValue((*m_events[i])[j].tick, trackdata); if (((*m_events[i])[j].getCommandByte() == 0xf0) || ((*m_events[i])[j].getCommandByte() == 0xf7)) { // 0xf0 == Complete sysex message (0xf0 is part of the raw MIDI). // 0xf7 == Raw byte message (0xf7 not part of the raw MIDI). // Print the first byte of the message (0xf0 or 0xf7), then // print a VLV length for the rest of the bytes in the message. // In other words, when creating a 0xf0 or 0xf7 MIDI message, // do not insert the VLV byte length yourself, as this code will // do it for you automatically. trackdata.push_back((*m_events[i])[j][0]); // 0xf0 or 0xf7; writeVLValue(((int)(*m_events[i])[j].size())-1, trackdata); for (k=1; k<(int)(*m_events[i])[j].size(); k++) { trackdata.push_back((*m_events[i])[j][k]); } } else { // non-sysex type of message, so just output the // bytes of the message: for (k=0; k<(int)(*m_events[i])[j].size(); k++) { trackdata.push_back((*m_events[i])[j][k]); } } } size = (int)trackdata.size(); if ((size < 3) || !((trackdata[size-3] == 0xff) && (trackdata[size-2] == 0x2f))) { trackdata.push_back(endoftrack[0]); trackdata.push_back(endoftrack[1]); trackdata.push_back(endoftrack[2]); trackdata.push_back(endoftrack[3]); } // now ready to write to MIDI file. // first write the track ID marker "MTrk": ch = 'M'; out << ch; ch = 'T'; out << ch; ch = 'r'; out << ch; ch = 'k'; out << ch; // A. write the size of the MIDI data to follow: longdata = (int)trackdata.size(); writeBigEndianULong(out, longdata); // B. write the actual data out.write((char*)trackdata.data(), trackdata.size()); } if (oldTimeState == TIME_STATE_ABSOLUTE) { makeAbsoluteTicks(); } return true; } ////////////////////////////// // // MidiFile::writeHex -- print the Standard MIDI file as a list of // ASCII Hex bytes, formatted 25 to a line by default, and // two digits for each hex byte code. If the input width is 0, // then don't wrap lines. // // default value: width=25 // bool MidiFile::writeHex(const std::string& filename, int width) { std::fstream output(filename.c_str(), std::ios::out); if (!output.is_open()) { std::cerr << "Error: could not write: " << filename << std::endl; return false; } m_rwstatus = writeHex(output, width); output.close(); return m_rwstatus; } // // ostream version of MidiFile::writeHex(). // bool MidiFile::writeHex(std::ostream& out, int width) { std::stringstream tempstream; MidiFile::write(tempstream); int len = (int)tempstream.str().length(); int wordcount = 1; int linewidth = width >= 0 ? width : 25; for (int i=0; iremoveEmpties(); } } ////////////////////////////// // // MidiFile::markSequence -- Assign a sequence serial number to // every MidiEvent in every track in the MIDI file. This is // useful if you want to preseve the order of MIDI messages in // a track when they occur at the same tick time. Particularly // for use with joinTracks() or sortTracks(). markSequence will // be done automatically when a MIDI file is read, in case the // ordering of m_events occuring at the same time is important. // Use clearSequence() to use the default sorting behavior of // sortTracks(). // void MidiFile::markSequence(void) { int sequence = 1; for (int i=0; i= 0) && (track < getTrackCount())) { operator[](track).markSequence(sequence); } else { std::cerr << "Warning: track " << track << " does not exist." << std::endl; } } ////////////////////////////// // // MidiFile::clearSequence -- Remove any seqence serial numbers from // MidiEvents in the MidiFile. This will cause the default ordering by // sortTracks() to be used, in which case the ordering of MidiEvents // occurring at the same tick may switch their ordering. // void MidiFile::clearSequence(void) { for (int i=0; i= 0) && (track < getTrackCount())) { operator[](track).clearSequence(); } else { std::cerr << "Warning: track " << track << " does not exist." << std::endl; } } ////////////////////////////// // // MidiFile::joinTracks -- Interleave the data from all tracks, // but keeping the identity of the tracks unique so that // the function splitTracks can be called to split the // tracks into separate units again. The style of the // MidiFile when read from a file is with tracks split. // The original track index is stored in the MidiEvent::track // variable. // void MidiFile::joinTracks(void) { if (getTrackState() == TRACK_STATE_JOINED) { return; } if (getNumTracks() == 1) { m_theTrackState = TRACK_STATE_JOINED; return; } MidiEventList* joinedTrack; joinedTrack = new MidiEventList; int messagesum = 0; int length = getNumTracks(); int i, j; for (i=0; ireserve((int)(messagesum + 32 + messagesum * 0.1)); int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_DELTA) { makeAbsoluteTicks(); } for (i=0; isize(); j++) { joinedTrack->push_back_no_copy(&(*m_events[i])[j]); } } clear_no_deallocate(); delete m_events[0]; m_events.resize(0); m_events.push_back(joinedTrack); sortTracks(); if (oldTimeState == TIME_STATE_DELTA) { makeDeltaTicks(); } m_theTrackState = TRACK_STATE_JOINED; } ////////////////////////////// // // MidiFile::splitTracks -- Take the joined tracks and split them // back into their separate track identities. // void MidiFile::splitTracks(void) { if (getTrackState() == TRACK_STATE_SPLIT) { return; } int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_DELTA) { makeAbsoluteTicks(); } int maxTrack = 0; int i; int length = m_events[0]->size(); for (i=0; i maxTrack) { maxTrack = (*m_events[0])[i].track; } } int m_trackCount = maxTrack + 1; if (m_trackCount <= 1) { return; } MidiEventList* olddata = m_events[0]; m_events[0] = NULL; m_events.resize(m_trackCount); for (i=0; ipush_back_no_copy(&(*olddata)[i]); } olddata->detach(); delete olddata; if (oldTimeState == TIME_STATE_DELTA) { makeDeltaTicks(); } m_theTrackState = TRACK_STATE_SPLIT; } ////////////////////////////// // // MidiFile::splitTracksByChannel -- Take the joined tracks and split them // back into their separate track identities. // void MidiFile::splitTracksByChannel(void) { joinTracks(); if (getTrackState() == TRACK_STATE_SPLIT) { return; } int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_DELTA) { makeAbsoluteTicks(); } int maxTrack = 0; int i; MidiEventList& eventlist = *m_events[0]; MidiEventList* olddata = &eventlist; int length = eventlist.size(); for (i=0; i 0) { trackValue = (eventlist[i][0] & 0x0f) + 1; } m_events[trackValue]->push_back_no_copy(&eventlist[i]); } olddata->detach(); delete olddata; if (oldTimeState == TIME_STATE_DELTA) { makeDeltaTicks(); } m_theTrackState = TRACK_STATE_SPLIT; } ////////////////////////////// // // MidiFile::getTrackState -- returns what type of track method // is being used: either TRACK_STATE_JOINED or TRACK_STATE_SPLIT. // int MidiFile::getTrackState(void) const { return m_theTrackState; } ////////////////////////////// // // MidiFile::hasJoinedTracks -- Returns true if the MidiFile tracks // are in a joined state. // int MidiFile::hasJoinedTracks(void) const { return m_theTrackState == TRACK_STATE_JOINED; } ////////////////////////////// // // MidiFile::hasSplitTracks -- Returns true if the MidiFile tracks // are in a split state. // int MidiFile::hasSplitTracks(void) const { return m_theTrackState == TRACK_STATE_SPLIT; } ////////////////////////////// // // MidiFile::getSplitTrack -- Return the track index when the MidiFile // is in the split state. This function returns the original track // when the MidiFile is in the joined state. The MidiEvent::track // variable is used to store the original track index when the // MidiFile is converted to the joined-track state. // int MidiFile::getSplitTrack(int track, int index) const { if (hasSplitTracks()) { return track; } else { return getEvent(track, index).track; } } // // When the parameter is void, assume track 0: // int MidiFile::getSplitTrack(int index) const { if (hasSplitTracks()) { return 0; } else { return getEvent(0, index).track; } } /////////////////////////////////////////////////////////////////////////// // // tick-related functions -- // ////////////////////////////// // // MidiFile::makeDeltaTicks -- convert the time data to // delta time, which means that the time field // in the MidiEvent struct represents the time // since the last event was played. When a MIDI file // is read from a file, this is the default setting. // void MidiFile::makeDeltaTicks(void) { if (getTickState() == TIME_STATE_DELTA) { return; } int i, j; int temp; int length = getNumTracks(); int *timedata = new int[length]; for (i=0; isize() > 0) { timedata[i] = (*m_events[i])[0].tick; } else { continue; } for (j=1; j<(int)m_events[i]->size(); j++) { temp = (*m_events[i])[j].tick; int deltatick = temp - timedata[i]; if (deltatick < 0) { std::cerr << "Error: negative delta tick value: " << deltatick << std::endl << "Timestamps must be sorted first" << " (use MidiFile::sortTracks() before writing)." << std::endl; } (*m_events[i])[j].tick = deltatick; timedata[i] = temp; } } m_theTimeState = TIME_STATE_DELTA; delete [] timedata; } // // MidiFile::deltaTicks -- Alias for MidiFile::makeDeltaTicks(). // void MidiFile::deltaTicks(void) { makeDeltaTicks(); } ////////////////////////////// // // MidiFile::makeAbsoluteTicks -- convert the time data to // absolute time, which means that the time field // in the MidiEvent struct represents the exact tick // time to play the event rather than the time since // the last event to wait untill playing the current // event. // void MidiFile::makeAbsoluteTicks(void) { if (getTickState() == TIME_STATE_ABSOLUTE) { return; } int i, j; int length = getNumTracks(); int* timedata = new int[length]; for (i=0; isize() > 0) { timedata[i] = (*m_events[i])[0].tick; } else { continue; } for (j=1; j<(int)m_events[i]->size(); j++) { timedata[i] += (*m_events[i])[j].tick; (*m_events[i])[j].tick = timedata[i]; } } m_theTimeState = TIME_STATE_ABSOLUTE; delete [] timedata; } // // MidiFile::absoluteTicks -- Alias for MidiFile::makeAbsoluteTicks(). // void MidiFile::absoluteTicks(void) { makeAbsoluteTicks(); } ////////////////////////////// // // MidiFile::getTickState -- returns what type of time method is // being used: either TIME_STATE_ABSOLUTE or TIME_STATE_DELTA. // int MidiFile::getTickState(void) const { return m_theTimeState; } ////////////////////////////// // // MidiFile::isDeltaTicks -- Returns true if MidiEvent .tick // variables are in delta time mode. // bool MidiFile::isDeltaTicks(void) const { return m_theTimeState == TIME_STATE_DELTA ? true : false; } ////////////////////////////// // // MidiFile::isAbsoluteTicks -- Returns true if MidiEvent .tick // variables are in absolute time mode. // bool MidiFile::isAbsoluteTicks(void) const { return m_theTimeState == TIME_STATE_ABSOLUTE ? true : false; } ////////////////////////////// // // MidiFile::getFileDurationInTicks -- Returns the largest // tick value in any track. The tracks must be sorted // before calling this function, since this function // assumes that the last MidiEvent in the track has the // highest tick timestamp. The file state can be in delta // ticks since this function will temporarily go to absolute // tick mode for the calculation of the max tick. // int MidiFile::getFileDurationInTicks(void) { bool revertToDelta = false; if (isDeltaTicks()) { makeAbsoluteTicks(); revertToDelta = true; } const MidiFile& mf = *this; int output = 0; for (int i=0; i output) { output = mf[i].back().tick; } } if (revertToDelta) { deltaTicks(); } return output; } /////////////////////////////// // // MidiFile::getFileDurationInQuarters -- Returns the Duration of the MidiFile // in units of quarter notes. If the MidiFile is in delta tick mode, // then temporarily got into absolute tick mode to do the calculations. // Note that this is expensive, so you should normally call this function // while in aboslute tick (default) mode. // double MidiFile::getFileDurationInQuarters(void) { return (double)getFileDurationInTicks() / (double)getTicksPerQuarterNote(); } ////////////////////////////// // // MidiFile::getFileDurationInSeconds -- returns the duration of the // logest track in the file. The tracks must be sorted before // calling this function, since this function assumes that the // last MidiEvent in the track has the highest timestamp. // The file state can be in delta ticks since this function // will temporarily go to absolute tick mode for the calculation // of the max time. double MidiFile::getFileDurationInSeconds(void) { if (m_timemapvalid == 0) { buildTimeMap(); if (m_timemapvalid == 0) { return -1.0; // something went wrong } } bool revertToDelta = false; if (isDeltaTicks()) { makeAbsoluteTicks(); revertToDelta = true; } const MidiFile& mf = *this; double output = 0.0; for (int i=0; i output) { output = mf[i].back().seconds; } } if (revertToDelta) { deltaTicks(); } return output; } /////////////////////////////////////////////////////////////////////////// // // physical-time analysis functions -- // ////////////////////////////// // // MidiFile::doTimeAnalysis -- Identify the real-time position of // all events by monitoring the tempo in relations to the tick // times in the file. // void MidiFile::doTimeAnalysis(void) { buildTimeMap(); } ////////////////////////////// // // MidiFile::getTimeInSeconds -- return the time in seconds for // the current message. // double MidiFile::getTimeInSeconds(int aTrack, int anIndex) { return getTimeInSeconds(getEvent(aTrack, anIndex).tick); } double MidiFile::getTimeInSeconds(int tickvalue) { if (m_timemapvalid == 0) { buildTimeMap(); if (m_timemapvalid == 0) { return -1.0; // something went wrong } } _TickTime key; key.tick = tickvalue; key.seconds = -1; void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(), sizeof(_TickTime), ticksearch); if (ptr == NULL) { // The specific tick value was not found, so do a linear // search for the two tick values which occur before and // after the tick value, and do a linear interpolation of // the time in seconds values to figure out the final // time in seconds. // Since the code is not yet written, kill the program at this point: return linearSecondInterpolationAtTick(tickvalue); } else { return ((_TickTime*)ptr)->seconds; } } ////////////////////////////// // // MidiFile::getAbsoluteTickTime -- return the tick value represented // by the input time in seconds. If there is not tick entry at // the given time in seconds, then interpolate between two values. // double MidiFile::getAbsoluteTickTime(double starttime) { if (m_timemapvalid == 0) { buildTimeMap(); if (m_timemapvalid == 0) { if (m_timemapvalid == 0) { return -1.0; // something went wrong } } } _TickTime key; key.tick = -1; key.seconds = starttime; void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(), sizeof(_TickTime), secondsearch); if (ptr == NULL) { // The specific seconds value was not found, so do a linear // search for the two time values which occur before and // after the given time value, and do a linear interpolation of // the time in tick values to figure out the final time in ticks. return linearTickInterpolationAtSecond(starttime); } else { return ((_TickTime*)ptr)->tick; } } /////////////////////////////////////////////////////////////////////////// // // note-analysis functions -- // ////////////////////////////// // // MidiFile::linkNotePairs -- Link note-ons to note-offs separately // for each track. Returns the total number of note message pairs // that were linked. // int MidiFile::linkNotePairs(void) { int i; int sum = 0; for (i=0; ilinkNotePairs(); } m_linkedEventsQ = true; return sum; } // // MidiFile::linkEventPairs -- Alias for MidiFile::linkNotePairs(). // int MidiFile::linkEventPairs(void) { return linkNotePairs(); } /////////////////////////////////////////////////////////////////////////// // // filename functions -- // ////////////////////////////// // // MidiFile::setFilename -- sets the filename of the MIDI file. // Currently removed any directory path. // void MidiFile::setFilename(const std::string& aname) { auto loc = aname.rfind('/'); if (loc != std::string::npos) { m_readFileName = aname.substr(loc+1); } else { m_readFileName = aname; } } ////////////////////////////// // // MidiFile::getFilename -- returns the name of the file read into the // structure (if the data was read from a file). // const char* MidiFile::getFilename(void) const { return m_readFileName.c_str(); } ////////////////////////////// // // MidiFile::addEvent -- // MidiEvent* MidiFile::addEvent(int aTrack, int aTick, std::vector& midiData) { m_timemapvalid = 0; MidiEvent* me = new MidiEvent; me->tick = aTick; me->track = aTrack; me->setMessage(midiData); m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addEvent -- Some bug here when joinedTracks(), but track==1... // MidiEvent* MidiFile::addEvent(MidiEvent& mfevent) { if (getTrackState() == TRACK_STATE_JOINED) { m_events[0]->push_back(mfevent); return &m_events[0]->back(); } else { m_events.at(mfevent.track)->push_back(mfevent); return &m_events.at(mfevent.track)->back(); } } // // Variant where the track is an input parameter: // MidiEvent* MidiFile::addEvent(int aTrack, MidiEvent& mfevent) { if (getTrackState() == TRACK_STATE_JOINED) { m_events[0]->push_back(mfevent); m_events[0]->back().track = aTrack; return &m_events[0]->back(); } else { m_events.at(aTrack)->push_back(mfevent); m_events.at(aTrack)->back().track = aTrack; return &m_events.at(aTrack)->back(); } } /////////////////////////////// // // MidiFile::addMetaEvent -- // MidiEvent* MidiFile::addMetaEvent(int aTrack, int aTick, int aType, std::vector& metaData) { m_timemapvalid = 0; int i; int length = (int)metaData.size(); std::vector fulldata; uchar size[23] = {0}; int lengthsize = makeVLV(size, length); fulldata.resize(2+lengthsize+length); fulldata[0] = 0xff; fulldata[1] = aType & 0x7F; for (i=0; i buffer; buffer.resize(length); int i; for (i=0; imakeText(text); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addCopyright -- Add a copyright notice meta-message (#2). // MidiEvent* MidiFile::addCopyright(int aTrack, int aTick, const std::string& text) { MidiEvent* me = new MidiEvent; me->makeCopyright(text); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addTrackName -- Add an track name meta-message (#3). // MidiEvent* MidiFile::addTrackName(int aTrack, int aTick, const std::string& name) { MidiEvent* me = new MidiEvent; me->makeTrackName(name); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addInstrumentName -- Add an instrument name meta-message (#4). // MidiEvent* MidiFile::addInstrumentName(int aTrack, int aTick, const std::string& name) { MidiEvent* me = new MidiEvent; me->makeInstrumentName(name); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addLyric -- Add a lyric meta-message (meta #5). // MidiEvent* MidiFile::addLyric(int aTrack, int aTick, const std::string& text) { MidiEvent* me = new MidiEvent; me->makeLyric(text); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addMarker -- Add a marker meta-message (meta #6). // MidiEvent* MidiFile::addMarker(int aTrack, int aTick, const std::string& text) { MidiEvent* me = new MidiEvent; me->makeMarker(text); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addCue -- Add a cue-point meta-message (meta #7). // MidiEvent* MidiFile::addCue(int aTrack, int aTick, const std::string& text) { MidiEvent* me = new MidiEvent; me->makeCue(text); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addTempo -- Add a tempo meta message (meta #0x51). // MidiEvent* MidiFile::addTempo(int aTrack, int aTick, double aTempo) { MidiEvent* me = new MidiEvent; me->makeTempo(aTempo); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addTimeSignature -- Add a time signature meta message // (meta #0x58). The "bottom" parameter must be a power of two; // otherwise, it will be set to the next highest power of two. // // Default values: // clocksPerClick == 24 (quarter note) // num32ndsPerQuarter == 8 (8 32nds per quarter note) // // Time signature of 4/4 would be: // top = 4 // bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2). // clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter) // num32ndsPerQuarter = 8 // // Time signature of 6/8 would be: // top = 6 // bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2). // clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter) // num32ndsPerQuarter = 8 // MidiEvent* MidiFile::addTimeSignature(int aTrack, int aTick, int top, int bottom, int clocksPerClick, int num32ndsPerQuarter) { MidiEvent* me = new MidiEvent; me->makeTimeSignature(top, bottom, clocksPerClick, num32ndsPerQuarter); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addCompoundTimeSignature -- Add a time signature meta message // (meta #0x58), where the clocksPerClick parameter is set to three // eighth notes for compount meters such as 6/8 which represents // two beats per measure. // // Default values: // clocksPerClick == 36 (quarter note) // num32ndsPerQuarter == 8 (8 32nds per quarter note) // MidiEvent* MidiFile::addCompoundTimeSignature(int aTrack, int aTick, int top, int bottom, int clocksPerClick, int num32ndsPerQuarter) { return addTimeSignature(aTrack, aTick, top, bottom, clocksPerClick, num32ndsPerQuarter); } ////////////////////////////// // // MidiFile::makeVLV -- This function is used to create // size byte(s) for meta-messages. If the size of the data // in the meta-message is greater than 127, then the size // should (?) be specified as a VLV. // int MidiFile::makeVLV(uchar *buffer, int number) { unsigned long value = (unsigned long)number; if (value >= (1 << 28)) { std::cerr << "Error: Meta-message size too large to handle" << std::endl; buffer[0] = 0; buffer[1] = 0; buffer[2] = 0; buffer[3] = 0; return 1; } buffer[0] = (value >> 21) & 0x7f; buffer[1] = (value >> 14) & 0x7f; buffer[2] = (value >> 7) & 0x7f; buffer[3] = (value >> 0) & 0x7f; int i; int flag = 0; int length = -1; for (i=0; i<3; i++) { if (buffer[i] != 0) { flag = 1; } if (flag) { buffer[i] |= 0x80; } if (length == -1 && buffer[i] >= 0x80) { length = 4-i; } } if (length == -1) { length = 1; } if (length < 4) { for (i=0; imakeNoteOn(aChannel, key, vel); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addNoteOff -- Add a note-off message (using 0x80 messages). // MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key, int vel) { MidiEvent* me = new MidiEvent; me->makeNoteOff(aChannel, key, vel); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addNoteOff -- Add a note-off message (using 0x90 messages with // zero attack velocity). // MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key) { MidiEvent* me = new MidiEvent; me->makeNoteOff(aChannel, key); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addController -- Add a controller message in the given // track at the given tick time in the given channel. // MidiEvent* MidiFile::addController(int aTrack, int aTick, int aChannel, int num, int value) { MidiEvent* me = new MidiEvent; me->makeController(aChannel, num, value); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addPatchChange -- Add a patch-change message in the given // track at the given tick time in the given channel. // MidiEvent* MidiFile::addPatchChange(int aTrack, int aTick, int aChannel, int patchnum) { MidiEvent* me = new MidiEvent; me->makePatchChange(aChannel, patchnum); me->tick = aTick; m_events[aTrack]->push_back_no_copy(me); return me; } ////////////////////////////// // // MidiFile::addTimbre -- Add a patch-change message in the given // track at the given tick time in the given channel. Alias for // MidiFile::addPatchChange(). // MidiEvent* MidiFile::addTimbre(int aTrack, int aTick, int aChannel, int patchnum) { return addPatchChange(aTrack, aTick, aChannel, patchnum); } ////////////////////////////// // // MidiFile::addPitchBend -- convert number in the range from -1 to +1 // into two 7-bit numbers (smallest piece first) // // -1.0 maps to 0 (0x0000) // 0.0 maps to 8192 (0x2000 --> 0x40 0x00) // +1.0 maps to 16383 (0x3FFF --> 0x7F 0x7F) // MidiEvent* MidiFile::addPitchBend(int aTrack, int aTick, int aChannel, double amount) { m_timemapvalid = 0; amount += 1.0; int value = int(amount * 8192 + 0.5); // prevent any wrap-around in case of round-off errors if (value > 0x3fff) { value = 0x3fff; } if (value < 0) { value = 0; } int lsbint = 0x7f & value; int msbint = 0x7f & (value >> 7); std::vector mididata; mididata.resize(3); if (aChannel < 0) { aChannel = 0; } else if (aChannel > 15) { aChannel = 15; } mididata[0] = uchar(0xe0 | aChannel); mididata[1] = uchar(lsbint); mididata[2] = uchar(msbint); return addEvent(aTrack, aTick, mididata); } /////////////////////////////////////////////////////////////////////////// // // Controller message adding convenience functions: // ////////////////////////////// // // MidiFile::addSustain -- Add a continuous controller message for the sustain pedal. // MidiEvent* MidiFile::addSustain(int aTrack, int aTick, int aChannel, int value) { return addController(aTrack, aTick, aChannel, 64, value); } // // MidiFile::addSustainPedal -- Alias for MidiFile::addSustain(). // MidiEvent* MidiFile::addSustainPedal(int aTrack, int aTick, int aChannel, int value) { return addSustain(aTrack, aTick, aChannel, value); } ////////////////////////////// // // MidiFile::addSustainOn -- Add a continuous controller message for the sustain pedal on. // MidiEvent* MidiFile::addSustainOn(int aTrack, int aTick, int aChannel) { return addSustain(aTrack, aTick, aChannel, 127); } // // MidiFile::addSustainPedalOn -- Alias for MidiFile::addSustainOn(). // MidiEvent* MidiFile::addSustainPedalOn(int aTrack, int aTick, int aChannel) { return addSustainOn(aTrack, aTick, aChannel); } ////////////////////////////// // // MidiFile::addSustainOff -- Add a continuous controller message for the sustain pedal off. // MidiEvent* MidiFile::addSustainOff(int aTrack, int aTick, int aChannel) { return addSustain(aTrack, aTick, aChannel, 0); } // // MidiFile::addSustainPedalOff -- Alias for MidiFile::addSustainOff(). // MidiEvent* MidiFile::addSustainPedalOff(int aTrack, int aTick, int aChannel) { return addSustainOff(aTrack, aTick, aChannel); } ////////////////////////////// // // MidiFile::addTrack -- adds a blank track at end of the // track list. Returns the track number of the added // track. // int MidiFile::addTrack(void) { int length = getNumTracks(); m_events.resize(length+1); m_events[length] = new MidiEventList; m_events[length]->reserve(10000); m_events[length]->clear(); return length; } int MidiFile::addTrack(int count) { int length = getNumTracks(); m_events.resize(length+count); int i; for (i=0; ireserve(10000); m_events[length + i]->clear(); } return length + count - 1; } // // MidiFile::addTracks -- Alias for MidiFile::addTrack(). // int MidiFile::addTracks(int count) { return addTrack(count); } ////////////////////////////// // // MidiFile::allocateEvents -- // void MidiFile::allocateEvents(int track, int aSize) { int oldsize = m_events[track]->size(); if (oldsize < aSize) { m_events[track]->reserve(aSize); } } ////////////////////////////// // // MidiFile::deleteTrack -- remove a track from the MidiFile. // Tracks are numbered starting at track 0. // void MidiFile::deleteTrack(int aTrack) { int length = getNumTracks(); if (aTrack < 0 || aTrack >= length) { return; } if (length == 1) { return; } delete m_events[aTrack]; for (int i=aTrack; isize(); } int MidiFile::getNumEvents(int aTrack) const { return m_events[aTrack]->size(); } ////////////////////////////// // // MidiFile::mergeTracks -- combine the data from two // tracks into one. Placing the data in the first // track location listed, and Moving the other tracks // in the file around to fill in the spot where Track2 // used to be. The results of this function call cannot // be reversed. // void MidiFile::mergeTracks(int aTrack1, int aTrack2) { MidiEventList* mergedTrack; mergedTrack = new MidiEventList; int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_DELTA) { makeAbsoluteTicks(); } int i, j; int length = getNumTracks(); for (i=0; i<(int)m_events[aTrack1]->size(); i++) { mergedTrack->push_back((*m_events[aTrack1])[i]); } for (j=0; j<(int)m_events[aTrack2]->size(); j++) { (*m_events[aTrack2])[j].track = aTrack1; mergedTrack->push_back((*m_events[aTrack2])[j]); } mergedTrack->sort(); delete m_events[aTrack1]; m_events[aTrack1] = mergedTrack; for (i=aTrack2; i= 0) && (track < getTrackCount())) { m_events.at(track)->sort(); } else { std::cerr << "Warning: track " << track << " does not exist." << std::endl; } } ////////////////////////////// // // MidiFile::sortTracks -- sort all tracks in the MidiFile. // void MidiFile::sortTracks(void) { if (m_theTimeState == TIME_STATE_ABSOLUTE) { for (int i=0; isort(); } } else { std::cerr << "Warning: Sorting only allowed in absolute tick mode."; } } ////////////////////////////// // // MidiFile::getTrackCountAsType1 -- Return the number of tracks in the // MIDI file. Returns the size of the events if not in joined state. // If in joined state, reads track 0 to find the maximum track // value from the original unjoined tracks. // int MidiFile::getTrackCountAsType1(void) { if (getTrackState() == TRACK_STATE_JOINED) { int output = 0; int i; for (i=0; i<(int)m_events[0]->size(); i++) { if (getEvent(0,i).track > output) { output = getEvent(0,i).track; } } return output+1; // I think the track values are 0 offset... } else { return (int)m_events.size(); } } ////////////////////////////// // // MidiFile::clearLinks -- // void MidiFile::clearLinks(void) { for (int i=0; iclearLinks(); } m_linkedEventsQ = false; } /////////////////////////////////////////////////////////////////////////// // // private functions // ////////////////////////////// // // MidiFile::linearTickInterpolationAtSecond -- return the tick value at the // given input time. // double MidiFile::linearTickInterpolationAtSecond(double seconds) { if (m_timemapvalid == 0) { buildTimeMap(); if (m_timemapvalid == 0) { return -1.0; // something went wrong } } int i; double lasttime = m_timemap[m_timemap.size()-1].seconds; // give an error value of -1 if time is out of range of data. if (seconds < 0.0) { return -1.0; } if (seconds > m_timemap[m_timemap.size()-1].seconds) { return -1.0; } // Guess which side of the list is closest to target: // Could do a more efficient algorithm since time values are sorted, // but good enough for now... int startindex = -1; if (seconds < lasttime / 2) { for (i=0; i<(int)m_timemap.size(); i++) { if (m_timemap[i].seconds > seconds) { startindex = i-1; break; } else if (m_timemap[i].seconds == seconds) { startindex = i; break; } } } else { for (i=(int)m_timemap.size()-1; i>0; i--) { if (m_timemap[i].seconds < seconds) { startindex = i+1; break; } else if (m_timemap[i].seconds == seconds) { startindex = i; break; } } } if (startindex < 0) { return -1.0; } if (startindex >= (int)m_timemap.size()-1) { return -1.0; } double x1 = m_timemap[startindex].seconds; double x2 = m_timemap[startindex+1].seconds; double y1 = m_timemap[startindex].tick; double y2 = m_timemap[startindex+1].tick; double xi = seconds; return (xi-x1) * ((y2-y1)/(x2-x1)) + y1; } ////////////////////////////// // // MidiFile::linearSecondInterpolationAtTick -- return the time in seconds // value at the given input tick time. (Ticks input could be made double). // double MidiFile::linearSecondInterpolationAtTick(int ticktime) { if (m_timemapvalid == 0) { buildTimeMap(); if (m_timemapvalid == 0) { return -1.0; // something went wrong } } int i; double lasttick = m_timemap[m_timemap.size()-1].tick; // give an error value of -1 if time is out of range of data. if (ticktime < 0.0) { return -1; } if (ticktime > m_timemap.back().tick) { return -1; // don't try to extrapolate } // Guess which side of the list is closest to target: // Could do a more efficient algorithm since time values are sorted, // but good enough for now... int startindex = -1; if (ticktime < lasttick / 2) { for (i=0; i<(int)m_timemap.size(); i++) { if (m_timemap[i].tick > ticktime) { startindex = i-1; break; } else if (m_timemap[i].tick == ticktime) { startindex = i; break; } } } else { for (i=(int)m_timemap.size()-1; i>0; i--) { if (m_timemap[i].tick < ticktime) { startindex = i; break; } else if (m_timemap[i].tick == ticktime) { startindex = i; break; } } } if (startindex < 0) { return -1; } if (startindex >= (int)m_timemap.size()-1) { return -1; } if (m_timemap[startindex].tick == ticktime) { return m_timemap[startindex].seconds; } double x1 = m_timemap[startindex].tick; double x2 = m_timemap[startindex+1].tick; double y1 = m_timemap[startindex].seconds; double y2 = m_timemap[startindex+1].seconds; double xi = ticktime; return (xi-x1) * ((y2-y1)/(x2-x1)) + y1; } ////////////////////////////// // // MidiFile::buildTimeMap -- build an index of the absolute tick values // found in a MIDI file, and their corresponding time values in // seconds, taking into consideration tempo change messages. If no // tempo messages are given (or untill they are given, then the // tempo is set to 120 beats per minute). If SMPTE time code is // used, then ticks are actually time values. So don't build // a time map for SMPTE ticks, and just calculate the time in // seconds from the tick value (1000 ticks per second SMPTE // is the only mode tested (25 frames per second and 40 subframes // per frame). // void MidiFile::buildTimeMap(void) { // convert the MIDI file to absolute time representation // in single track mode (and undo if the MIDI file was not // in that state when this function was called. // int trackstate = getTrackState(); int timestate = getTickState(); makeAbsoluteTicks(); joinTracks(); int allocsize = getNumEvents(0); m_timemap.reserve(allocsize+10); m_timemap.clear(); _TickTime value; int lasttick = 0; int tickinit = 0; int i; int tpq = getTicksPerQuarterNote(); double defaultTempo = 120.0; double secondsPerTick = 60.0 / (defaultTempo * tpq); double lastsec = 0.0; double cursec = 0.0; for (i=0; i lasttick) || !tickinit) { tickinit = 1; // calculate the current time in seconds: cursec = lastsec + (curtick - lasttick) * secondsPerTick; getEvent(0, i).seconds = cursec; // store the new tick to second mapping value.tick = curtick; value.seconds = cursec; m_timemap.push_back(value); lasttick = curtick; lastsec = cursec; } // update the tempo if needed: if (getEvent(0,i).isTempo()) { secondsPerTick = getEvent(0,i).getTempoSPT(getTicksPerQuarterNote()); } } // reset the states of the tracks or time values if necessary here: if (timestate == TIME_STATE_DELTA) { deltaTicks(); } if (trackstate == TRACK_STATE_SPLIT) { splitTracks(); } m_timemapvalid = 1; } ////////////////////////////// // // MidiFile::extractMidiData -- Extract MIDI data from input // stream. Return value is 0 if failure; otherwise, returns 1. // int MidiFile::extractMidiData(std::istream& input, std::vector& array, uchar& runningCommand) { int character; uchar byte; array.clear(); int runningQ; character = input.get(); if (character == EOF) { std::cerr << "Error: unexpected end of file." << std::endl; return 0; } else { byte = (uchar)character; } if (byte < 0x80) { runningQ = 1; if (runningCommand == 0) { std::cerr << "Error: running command with no previous command" << std::endl; return 0; } if (runningCommand >= 0xf0) { std::cerr << "Error: running status not permitted with meta and sysex" << " event." << std::endl; std::cerr << "Byte is 0x" << std::hex << (int)byte << std::dec << std::endl; return 0; } } else { runningCommand = byte; runningQ = 0; } array.push_back(runningCommand); if (runningQ) { array.push_back(byte); } switch (runningCommand & 0xf0) { case 0x80: // note off (2 more bytes) case 0x90: // note on (2 more bytes) case 0xA0: // aftertouch (2 more bytes) case 0xB0: // cont. controller (2 more bytes) case 0xE0: // pitch wheel (2 more bytes) byte = readByte(input); if (!status()) { return m_rwstatus; } if (byte > 0x7f) { std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; m_rwstatus = false; return m_rwstatus; } array.push_back(byte); if (!runningQ) { byte = readByte(input); if (!status()) { return m_rwstatus; } if (byte > 0x7f) { std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; m_rwstatus = false; return m_rwstatus; } array.push_back(byte); } break; case 0xC0: // patch change (1 more byte) case 0xD0: // channel pressure (1 more byte) if (!runningQ) { byte = readByte(input); if (!status()) { return m_rwstatus; } if (byte > 0x7f) { std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; m_rwstatus = false; return m_rwstatus; } array.push_back(byte); } break; case 0xF0: switch (runningCommand) { case 0xff: // meta event { if (!runningQ) { byte = readByte(input); // meta type if (!status()) { return m_rwstatus; } array.push_back(byte); } ulong length = 0; uchar byte1 = 0; uchar byte2 = 0; uchar byte3 = 0; uchar byte4 = 0; byte1 = readByte(input); if (!status()) { return m_rwstatus; } array.push_back(byte1); if (byte1 >= 0x80) { byte2 = readByte(input); if (!status()) { return m_rwstatus; } array.push_back(byte2); if (byte2 > 0x80) { byte3 = readByte(input); if (!status()) { return m_rwstatus; } array.push_back(byte3); if (byte3 >= 0x80) { byte4 = readByte(input); if (!status()) { return m_rwstatus; } array.push_back(byte4); if (byte4 >= 0x80) { std::cerr << "Error: cannot handle large VLVs" << std::endl; m_rwstatus = false; return m_rwstatus; } else { length = unpackVLV(byte1, byte2, byte3, byte4); if (!m_rwstatus) { return m_rwstatus; } } } else { length = unpackVLV(byte1, byte2, byte3); if (!m_rwstatus) { return m_rwstatus; } } } else { length = unpackVLV(byte1, byte2); if (!m_rwstatus) { return m_rwstatus; } } } else { length = byte1; } for (int j=0; j<(int)length; j++) { byte = readByte(input); // meta type if (!status()) { return m_rwstatus; } array.push_back(byte); } } break; // The 0xf0 and 0xf7 meta commands deal with system-exclusive // messages. 0xf0 is used to either start a message or to store // a complete message. The 0xf0 is part of the outgoing MIDI // bytes. The 0xf7 message is used to send arbitrary bytes, // typically the middle or ends of system exclusive messages. The // 0xf7 byte at the start of the message is not part of the // outgoing raw MIDI bytes, but is kept in the MidiFile message // to indicate a raw MIDI byte message (typically a partial // system exclusive message). case 0xf7: // Raw bytes. 0xf7 is not part of the raw // bytes, but are included to indicate // that this is a raw byte message. case 0xf0: // System Exclusive message { // (complete, or start of message). int length = (int)readVLValue(input); for (int i=0; i 0x7f)) { count++; } count++; if (count >= 6) { std::cerr << "VLV number is too large" << std::endl; m_rwstatus = false; return 0; } ulong output = 0; for (int i=0; i& outdata) { uchar bytes[4] = {0}; if ((unsigned long)aValue >= (1 << 28)) { std::cerr << "Error: number too large to convert to VLV" << std::endl; aValue = 0x0FFFffff; } bytes[0] = (uchar)(((ulong)aValue >> 21) & 0x7f); // most significant 7 bits bytes[1] = (uchar)(((ulong)aValue >> 14) & 0x7f); bytes[2] = (uchar)(((ulong)aValue >> 7) & 0x7f); bytes[3] = (uchar)(((ulong)aValue) & 0x7f); // least significant 7 bits int start = 0; while ((start<4) && (bytes[start] == 0)) start++; for (int i=start; i<3; i++) { bytes[i] = bytes[i] | 0x80; outdata.push_back(bytes[i]); } outdata.push_back(bytes[3]); } ////////////////////////////// // // MidiFile::clear_no_deallocate -- Similar to clear() but does not // delete the Events in the lists. This is primarily used internally // to the MidiFile class, so don't use unless you really know what you // are doing (otherwise you will end up with memory leaks or // segmentation faults). // void MidiFile::clear_no_deallocate(void) { for (int i=0; idetach(); delete m_events[i]; m_events[i] = NULL; } m_events.resize(1); m_events[0] = new MidiEventList; m_timemapvalid=0; m_timemap.clear(); // m_events.resize(0); // causes a memory leak [20150205 Jorden Thatcher] } ////////////////////////////// // // MidiFile::ticksearch -- for finding a tick entry in the time map. // int MidiFile::ticksearch(const void* A, const void* B) { _TickTime& a = *((_TickTime*)A); _TickTime& b = *((_TickTime*)B); if (a.tick < b.tick) { return -1; } else if (a.tick > b.tick) { return 1; } return 0; } ////////////////////////////// // // MidiFile::secondsearch -- for finding a second entry in the time map. // int MidiFile::secondsearch(const void* A, const void* B) { _TickTime& a = *((_TickTime*)A); _TickTime& b = *((_TickTime*)B); if (a.seconds < b.seconds) { return -1; } else if (a.seconds > b.seconds) { return 1; } return 0; } /////////////////////////////////////////////////////////////////////////// // // Static functions: // ////////////////////////////// // // MidiFile::readLittleEndian4Bytes -- Read four bytes which are in // little-endian order (smallest byte is first). Then flip // the order of the bytes to create the return value. // ulong MidiFile::readLittleEndian4Bytes(std::istream& input) { uchar buffer[4] = {0}; input.read((char*)buffer, 4); if (input.eof()) { std::cerr << "Error: unexpected end of file." << std::endl; return 0; } return buffer[3] | (buffer[2] << 8) | (buffer[1] << 16) | (buffer[0] << 24); } ////////////////////////////// // // MidiFile::readLittleEndian2Bytes -- Read two bytes which are in // little-endian order (smallest byte is first). Then flip // the order of the bytes to create the return value. // ushort MidiFile::readLittleEndian2Bytes(std::istream& input) { uchar buffer[2] = {0}; input.read((char*)buffer, 2); if (input.eof()) { std::cerr << "Error: unexpected end of file." << std::endl; return 0; } return buffer[1] | (buffer[0] << 8); } ////////////////////////////// // // MidiFile::readByte -- Read one byte from input stream. Set // fail status error if there was a problem (calling function // has to check this status for an error after reading). // uchar MidiFile::readByte(std::istream& input) { uchar buffer[1] = {0}; input.read((char*)buffer, 1); if (input.eof()) { std::cerr << "Error: unexpected end of file." << std::endl; m_rwstatus = false; return 0; } return buffer[0]; } ////////////////////////////// // // MidiFile::writeLittleEndianUShort -- // std::ostream& MidiFile::writeLittleEndianUShort(std::ostream& out, ushort value) { union { char bytes[2]; ushort us; } data; data.us = value; out << data.bytes[0]; out << data.bytes[1]; return out; } ////////////////////////////// // // MidiFile::writeBigEndianUShort -- // std::ostream& MidiFile::writeBigEndianUShort(std::ostream& out, ushort value) { union { char bytes[2]; ushort us; } data; data.us = value; out << data.bytes[1]; out << data.bytes[0]; return out; } ////////////////////////////// // // MidiFile::writeLittleEndianShort -- // std::ostream& MidiFile::writeLittleEndianShort(std::ostream& out, short value) { union { char bytes[2]; short s; } data; data.s = value; out << data.bytes[0]; out << data.bytes[1]; return out; } ////////////////////////////// // // MidiFile::writeBigEndianShort -- // std::ostream& MidiFile::writeBigEndianShort(std::ostream& out, short value) { union { char bytes[2]; short s; } data; data.s = value; out << data.bytes[1]; out << data.bytes[0]; return out; } ////////////////////////////// // // MidiFile::writeLittleEndianULong -- // std::ostream& MidiFile::writeLittleEndianULong(std::ostream& out, ulong value) { union { char bytes[4]; ulong ul; } data; data.ul = value; out << data.bytes[0]; out << data.bytes[1]; out << data.bytes[2]; out << data.bytes[3]; return out; } ////////////////////////////// // // MidiFile::writeBigEndianULong -- // std::ostream& MidiFile::writeBigEndianULong(std::ostream& out, ulong value) { union { char bytes[4]; long ul; } data; data.ul = value; out << data.bytes[3]; out << data.bytes[2]; out << data.bytes[1]; out << data.bytes[0]; return out; } ////////////////////////////// // // MidiFile::writeLittleEndianLong -- // std::ostream& MidiFile::writeLittleEndianLong(std::ostream& out, long value) { union { char bytes[4]; long l; } data; data.l = value; out << data.bytes[0]; out << data.bytes[1]; out << data.bytes[2]; out << data.bytes[3]; return out; } ////////////////////////////// // // MidiFile::writeBigEndianLong -- // std::ostream& MidiFile::writeBigEndianLong(std::ostream& out, long value) { union { char bytes[4]; long l; } data; data.l = value; out << data.bytes[3]; out << data.bytes[2]; out << data.bytes[1]; out << data.bytes[0]; return out; } ////////////////////////////// // // MidiFile::writeBigEndianFloat -- // std::ostream& MidiFile::writeBigEndianFloat(std::ostream& out, float value) { union { char bytes[4]; float f; } data; data.f = value; out << data.bytes[3]; out << data.bytes[2]; out << data.bytes[1]; out << data.bytes[0]; return out; } ////////////////////////////// // // MidiFile::writeLittleEndianFloat -- // std::ostream& MidiFile::writeLittleEndianFloat(std::ostream& out, float value) { union { char bytes[4]; float f; } data; data.f = value; out << data.bytes[0]; out << data.bytes[1]; out << data.bytes[2]; out << data.bytes[3]; return out; } ////////////////////////////// // // MidiFile::writeBigEndianDouble -- // std::ostream& MidiFile::writeBigEndianDouble(std::ostream& out, double value) { union { char bytes[8]; double d; } data; data.d = value; out << data.bytes[7]; out << data.bytes[6]; out << data.bytes[5]; out << data.bytes[4]; out << data.bytes[3]; out << data.bytes[2]; out << data.bytes[1]; out << data.bytes[0]; return out; } ////////////////////////////// // // MidiFile::writeLittleEndianDouble -- // std::ostream& MidiFile::writeLittleEndianDouble(std::ostream& out, double value) { union { char bytes[8]; double d; } data; data.d = value; out << data.bytes[0]; out << data.bytes[1]; out << data.bytes[2]; out << data.bytes[3]; out << data.bytes[4]; out << data.bytes[5]; out << data.bytes[6]; out << data.bytes[7]; return out; } } // end namespace smf /////////////////////////////////////////////////////////////////////////// // // external functions // ////////////////////////////// // // operator<< -- for printing an ASCII version of the MIDI file // std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile) { aMidiFile.writeBinascWithComments(out); return out; }