Midifile View on GitHub

MidiFile class

home   »   documentation   »   classes

The MidiFile class is an interface for reading and writing Standard MIDI files. MIDI file tracks are stored as a list of MidiEventList objects, which in turn are lists of MidiEvents The MidiFile class can be considered to be a two-dimensional array of events that are accessible with the [] operator. The first dimension is the track index, and the second dimension is the event index for the given track. Thus, midifile[2][15] would return the 16th event in the third track.

MidiEvents consist of a list of MIDI message bytes along with timing and other variables. For example, the construct midifile[2][15][0] would return the MIDI command byte for the 16th message in the third track, and midifile[2][15].tick would return the timestamp for the event (either in delta or absolute tick values, see the tick-related functions described further below).

Reading/writing functions

The two main member functions for reading and writing Standard MIDI Files are read and write. The argument to these functions can be either a filename or an input/output stream object. The status function can be called after reading or writing to determine if the action was successful.

read — Read a Standard MIDI File in binary or ASCII formats.
int read(const char* filename);
int read(const string& filename);
int read(istream& input);
Parse a Standard MIDI File from either a file or from an already opened input stream, and store its contents in the MidiFile object. The function can also handle ASCII text input that describes a Stadard MIDI file in the binasc format (either as hex byte codes, or in more structured content).
Examples:Expand all
  1. Read a MidiFile from a file with the read() function.
    		
    #include "MidiFile.h"
    
    int main(void) {
       MidiFile midifile;
       MidiFile midifile.read("test.mid");
       return 0;
    }
    	
  2. Use the MidiFile constructor to read from a file.
    		
    #include "MidiFile.h"
    
    int main(void) {
       MidiFile midifile("test.mid");
       return 0;
    }
    	
Source code for read on GitHub
//////////////////////////////
//
// MidiFile::read -- Parse a Standard MIDI File and store its contents
//      in the object.
//

int MidiFile::read(const char* filename) {
   rwstatus = 1;
   timemapvalid = 0;
   if (filename != NULL) {
      setFilename(filename);
   }

   fstream input;
   input.open(filename, ios::binary | ios::in);

   if (!input.is_open()) {
      return 0;
   }

   rwstatus = MidiFile::read(input);
   return rwstatus;
}


//
// string version of read().
//


int MidiFile::read(const string& filename) {
   timemapvalid = 0;
   setFilename(filename);
   rwstatus = 1;

   fstream input;
   input.open(filename.data(), ios::binary | ios::in);

   if (!input.is_open()) {
      return 0;
   }

   rwstatus = MidiFile::read(input);
   return rwstatus;
}


//
// istream version of read().
//

int MidiFile::read(istream& input) {
   rwstatus = 1;
   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.
      stringstream binarydata;
      Binasc binasc;
      binasc.writeToBinary(binarydata, input);
      binarydata.seekg(0, ios_base::beg);
      if (binarydata.peek() != 'M') {
         cerr << "Bad MIDI data input" << endl;
	 rwstatus = 0;
         return rwstatus;
      } else {
         rwstatus = read(binarydata);
         return rwstatus;
      }
   }

   const char* 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) {
      cerr << "In file " << filename << ": unexpected end of file." << endl;
      cerr << "Expecting 'M' at first byte, but found nothing." << endl;
      rwstatus = 0; return rwstatus;
   } else if (character != 'M') {
      cerr << "File " << filename << " is not a MIDI file" << endl;
      cerr << "Expecting 'M' at first byte but got '"
           << character << "'" << endl;
      rwstatus = 0; return rwstatus;
   }

   character = input.get();
   if (character == EOF) {
      cerr << "In file " << filename << ": unexpected end of file." << endl;
      cerr << "Expecting 'T' at first byte, but found nothing." << endl;
      rwstatus = 0; return rwstatus;
   } else if (character != 'T') {
      cerr << "File " << filename << " is not a MIDI file" << endl;
      cerr << "Expecting 'T' at first byte but got '"
           << character << "'" << endl;
      rwstatus = 0; return rwstatus;
   }

   character = input.get();
   if (character == EOF) {
      cerr << "In file " << filename << ": unexpected end of file." << endl;
      cerr << "Expecting 'h' at first byte, but found nothing." << endl;
      rwstatus = 0; return rwstatus;
   } else if (character != 'h') {
      cerr << "File " << filename << " is not a MIDI file" << endl;
      cerr << "Expecting 'h' at first byte but got '"
           << character << "'" << endl;
      rwstatus = 0; return rwstatus;
   }

   character = input.get();
   if (character == EOF) {
      cerr << "In file " << filename << ": unexpected end of file." << endl;
      cerr << "Expecting 'd' at first byte, but found nothing." << endl;
      rwstatus = 0; return rwstatus;
   } else if (character != 'd') {
      cerr << "File " << filename << " is not a MIDI file" << endl;
      cerr << "Expecting 'd' at first byte but got '"
           << character << "'" << endl;
      rwstatus = 0; return rwstatus;
   }

   // read header size (allow larger header size?)
   longdata = MidiFile::readLittleEndian4Bytes(input);
   if (longdata != 6) {
      cerr << "File " << filename
           << " is not a MIDI 1.0 Standard MIDI file." << endl;
      cerr << "The header size is " << longdata << " bytes." << endl;
      rwstatus = 0; return rwstatus;
   }

   // Header parameter #1: format type
   int type;
   shortdata = MidiFile::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.
      default:
         cerr << "Error: cannot handle a type-" << shortdata
              << " MIDI file" << endl;
         rwstatus = 0; return rwstatus;
   }

   // Header parameter #2: track count
   int tracks;
   shortdata = MidiFile::readLittleEndian2Bytes(input);
   if (type == 0 && shortdata != 1) {
      cerr << "Error: Type 0 MIDI file can only contain one track" << endl;
      cerr << "Instead track count is: " << shortdata << endl;
      rwstatus = 0; return rwstatus;
   } else {
      tracks = shortdata;
   }
   clear();
   if (events[0] != NULL) {
      delete events[0];
   }
   events.resize(tracks);
   for (int z=0; z<tracks; z++) {
      events[z] = new MidiEventList;
      events[z]->reserve(10000);   // Initialize with 10,000 event storage.
      events[z]->clear();
   }

   // Header parameter #3: Ticks per quarter note
   shortdata = MidiFile::readLittleEndian2Bytes(input);
   if (shortdata >= 0x8000) {
      int framespersecond = ((!(shortdata >> 8))+1) & 0x00ff;
      int resolution      = shortdata & 0x00ff;
      switch (framespersecond) {
         case 232:  framespersecond = 24; break;
         case 231:  framespersecond = 25; break;
         case 227:  framespersecond = 29; break;
         case 226:  framespersecond = 30; break;
         default:
               cerr << "Warning: unknown FPS: " << framespersecond << endl;
               framespersecond = 255 - framespersecond + 1;
               cerr << "Setting FPS to " << framespersecond << endl;
      }
      // actually ticks per second (except for frame=29 (drop frame)):
      ticksPerQuarterNote = shortdata;

      cerr << "SMPTE ticks: " << ticksPerQuarterNote << " ticks/sec" << endl;
      cerr << "SMPTE frames per second: " << framespersecond << endl;
      cerr << "SMPTE frame resolution per frame: " << resolution << endl;
   }  else {
      ticksPerQuarterNote = shortdata;
   }


   //////////////////////////////////////////////////
   //
   // now read individual tracks:
   //

   uchar runningCommand;
   MidiEvent event;
   vector<uchar> bytes;
   int absticks;
   int xstatus;
   // int barline;

   for (int i=0; i<tracks; i++) {
      runningCommand = 0;

      // cout << "\nReading Track: " << i + 1 << flush;

      // read track header...

      character = input.get();
      if (character == EOF) {
         cerr << "In file " << filename << ": unexpected end of file." << endl;
         cerr << "Expecting 'M' at first byte in track, but found nothing."
              << endl;
         rwstatus = 0; return rwstatus;
      } else if (character != 'M') {
         cerr << "File " << filename << " is not a MIDI file" << endl;
         cerr << "Expecting 'M' at first byte in track but got '"
              << character << "'" << endl;
         rwstatus = 0; return rwstatus;
      }

      character = input.get();
      if (character == EOF) {
         cerr << "In file " << filename << ": unexpected end of file." << endl;
         cerr << "Expecting 'T' at first byte in track, but found nothing."
              << endl;
         rwstatus = 0; return rwstatus;
      } else if (character != 'T') {
         cerr << "File " << filename << " is not a MIDI file" << endl;
         cerr << "Expecting 'T' at first byte in track but got '"
              << character << "'" << endl;
         rwstatus = 0; return rwstatus;
      }

      character = input.get();
      if (character == EOF) {
         cerr << "In file " << filename << ": unexpected end of file." << endl;
         cerr << "Expecting 'r' at first byte in track, but found nothing."
              << endl;
         rwstatus = 0; return rwstatus;
      } else if (character != 'r') {
         cerr << "File " << filename << " is not a MIDI file" << endl;
         cerr << "Expecting 'r' at first byte in track but got '"
              << character << "'" << endl;
         rwstatus = 0; return rwstatus;
      }

      character = input.get();
      if (character == EOF) {
         cerr << "In file " << filename << ": unexpected end of file." << endl;
         cerr << "Expecting 'k' at first byte in track, but found nothing."
              << endl;
         rwstatus = 0; return rwstatus;
      } else if (character != 'k') {
         cerr << "File " << filename << " is not a MIDI file" << endl;
         cerr << "Expecting 'k' at first byte in track but got '"
              << character << "'" << endl;
         rwstatus = 0; return rwstatus;
      }

      // Now read track chunk size and throw it away because it is
      // not really necessary since the track MUST end with an
      // end of track meta event, and many MIDI files found in the wild
      // do not correctly give the track size.
      longdata = MidiFile::readLittleEndian4Bytes(input);

      // set the size of the track allocation so that it might
      // approximately fit the data.
      events[i]->reserve((int)longdata/2);
      events[i]->clear();

      // process the track
      absticks = 0;
      // barline = 1;
      while (!input.eof()) {
         longdata = readVLValue(input);
         //cout << "ticks = " << longdata << endl;
         absticks += longdata;
         xstatus = extractMidiData(input, bytes, runningCommand);
         if (xstatus == 0) {
            rwstatus = 0;  return rwstatus;
         }
         event.setMessage(bytes);
         //cout << "command = " << hex << (int)event.data[0] << dec << endl;
         if (bytes[0] == 0xff && (bytes[1] == 1 ||
             bytes[1] == 2 || bytes[1] == 3 || bytes[1] == 4)) {
           // mididata.push_back('\0');
           // cout << '\t';
           // for (int m=0; m<event.data[2]; m++) {
           //    cout << event.data[m+3];
           // }
           // cout.flush();
         } else if (bytes[0] == 0xff && bytes[1] == 0x2f) {
            // end of track message
            // uncomment out the following three lines if you don't want
            // to see the end of track message (which is always required,
            // and added automatically when a MIDI is written.
            event.tick = absticks;
            event.track = i;
            events[i]->push_back(event);

            break;
         }

         if (bytes[0] != 0xff && bytes[0] != 0xf0) {
            event.tick = absticks;
            event.track = i;
            events[i]->push_back(event);
         } else {
            event.tick = absticks;
            event.track = i;
            events[i]->push_back(event);
         }

      }

   }

   theTimeState = TIME_STATE_ABSOLUTE;
   return 1;
}
write — Write a Standard MIDI file from the MidiFile contents.
int write(const char* filename);
int write(const string& filename);
Write a Standard MIDI file from the MidiFile contents.
Example: Read MIDI file and then save a copy.
		
#include "MidiFile.h"

int main(void) {
   MidiFile midifile;
   midifile.read("input.mid");
   midifile.write("output.mid");
   return 0;
}
	
Source code for write on GitHub
//////////////////////////////
//
// MidiFile::write -- write a standard MIDI file to a file or an output
//    stream.
//

int MidiFile::write(const char* filename) {
   fstream output(filename, ios::binary | ios::out);

   if (!output.is_open()) {
      cerr << "Error: could not write: " << filename << endl;
      return 0;
   }
   rwstatus = write(output);
   output.close();
   return rwstatus;
}


int MidiFile::write(const string& filename) {
   return MidiFile::write(filename.data());
}


int MidiFile::write(ostream& out) {
   int oldTimeState = getTickState();
   if (oldTimeState == TIME_STATE_ABSOLUTE) {
      deltaTicks();
   }

   // 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.
   vector<uchar> trackdata;
   uchar endoftrack[4] = {0, 0xff, 0x2f, 0x00};
   int i, j, k;
   int size;
   for (i=0; i<getNumTracks(); i++) {
      trackdata.reserve(123456);   // make the track data larger than
                                   // expected data input
      trackdata.clear();
      for (j=0; j<(int)events[i]->size(); j++) {
         if ((*events[i])[j].isEndOfTrack()) {
            // suppress end-of-track meta messages (one will be added
            // automatically after all track data has been written).
            continue;
         }
         writeVLValue((*events[i])[j].tick, trackdata);
         if (((*events[i])[j].getCommandByte() == 0xf0) ||
             ((*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((*events[i])[j][0]); // 0xf0 or 0xf7;
            writeVLValue((*events[i])[j].size()-1, trackdata);
            for (k=1; k<(int)(*events[i])[j].size(); k++) {
               trackdata.push_back((*events[i])[j][k]);
            }
         } else {
            // non-sysex type of message, so just output the
            // bytes of the message:
            for (k=0; k<(int)(*events[i])[j].size(); k++) {
               trackdata.push_back((*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 = trackdata.size();
      writeBigEndianULong(out, longdata);

      // B. write the actual data
      out.write((char*)trackdata.data(), trackdata.size());
   }

   if (oldTimeState == TIME_STATE_ABSOLUTE) {
      absoluteTicks();
   }

   return 1;
}
status — Returns true if last read/write was successful.
int status(void);
Returns true if the list read or write was successful.
Source code for status on GitHub
//////////////////////////////
//
// MidiFile::status -- return the success flag from the last read or
//    write (writeHex, writeBinasc).
//

int MidiFile::status(void) {
   return rwstatus;
}


Two additional functions, called writeHex and writeBinasc can be used to print ASCII hex codes that determinstically represent a binary Standard MIDI File. The read function will transparently parse either binary MIDI files, or their ASCII representations in one of these two formats.

writeHex — Print MidiFile as a list of ASCII hex bytes.
ostream& writeHex(const char* filename);
ostream& writeHex(const string& filename);
ostream& writeHex(int);
Print the MIDI file as a sequence of ASCII hex bytes, formatted 25 bytes to a line and two digits each.
Example: Write a MIDI file with a single note as Hex bytes.
		
#include "MidiFile.h"
#include <iostream>
int main(void) {
   MidiFile midifile;
   MidiEvent event;
   event.tick = 0;
   event.track = 0;
   event.makeNoteOn(60, 64);
   midifile.addEvent(event);
   event.makeNoteOff();
   event.tick = midifile.getTPQ() * 2;  // half-note
   midifile.addEvent(event);
   midifile.writeHex(std::cout);
   return 0;
}
	
output:
4d 54 68 64 00 00 00 06 00 00 00 01 00 78 4d 54 72 6b 00 00 00 0d 00 90 3c
40 81 70 90 3c 00 00 ff 2f 00
	
Source code for writeHex on GitHub
//////////////////////////////
//
// 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
//

int MidiFile::writeHex(const char* aFile, int width) {
   fstream output(aFile, ios::out);
   if (!output.is_open()) {
      cerr << "Error: could not write: " << aFile << endl;
      return 0;
   }
   rwstatus = writeHex(output, width);
   output.close();
   return rwstatus;
}


//
// string version of writeHex().
//

int MidiFile::writeHex(const string& aFile, int width) {
   return MidiFile::writeHex(aFile.data(), width);
}


//
// ostream version of writeHex().
//

int MidiFile::writeHex(ostream& out, int width) {
   stringstream tempstream;
   MidiFile::write(tempstream);
   int value = 0;
   int len = (int)tempstream.str().length();
   int wordcount = 1;
   int linewidth = width >= 0 ? width : 25;
   for (int i=0; i<len; i++) {
      value = (unsigned char)tempstream.str()[i];
      printf("%02x", value);
      if (linewidth) {
         if (i < len - 1) {
            out << (wordcount % linewidth ? ' ' : '\n');
         }
         wordcount++;
      } else {
         // print with no line breaks
         if (i < len - 1) {
            out << ' ';
         }
      }
   }
   if (linewidth) {
      out << '\n';
   }
   return 1;
}
writeBinasc — Print ASCII version of MIDI file in binasc format.
ostream& writeBinasc(ostream& out);
ostream& writeBinasc(const char* filename);
ostream& writeBinasc(const string& filename);
Print ASCII version of MIDI file in binasc format.
Example: Write a MIDI file with a single note as binasc text.
		
#include "MidiFile.h"
#include <iostream>
int main(void) {
   MidiFile midifile;
   MidiEvent event;
   event.tick = 0;
   event.track = 0;
   event.makeNoteOn(60, 64);
   midifile.addEvent(event);
   event.makeNoteOff();
   event.tick = midifile.getTPQ() * 2;  // half-note
   midifile.addEvent(event);
   midifile.writeBinasc(std::cout);
   return 0;
}
	
output:
+M +T +h +d
4'6
2'0
2'1
2'120

; TRACK 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+M +T +r +k
4'13
v0	90 '60 '64
v240	90 '60 '0
v0	ff 2f '0
	
Source code for writeBinasc on GitHub
//////////////////////////////
//
// MidiFile::writeBinasc -- write a standard MIDI file from data into
//    the binasc format (ASCII version of the MIDI file).
//

int MidiFile::writeBinasc(const char* aFile) {
   fstream output(aFile, ios::out);

   if (!output.is_open()) {
      cerr << "Error: could not write: " << aFile << endl;
      return 0;
   }
   rwstatus = writeBinasc(output);
   output.close();
   return rwstatus;
}


int MidiFile::writeBinasc(const string& aFile) {
   return writeBinasc(aFile.data());
}


int MidiFile::writeBinasc(ostream& output) {
   stringstream binarydata;
   rwstatus = write(binarydata);
   if (rwstatus == 0) {
      return 0;
   }

   Binasc binasc;
   binasc.setMidiOn();
   binarydata.seekg(0, ios_base::beg);
   binasc.readFromBinary(output, binarydata);
   return 1;
}


Track-related functions ———————–

The MidiFile class contains a list tracks, each stored as a MidiEventList object. The [] operator accesses each list, and the getTrackCount function will report the current number of tracks in a MidiFile object.

operator[] — Returns the list of events for a track.
MidiEventList& operator[](int index);
Returns the list of events for a track.
Source code for operator[] on GitHub
//////////////////////////////
//
// MidiFile::operator[] -- return the event list for the specified track.
//

MidiEventList& MidiFile::operator[](int aTrack) {
   return *events[aTrack];
}
getTrackCount — Return the number of tracks in the MidiFile.
int getTrackCount(void);
Return the number of tracks in the MidiFile.
Source code for getTrackCount on GitHub
//////////////////////////////
//
// MidiFile::getTrackCount -- return the number of tracks in
//   the Midi File.
//

int MidiFile::getTrackCount(void) {
   return (int)events.size();
}

//
// Alias for getTrackCount()
//

int MidiFile::getNumTracks(void) {
   return getTrackCount();
}

//
// Alias for getTrackCount()
//

int MidiFile::size(void) {
   return getTrackCount();
}


The tracks in a MidiFile can reversibly be merged into a single event list by calling the joinTracks function. This will cause the getTrackCount function to report that there is one track in the file, and if the file is written in this state, it will be saved as a type-0 MIDI file (from which the multi-track state will not be recoverable). Before tracks are joined, the events in each track must be in correct time sequence, so the sort function may need to be called before joining the tracks.

joinTracks — Merge all tracks into a single stream of events.
splitTracks — Separate events into their original track configuration.
void splitTracks(void);
Take joined tracks and split them back into their separate track identities.
Source code for splitTracks on GitHub
//////////////////////////////
//
// 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) {
      absoluteTicks();
   }

   int maxTrack = 0;
   int i;
   int length = events[0]->size();
   for (i=0; i<length; i++) {
      if ((*events[0])[i].track > maxTrack) {
          maxTrack = (*events[0])[i].track;
      }
   }
   int trackCount = maxTrack + 1;

   if (trackCount <= 1) {
      return;
   }

   MidiEventList* olddata = events[0];
   events[0] = NULL;
   events.resize(trackCount);
   for (i=0; i<trackCount; i++) {
      events[i] = new MidiEventList;
   }

   int trackValue = 0;
   for (i=0; i<length; i++) {
      trackValue = (*olddata)[i].track;
      events[trackValue]->push_back_no_copy(&(*olddata)[i]);
   }

   olddata->detach();
   delete olddata;

   if (oldTimeState == TIME_STATE_DELTA) {
      deltaTicks();
   }

   theTrackState = TRACK_STATE_SPLIT;
}


The hasJoinedTracks and hasSplitTracks functions can be used to detect the current state of the tracks in a MidiFile. By default, a MidiFile will be in the split state.

hasJoinedTracks — Return true if MidiFile is in the joined-track state.
int hasJoinedTracks(void);
Return true if MidiFile is in the joined-track state.
Source code for hasJoinedTracks on GitHub
//////////////////////////////
//
// MidiFile::hasJoinedTracks -- Returns true if the MidiFile tracks
//    are in a joined state.
//

int MidiFile::hasJoinedTracks(void) {
   return theTrackState == TRACK_STATE_JOINED;
}
hasSplitTracks — Return true if MidiFile is in the split-track state.


When a MidiFile is in the joined state, the original track is stored in the track variable of each MidiEvent. The getSplitTrack function returns the track index for when a MidiFile is in the split state.

getSplitTrack — Get the track number of an event when MidiFile is in split state.
int getSplitTrack(int track, int index);
Get the track number of a MidiEvent when the MidiFile is in the split state. This function will return the original track index when the MidiFile is in the joined-track state.
Source code for getSplitTrack on GitHub
//////////////////////////////
//
// 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) {
   if (hasSplitTracks()) {
      return track;
   } else {
      return getEvent(track, index).track;
   }
}

//
// When the parameter is void, assume track 0:
//

int MidiFile::getSplitTrack(int index) {
   if (hasSplitTracks()) {
      return 0;
   } else {
      return getEvent(0, index).track;
   }
}


Here are functions which relate to adding, deleting and merging tracks:

addTrack — Added one or more empty tracks to the MidiFile.
deleteTicks — Remove the specified track from the MidiFile.
void deleteTicks(int track);
Remove a track from the MidiFile. Tracks are numbered starting at track 0.
Source code for deleteTicks on GitHub
mergeTracks — Combine two tracks into a single track.
void mergeTracks(int track1, int track2);
Combine the data from two tracks into one, placing the data in the first track parameter, and moving the other tracks in the file around to file in the spot where track 2 was deleted from. The results of this function call cannot be reversed.
Source code for mergeTracks on GitHub
//////////////////////////////
//
// 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) {
      absoluteTicks();
   }
   int i, j;
   int length = getNumTracks();
   for (i=0; i<(int)events[aTrack1]->size(); i++) {
      mergedTrack->push_back((*events[aTrack1])[i]);
   }
   for (j=0; j<(int)events[aTrack2]->size(); i++) {
      (*events[aTrack2])[i].track = aTrack1;
      mergedTrack->push_back((*events[aTrack2])[i]);
   }

   sortTrack(*mergedTrack);

   delete events[aTrack1];

   events[aTrack1] = mergedTrack;

   for (i=aTrack2; i<length-1; i++) {
      events[i] = events[i+1];
   }

   events[length] = NULL;
   events.resize(length-1);

   if (oldTimeState == TIME_STATE_DELTA) {
      deltaTicks();
   }
}


When a MidiFile is in absolute-tick mode (see further below), the tick variable of MidiEvent objects in the MidiFile are the total number of ticks since the start time for the file. In the absolute-tick mode, MidiEvents can be added to tracks in non-sequential order. To re-arrange the events into proper time order (such as before writing the file), use the sortTracks function, which will sort all tracks in the MIDI file, or sortTrack function, which will sort a particular track by its index number.

sortTracks — Sort all tracks in the MidiFile by event timestamps.
sortTrack — Sort a track in terms of timestamps of the events.


Time-related functions ———————-

MidiEvents stored in a MidiFile structure contain two public variables related to time:

  1. int MidiEvent::tick — Quanta time-units describing durations in Standard MIDI Files.
  2. double MidiEvent::seconds — Interpreted physical time units in seconds calculated by doTimeInSecondsAnalysis() from .tick data and tempo meta-messages stored in the MidiFile.

Event tick interpretation

Tick values on MidiEvents can be set to two types of states: (1) delta time, which indicate the number of ticks to wait from the last event in the track before performing the current event, and (2) absoute time, where the tick value represents the cumulative tick time since the start of the MidiFile until the performance time of the current event. Standard MIDI Files store tick times as delta ticks, but it is often more useful to manipulate event data with absolute ticks. The absoluteTicks and deltaTicks functions switch the event tick values within the MidiFile between these two modes:

absoluteTicks — Convert event delta tick values into absolute ticks.
deltaTicks — Convert timestamps into delta times.


The isDeltaTime and isAbsoluteTime functions can be used to check which mode in which the event ticks are currently given.

isDeltaTime — Returns true if event ticks are in delta-time mode.
int isDeltaTime(void);
Returns true if event ticks are in delta-time mode.
Source code for isDeltaTime on GitHub
isAbsoluteTime — Returns true if event ticks are in absolute-time mode.


Tick values are symbolic time units, such as rhythms in music notation. For example quarter notes do not have a specific duration in seconds until a tempo is applied to the rhythm. Within Standard MIDI Files, there is a field which specifies how many ticks represent a quarter note. This convserion value can be read or set with the following two MidiFile member functions:

getTicksPerQuarterNote — Return the delta time ticks units which represent a quarter note.
int getTicksPerQuarterNote(void);
Return the delta time ticks units which represent a quarter note.
Source code for getTicksPerQuarterNote on GitHub
//////////////////////////////
//
// MidiFile::getTicksPerQuarterNote -- returns the number of
//   time units that are supposed to occur during a quarternote.
//

int MidiFile::getTicksPerQuarterNote(void) {
   if (ticksPerQuarterNote == 0xE728) {
      // this is a special case which is the SMPTE time code
      // setting for 25 frames a second with 40 subframes
      // which means one tick per millisecond.  When SMPTE is
      // being used, there is no real concept of the quarter note,
      // so presume 60 bpm as a simiplification here.
      // return 1000;
   }
   return ticksPerQuarterNote;
}

//
// Alias for getTicksPerQuarterNote:
//

int MidiFile::getTPQ(void) {
   return getTicksPerQuarterNote();
}
setTicksPerQuarterNote — Set the ticks-per-quarter note value.


Physical time of events

doTimeAnalysis — Calculate the time in seconds for each event in the MidiFile.
getTimeInSeconds — Return the time in seconds for the specified message.
setMillisecondTicks — Set the ticks per quarter note value in the MIDI file header
getAbsoluteTickTime — Convert a time in seconds into an absolute tick time.
sortTrack — Sort a track in terms of timestamps of the events.
sortTracks — Sort all tracks in the MidiFile by event timestamps.


Other functions —————-

addEvent — Add a new MidiEvents to the end of the specified track.
addMetaEvent — Insert a meta message event.
addPitchBend — Insert a pitch bend message into the given track.
allocateEvents — Allocate extra space for a track event list.
clear — Clear all tracks, leaving one track with no data in it.
extractVlvTime — Extract a VLV value from the input stream.
getEvent — Return the event at the given index in the specified track.
getEventCount — Return the number of events in the specified track.
getFilename — Return the filename for the MidiFile.
makeVLV — Convert an integer into a list of variable length value bytes.
writeHex — Print MidiFile as a list of ASCII hex bytes.
ostream& writeHex(const char* filename);
ostream& writeHex(const string& filename);
ostream& writeHex(int);
Print the MIDI file as a sequence of ASCII hex bytes, formatted 25 bytes to a line and two digits each.
Example: Write a MIDI file with a single note as Hex bytes.
		
#include "MidiFile.h"
#include <iostream>
int main(void) {
   MidiFile midifile;
   MidiEvent event;
   event.tick = 0;
   event.track = 0;
   event.makeNoteOn(60, 64);
   midifile.addEvent(event);
   event.makeNoteOff();
   event.tick = midifile.getTPQ() * 2;  // half-note
   midifile.addEvent(event);
   midifile.writeHex(std::cout);
   return 0;
}
	
output:
4d 54 68 64 00 00 00 06 00 00 00 01 00 78 4d 54 72 6b 00 00 00 0d 00 90 3c
40 81 70 90 3c 00 00 ff 2f 00
	
Source code for writeHex on GitHub
//////////////////////////////
//
// 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
//

int MidiFile::writeHex(const char* aFile, int width) {
   fstream output(aFile, ios::out);
   if (!output.is_open()) {
      cerr << "Error: could not write: " << aFile << endl;
      return 0;
   }
   rwstatus = writeHex(output, width);
   output.close();
   return rwstatus;
}


//
// string version of writeHex().
//

int MidiFile::writeHex(const string& aFile, int width) {
   return MidiFile::writeHex(aFile.data(), width);
}


//
// ostream version of writeHex().
//

int MidiFile::writeHex(ostream& out, int width) {
   stringstream tempstream;
   MidiFile::write(tempstream);
   int value = 0;
   int len = (int)tempstream.str().length();
   int wordcount = 1;
   int linewidth = width >= 0 ? width : 25;
   for (int i=0; i<len; i++) {
      value = (unsigned char)tempstream.str()[i];
      printf("%02x", value);
      if (linewidth) {
         if (i < len - 1) {
            out << (wordcount % linewidth ? ' ' : '\n');
         }
         wordcount++;
      } else {
         // print with no line breaks
         if (i < len - 1) {
            out << ' ';
         }
      }
   }
   if (linewidth) {
      out << '\n';
   }
   return 1;
}
setFilename — Set the filename of the MidiFile.


Static functions —————-

readByte — Read one byte from the input stream.
readLittleEndian2Bytes — Read two bytes from the input stream in little-endian format.
readLittleEndian4Bytes — Read four bytes from the input stream in little-endian order.


Private functions —————–

buildTimeMap — Create a time map relating ticks to absolute time.
extractMidiData — Extract a MIDI message from the input stream.
linearTickInterpolationAtSecond — Return the tick value at a given input time.
secondsearch — Comparison function for sorting time.
ticksearch — Compare function for finding a tick entry in the time map.
unpackVLV — Convert VLV byte list into an unsigned long value.
writeVLValue — Convert an integer int a list of VLV bytes.

 

Keyboard shortcuts for this page:

  • ctrl++/= to open all function documentation entries.
  • ctrl+ to close all documentation.
  • ctrl+eto toggle display of all code examples.
  • shft+click will open documentation for a single method and close all other entries.