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.
write — Write a Standard MIDI file from the MidiFile contents.
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; }
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.
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.
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.
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.
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.
Here are functions which relate to adding, deleting and merging
tracks:
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.
Time-related functions
———————-
MidiEvents stored in a MidiFile structure contain two public variables related to time:
- int MidiEvent::tick — Quanta time-units describing durations in Standard MIDI Files.
- 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:
The isDeltaTime
and isAbsoluteTime
functions can be used to check
which mode in which the event ticks are currently given.
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:
Physical time of events
Other functions
—————-
Static functions
—————-
Private functions
—————–
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.