The code examples in the following tutorials can be placed into the midifile/src-programs directory to compile them. For example if the program is named “src-programs/myprogram.cpp”, then type make myprogram in the midifile base directory to compile it. The compiled program will be named in the myprogram in the midifile/bin directory.
Fix Standard MIDI File track chunk byte counts.
MIDI track chunk byte counts are often incorrect in Standard MIDI Files. This example program shows how to correct byte counts by reading a MIDI file and then writing it out again, without doing anything else to automatically fix the track chunk sizes. The MidiFile class ignores the byte-count value in track chunk headers when reading a MIDI file, so incorrect byte counts do not affect parsing of the file. When writing a file out again, the byte count for each track is calculated and automatically corrected. Note that the MidiFile class usually expands running-status messages, so the size of the MIDI file may change from the original size. Also note that this example overwrites the old file contents. To ensure that there was not a problem reading the file, the MidiFile::status function is checked to see if the parsing of the input data was successful.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "MidiFile.h"
#include "Options.h"
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
Options options;
options.process(argc, argv);
if (options.getArgCount() < 1) {
cerr << "Usage: " << options.getCommand() << " input(s)\n";
exit(1);
}
MidiFile midifile;
for (int i=1; i<options.getArgCount(); i++ ) {
midifile.read(options.getArg(i));
if (midifile.status()) {
midifile.write(options.getArg(i));
} else {
cerr << "Error reading MIDI file: " << options.getArg(i) << endl;
}
}
return 0;
}
-
MidiFile::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:-
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; }
-
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; }
-
-
MidiFile::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; }
-
MidiFile::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; }
-
Options::getArg — Return an argument string after options have been filtered out.
const string& getArg(int index);
Returns the specified argument. The first argument is accessed by .getArg(1).Source code for getArg on GitHub
////////////////////////////// // // Options::getArg -- returns the specified argument. // argurment 0 is the command name. // const string& Options::getArg(int index) { if (index < 0 || index >= (int)argument.size()) { cerr << "Error: argument " << index << " does not exist." << endl; exit(1); } return argument[index]; } // Alias: const string& Options::getArgument(int index) { return getArg(index); }
-
Options::getArgCount — Return the number of arguments.
int getArgCount(void);
Return the number of arguments. This count excludes the command name, and filtered options.Source code for getArgCount on GitHub
////////////////////////////// // // Options::getArgCount -- number of arguments on command line. // does not count the options or the command name. // int Options::getArgCount(void) { return argument.size() - 1; } // Alias: int Options::getArgumentCount(void) { return getArgCount(); }
-
Options::getCommand — Return the command name.
string getCommand(void);
Return the command name.Source code for getCommand on GitHub
////////////////////////////// // // Options::getCommand -- returns argv[0] (the first string // in the original argv list. // string Options::getCommand(void) { if (argument.size() == 0) { return ""; } else { return argument[0]; } }
-
Options::process — Parse the command-line string to identify options and arguments.
void process(int argc, char** argv, int error_check, int suppress);
void process(int error_check, int suppress);
Parse the command-line string to identify options and arguments.Source code for process on GitHub
////////////////////////////// // // Options::process -- Same as xverify. // default values: error_check = 1, suppress = 0; // void Options::process(int argc, char** argv, int error_check, int suppress) { setOptions(argc, argv); xverify(error_check, suppress); } void Options::process(int error_check, int suppress) { xverify(error_check, suppress); }
Switch note-offs between 0x90 w/ 0 velocity and 0x80 styles.
The MIDI protocol allows two types of note-off messages: (1) using the a command byte starting with the hex digit "8" that always indicates a note-off message for a given key with a given off-velocity, and (2) using note-on commands (starting with the hex digit "8"), but setting the attack velocity to 0. The following program can convert between these two different styles. The default behavior is to convert all note-off messages to the "8" style. If the -0 option is used, the note-offs will be converted into the "9" style note-off message which use a zero attack velocity within a note-on message.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "MidiFile.h"
#include "Options.h"
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
Options options;
options.define("0|O|o|z|9|zero=b",
"Set note-offs to note-on messages with zero velocity.");
options.process(argc, argv);
bool zeroQ = options.getBoolean("zero");
if (options.getArgCount() != 2) {
cerr << "Usage: " << options.getCommand() << " input.mid output.mid\n";
exit(1);
}
MidiFile midifile;
midifile.read(options.getArg(1));
if (!midifile.status()) {
cerr << "Error: could not read file" << options.getArg(1) << endl;
exit(1);
}
for (int track=0; track<midifile.size(); track++) {
for (int event=0; event<midifile[track].size(); event++) {
if (!midifile[track][event].isNoteOff()) {
continue;
}
if (zeroQ) {
midifile[track][event].setCommandNibble(0x90);
midifile[track][event][2] = 0;
} else {
midifile[track][event].setCommandNibble(0x80);
}
}
}
midifile.write(options.getArg(2));
return 0;
}
-
MidiFile::getEventCount — Return the number of events in the specified track.
int getEventCount(int track);
Return the number of events in the specified track.Source code for getEventCount on GitHub
////////////////////////////// // // MidiFile::getEventCount -- returns the number of events // in a given track. // int MidiFile::getEventCount(int aTrack) { return events[aTrack]->size(); } int MidiFile::getNumEvents(int aTrack) { return events[aTrack]->size(); }
-
MidiFile::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(); }
-
MidiFile::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:-
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; }
-
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; }
-
-
MidiFile::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; }
-
MidiFile::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; }
-
MidiMessage::isNoteOff — Return true if MIDI message is a note off message.
int isNoteOff(void);
Return true if command nibble is 0x80, or command nibble is 0x90 and key velocity is 0.Source code for isNoteOff on GitHub
////////////////////////////// // // MidiMessage::isNoteOff -- Returns true if the command nibble is 0x80 // or if the command nibble is 0x90 with p2=0 velocity. // int MidiMessage::isNoteOff(void) { if (size() != 3) { return 0; } else if (((*this)[0] & 0xf0) == 0x80) { return 1; } else if ((((*this)[0] & 0xf0) == 0x90) && ((*this)[2] == 0)) { return 1; } else { return 0; } }
-
MidiMessage::setCommandNibble — Set the command nibble of a command byte.
void setCommandNibble(int nibble);
Set the command nibble of a command byte. The value can either be in the range from 0x8 to 0xf, or 0x80 to 0xf0.Source code for setCommandNibble on GitHub
////////////////////////////// // // MidiMessage::setCommandNibble -- // void MidiMessage::setCommandNibble(int value) { if (this->size() < 1) { this->resize(1); } if (value <= 0x0f) { (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)((value << 4) & 0xf0)); } else { (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)(value & 0xf0)); } }
-
Options::define — Define an option.
int define(const string& definition);
int define(const string& definition, const string& description);
Define an option that will be used to parse the argument list. Option definitions have this structure:- option-name: name of the option (one or more characters, note includeing spaces or equal signs.
- alias-name: equivalent name(s) of the option.
- option-type: single character indicating the option data type.
- option-default: default value for option if none given on the command-line.
An optional description of the option can be given as a second parameter.
Source code for define on GitHub
////////////////////////////// // // Options::define -- store an option definition in the registry. Option // definitions have this sructure: // option-name|alias-name1|alias-name2=option-type:option-default // option-name :: name of the option (one or more character, not including // spaces or equal signs. // alias-name :: equivalent name(s) of the option. // option-type :: single charater indicating the option data type. // option-default :: default value for option if no given on the command-line. // int Options::define(const string& aDefinition) { Option_register* definitionEntry = NULL; // Error if definition string doesn't contain an equals sign auto location = aDefinition.find("="); if (location == string::npos) { cerr << "Error: no \"=\" in option definition: " << aDefinition << endl; exit(1); } string aliases = aDefinition.substr(0, location); string rest = aDefinition.substr(location+1); string otype = rest; string ovalue = ""; location = rest.find(":"); if (location != string::npos) { otype = rest.substr(0, location); ovalue = rest.substr(location+1); } // Remove anyspaces in the option type field otype.erase(remove_if(otype.begin(), otype.end(), ::isspace), otype.end()); // Option types are only a single charater (b, i, d, c or s) if (otype.size() != 1) { cerr << "Error: option type is invalid: " << otype << " in option definition: " << aDefinition << endl; exit(1); } // Check to make sure that the type is known if (otype[0] != OPTION_STRING_TYPE && otype[0] != OPTION_INT_TYPE && otype[0] != OPTION_FLOAT_TYPE && otype[0] != OPTION_DOUBLE_TYPE && otype[0] != OPTION_BOOLEAN_TYPE && otype[0] != OPTION_CHAR_TYPE ) { cerr << "Error: unknown option type \'" << otype[0] << "\' in defintion: " << aDefinition << endl; exit(1); } // Set up space for a option entry in the registry definitionEntry = new Option_register(aDefinition, otype[0], ovalue); auto definitionIndex = optionRegister.size(); // Store option aliases string optionName; unsigned int i; aliases += '|'; for (i=0; i<aliases.size(); i++) { if (::isspace(aliases[i])) { continue; } else if (aliases[i] == '|') { if (isDefined(optionName)) { cerr << "Option \"" << optionName << "\" from definition:" << endl; cerr << "\t" << aDefinition << endl; cerr << "is already defined in: " << endl; cerr << "\t" << getDefinition(optionName) << endl; exit(1); } if (optionName.size() > 0) { optionList[optionName] = definitionIndex; } optionName.clear(); } else { optionName += aliases[i]; } } // Store definition in registry and return its indexed location. // This location will be used to link option aliases to the main // command name. optionRegister.push_back(definitionEntry); return definitionIndex; } int Options::define(const string& aDefinition, const string& aDescription) { int index = define(aDefinition); optionRegister[index]->setDescription(aDescription); return index; }
-
Options::getArg — Return an argument string after options have been filtered out.
const string& getArg(int index);
Returns the specified argument. The first argument is accessed by .getArg(1).Source code for getArg on GitHub
////////////////////////////// // // Options::getArg -- returns the specified argument. // argurment 0 is the command name. // const string& Options::getArg(int index) { if (index < 0 || index >= (int)argument.size()) { cerr << "Error: argument " << index << " does not exist." << endl; exit(1); } return argument[index]; } // Alias: const string& Options::getArgument(int index) { return getArg(index); }
-
Options::getArgCount — Return the number of arguments.
int getArgCount(void);
Return the number of arguments. This count excludes the command name, and filtered options.Source code for getArgCount on GitHub
////////////////////////////// // // Options::getArgCount -- number of arguments on command line. // does not count the options or the command name. // int Options::getArgCount(void) { return argument.size() - 1; } // Alias: int Options::getArgumentCount(void) { return getArgCount(); }
-
Options::getBoolean — Returns true if the option was set on the command-line.
int getBoolean(const string& option);
Returns true if the option was set on the command-line.Source code for getBoolean on GitHub
////////////////////////////// // // Options::getBoolean -- returns true if the option was // used on the command line. // int Options::getBoolean(const string& optionName) { int index = getRegIndex(optionName); if (index < 0) { return 0; } return optionRegister[index]->isModified(); }
-
Options::getCommand — Return the command name.
string getCommand(void);
Return the command name.Source code for getCommand on GitHub
////////////////////////////// // // Options::getCommand -- returns argv[0] (the first string // in the original argv list. // string Options::getCommand(void) { if (argument.size() == 0) { return ""; } else { return argument[0]; } }
-
Options::process — Parse the command-line string to identify options and arguments.
void process(int argc, char** argv, int error_check, int suppress);
void process(int error_check, int suppress);
Parse the command-line string to identify options and arguments.Source code for process on GitHub
////////////////////////////// // // Options::process -- Same as xverify. // default values: error_check = 1, suppress = 0; // void Options::process(int argc, char** argv, int error_check, int suppress) { setOptions(argc, argv); xverify(error_check, suppress); } void Options::process(int error_check, int suppress) { xverify(error_check, suppress); }
Print a list of note attacks in a MIDI file
The following example program will print a list of all note-ons in the MIDI file. The notes are will be time sorted, and each note will be prefixed with the absolute time tick value.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "MidiFile.h"
#include "Options.h"
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
Options options;
options.process(argc, argv);
if (options.getArgCount() != 1) {
cerr << "At least one MIDI filename is required.\n";
exit(1);
}
MidiFile midifile;
midifile.read(options.getArg(1));
if (!midifile.status()) {
cerr << "Error reading MIDI file " << options.getArg(1)) << endl;
exit(1);
}
midifile.joinTracks();
int track = 0;
for (int i=0; i<midifile[track].size(); i++) {
if (!midifile[track][i].isNoteOn()) {
continue;
}
cout << midifile[track][i].time
<< '\t' << midifile[track][i][1]
<< endl;
}
}
-
MidiEventList::operator[] — Access a MidiEvent in the event list.
MidiEvent& operator[](int index);
Access a MidiEvent in the event list.Source code for operator[] on GitHub
////////////////////////////// // // MidiEventList::operator[] -- // MidiEvent& MidiEventList::operator[](int index) { return *list[index]; }
- —
-
MidiFile::joinTracks — Merge all tracks into a single stream of events.
void joinTracks(void);
Merge the data from all tracks, but keep the identify of the tracks unique so that the function splitTracks() can be called to split the tracks into separate streams again. The style of the MidiFile when read from a file is with the tracks split.Source code for joinTracks on GitHub
////////////////////////////// // // 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) { return; } MidiEventList* joinedTrack; joinedTrack = new MidiEventList; int messagesum = 0; int length = getNumTracks(); int i, j; for (i=0; i<length; i++) { messagesum += (*events[i]).size(); } joinedTrack->reserve((int)(messagesum + 32 + messagesum * 0.1)); int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_DELTA) { absoluteTicks(); } for (i=0; i<length; i++) { for (j=0; j<(int)events[i]->size(); j++) { joinedTrack->push_back_no_copy(&(*events[i])[j]); } } clear_no_deallocate(); delete events[0]; events.resize(0); events.push_back(joinedTrack); sortTracks(); if (oldTimeState == TIME_STATE_DELTA) { deltaTicks(); } theTrackState = TRACK_STATE_JOINED; }
-
MidiFile::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]; }
-
MidiFile::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:-
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; }
-
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; }
-
-
MidiFile::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(); }
-
MidiFile::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; }
-
MidiMessage::isNoteOn — Returns true if MIDI message is a note-on message.
int isNoteOn(void);
Returns true if MIDI message is a note-on message.Source code for isNoteOn on GitHub
////////////////////////////// // // MidiMessage::isNoteOn -- Returns true if the command byte is in the 0x90 // range and the velocity is non-zero // int MidiMessage::isNoteOn(void) { if (size() != 3) { return 0; } else if (((*this)[0] & 0xf0) != 0x90) { return 0; } else if ((*this)[2] == 0) { return 0; } else { return 1; } }
-
MidiMessage::operator[] — Returns the specified byte of a MIDI message.
uchar& operator[](int index);
Returns the specified byte of a MIDI message. The first byte will always be a command byte in the range from 0x80 to 0xff for the first byte of a valid MIDI message (or meta message). In other words, running status is not allowed in MidiMessage data. This function is inherited from the Standard Template Library class vector<uchar>. -
Options::getArg — Return an argument string after options have been filtered out.
const string& getArg(int index);
Returns the specified argument. The first argument is accessed by .getArg(1).Source code for getArg on GitHub
////////////////////////////// // // Options::getArg -- returns the specified argument. // argurment 0 is the command name. // const string& Options::getArg(int index) { if (index < 0 || index >= (int)argument.size()) { cerr << "Error: argument " << index << " does not exist." << endl; exit(1); } return argument[index]; } // Alias: const string& Options::getArgument(int index) { return getArg(index); }
-
Options::getArgCount — Return the number of arguments.
int getArgCount(void);
Return the number of arguments. This count excludes the command name, and filtered options.Source code for getArgCount on GitHub
////////////////////////////// // // Options::getArgCount -- number of arguments on command line. // does not count the options or the command name. // int Options::getArgCount(void) { return argument.size() - 1; } // Alias: int Options::getArgumentCount(void) { return getArgCount(); }
-
Options::process — Parse the command-line string to identify options and arguments.
void process(int argc, char** argv, int error_check, int suppress);
void process(int error_check, int suppress);
Parse the command-line string to identify options and arguments.Source code for process on GitHub
////////////////////////////// // // Options::process -- Same as xverify. // default values: error_check = 1, suppress = 0; // void Options::process(int argc, char** argv, int error_check, int suppress) { setOptions(argc, argv); xverify(error_check, suppress); } void Options::process(int error_check, int suppress) { xverify(error_check, suppress); }
Reverse the order of notes in a MIDI file.
The following example program will reverse the order of pitches in a MIDI file, while at the same time preserve the original rhythms. First note-ons and note-offs are linked by calling the MidiFile::linkNotePairs function. Then a list of note-ons are extracted by iterating through each event in each track. And finally the extracted note list is used to switch the key numbers in reverse order in the list. If the MIDI file is monophonic (single melody), then this program will reverse the order of notes in the music, but keep the rhythms in theor original locations. If the music is polyphonic, the sonorities will likely change. See the mirror tutorial for how to reverse both pitch and durations.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "MidiFile.h"
#include "Options.h"
using namespace std;
void swapNotes(vector<MidiEvent*>& notes, int index1, int index2);
int main(int argc, char** argv) {
Options options;
options.process(argc, argv);
if (options.getArgCount() != 2) {
cerr << "two MIDI filenames are required.\n";
exit(1);
}
MidiFile midifile;
midifile.read(options.getArg(1));
if (!midifile.status()) {
cerr << "Error reading MIDI file " << options.getArg(1) << endl;
exit(1);
}
midifile.linkNotePairs();
vector<MidiEvent*> notes;
notes.reserve(123456);
for (int track=0; track<midifile.size(); track++) {
for (int event=0; event<midifile[track].size(); event++) {
if (midifile[track][event].isNoteOn()) {
if (midifile[track][event].isLinked()) {
notes.push_back(&midifile[track][event]);
}
}
}
}
int count = notes.size();
for (int i=0; i<count/2; i++) {
swapNotes(notes, i, count-1-i);
}
midifile.write(options.getArg(2));
return 0;
}
void swapNotes(vector<MidiEvent*>& notes, int index1, int index2) {
MidiEvent* noteon1 = notes[index1];
MidiEvent* noteon2 = notes[index2];
MidiEvent* noteoff1 = notes[index1]->getLinkedEvent();
MidiEvent* noteoff2 = notes[index2]->getLinkedEvent();
if (noteon1 == NULL) { return; }
if (noteon2 == NULL) { return; }
if (noteoff1 == NULL) { return; }
if (noteoff2 == NULL) { return; }
int pitch1 = noteon1->getKeyNumber();
int pitch2 = noteon2->getKeyNumber();
if (pitch1 == pitch2) { return; }
if (pitch1 < 0) { return; }
if (pitch2 < 0) { return; }
noteon1->setKeyNumber(pitch2);
noteoff1->setKeyNumber(pitch2);
noteon2->setKeyNumber(pitch1);
noteoff2->setKeyNumber(pitch1);
}
-
MidiEvent::getLinkedEvent — Returns a linked event (such as for note-on/note-off pair).
MidiEvent* getLinkedEvent(void);
Returns a linked event. Usually this is the note-off message for a note-on message and vice-versa. Returns NULL if there are no links.Source code for getLinkedEvent on GitHub
////////////////////////////// // // MidiEvent::getLinkedEvent -- Returns a linked event. Usually // this is the note-off message for a note-on message and vice-versa. // Returns null if there are no links. // MidiEvent* MidiEvent::getLinkedEvent(void) { return eventlink; }
-
MidiEvent::isLinked — Returns true of the message is linked to another event.
Returns true if there is an event which is not NULL. This function is similar to MidiEvent::getLinkedEvent().Source code for isLinked on GitHub
////////////////////////////// // // MidiEvent::isLinked -- Returns true if there is an event which is not // NULL. This function is similar to getLinkedEvent(). // int MidiEvent::isLinked(void) { return eventlink == NULL ? 0 : 1; }
-
MidiEventList::operator[] — Access a MidiEvent in the event list.
MidiEvent& operator[](int index);
Access a MidiEvent in the event list.Source code for operator[] on GitHub
////////////////////////////// // // MidiEventList::operator[] -- // MidiEvent& MidiEventList::operator[](int index) { return *list[index]; }
-
MidiFile::getEventCount — Return the number of events in the specified track.
int getEventCount(int track);
Return the number of events in the specified track.Source code for getEventCount on GitHub
////////////////////////////// // // MidiFile::getEventCount -- returns the number of events // in a given track. // int MidiFile::getEventCount(int aTrack) { return events[aTrack]->size(); } int MidiFile::getNumEvents(int aTrack) { return events[aTrack]->size(); }
-
MidiFile::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(); }
-
MidiFile::linkNotePairs — Link corresponding note-ons and note-offs.
int linkNotePairs(void);
Link corresponding note-ons and note-offs.Source code for linkNotePairs on GitHub
////////////////////////////// // // 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; i<getTrackCount(); i++) { if (events[i] == NULL) { continue; } sum += events[i]->linkNotePairs(); } return sum; }
-
MidiFile::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]; }
-
MidiFile::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:-
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; }
-
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; }
-
-
MidiFile::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(); }
-
MidiFile::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; }
-
MidiFile::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; }
-
MidiMessage::getKeyNumber — Get the MIDI key number for notes and aftertouch.
int getKeyNumber(void);
Get the MIDI key number for notes and aftertouch.Source code for getKeyNumber on GitHub
////////////////////////////// // // MidiMessage::getKeyNumber -- Return the key number (such as 60 for // middle C). If the message does not have a note parameter, then // return -1; if the key is invalid (above 127 in value), then // limit to the range 0 to 127. // int MidiMessage::getKeyNumber(void) { if (isNote() || isAftertouch()) { int output = getP1(); if (output < 0) { return output; } else { return 0xff & output; } } else { return -1; } }
-
MidiMessage::isNoteOn — Returns true if MIDI message is a note-on message.
int isNoteOn(void);
Returns true if MIDI message is a note-on message.Source code for isNoteOn on GitHub
////////////////////////////// // // MidiMessage::isNoteOn -- Returns true if the command byte is in the 0x90 // range and the velocity is non-zero // int MidiMessage::isNoteOn(void) { if (size() != 3) { return 0; } else if (((*this)[0] & 0xf0) != 0x90) { return 0; } else if ((*this)[2] == 0) { return 0; } else { return 1; } }
-
MidiMessage::setKeyNumber — Set the MIDI key number for notes and aftertouch.
void setKeyNumber(int size);
Set the MIDI key number for notes and aftertouch.Source code for setKeyNumber on GitHub
////////////////////////////// // // MidiMessage::setKeyNumber -- Set the note on/off key number (or // aftertouch key). Ignore if not note or aftertouch message. // Limits the input value to the range from 0 to 127. // void MidiMessage::setKeyNumber(int value) { if (isNote() || isAftertouch()) { setP1(value & 0xff); } else { // don't do anything since this is not a note-related message. } }
-
Options::getArg — Return an argument string after options have been filtered out.
const string& getArg(int index);
Returns the specified argument. The first argument is accessed by .getArg(1).Source code for getArg on GitHub
////////////////////////////// // // Options::getArg -- returns the specified argument. // argurment 0 is the command name. // const string& Options::getArg(int index) { if (index < 0 || index >= (int)argument.size()) { cerr << "Error: argument " << index << " does not exist." << endl; exit(1); } return argument[index]; } // Alias: const string& Options::getArgument(int index) { return getArg(index); }
-
Options::getArgCount — Return the number of arguments.
int getArgCount(void);
Return the number of arguments. This count excludes the command name, and filtered options.Source code for getArgCount on GitHub
////////////////////////////// // // Options::getArgCount -- number of arguments on command line. // does not count the options or the command name. // int Options::getArgCount(void) { return argument.size() - 1; } // Alias: int Options::getArgumentCount(void) { return getArgCount(); }
-
Options::process — Parse the command-line string to identify options and arguments.
void process(int argc, char** argv, int error_check, int suppress);
void process(int error_check, int suppress);
Parse the command-line string to identify options and arguments.Source code for process on GitHub
////////////////////////////// // // Options::process -- Same as xverify. // default values: error_check = 1, suppress = 0; // void Options::process(int argc, char** argv, int error_check, int suppress) { setOptions(argc, argv); xverify(error_check, suppress); } void Options::process(int error_check, int suppress) { xverify(error_check, suppress); }
Mirror a MIDI file in time and/or pitch.
The example program in this tutorial will perform mirror operations on a MIDI file by reversing time in the file and/or mirroring the pitches in the file. Without options, the time of the MIDI file will be reversed. Specifying the -p option with a MIDI key number to reflect on will cause the pitches to be mirrored, such as -p 60 to invert the pitch around middle-C. To mirror both time and pitch, add the -r option, such as:mirror -r -p 60 input.mid output.mid
or
mirror -rp 60 input.mid output.mid. As a demonstration, here are four MIDI files for Bach's Well-tempered Clavier, Book II, prelude 2 in C minor in for orientations:
- Original MIDI file
- Retrograde
- Inverted pitch around middle-C
- Retrograde Inversion
</ol>
Trying mirroring a MIDI file and then repeat the mirroring process
to take the MIDI file back to its original state.
The doTimeMirror() function
(lines 51-69)
reverses time by extracting the largest absolute tick time and
subtracting each
MidiMessage
tick time from that maximum timestamp to reverse the time sequence.
This will place note-offs before their matching note-ons, so the
times of note-ons and their matching note-offs are then swapped.
At the end of the function the
linkNotePairs
function is called to reverse messages in each track, as well as
move notes-ons back in front of their matching note-offs.
The doPitchMirror() function
(lines 83-98)
does not require moving any events around in the MIDI file, since
the note-ons and note-offs do not reverse order. Notes on the 10th
MIDI channel (channel "9" from a programming viewpoint) are not
altered since this is the General MIDI drum track channel. The
linkNotePairs
function has built in range checking which prevents values other
than 0–127 from incorrectly being inserted into the MIDI
message.
Library functions used in this example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
#include "MidiFile.h" #include "Options.h" #include <iostream> using namespace std; void doTimeMirror (MidiFile& midifile); void swapLinks (MidiEvent& event); void doPitchMirror (MidiFile& midifile, double pivot); void setMirror (vector<int>& mirror, double pivot); int main(int argc, char** argv) { Options options; options.define("p|pitch|i|inversion=d:60.0", "Do a pitch mirror, reflecting around middle C"); options.define("r|reverse|retrograde=b", "Do a time reversal of the MIDI file"); options.process(argc, argv); if (options.getArgCount() != 2) { cerr << "two MIDI filenames are required.\n"; exit(1); } MidiFile midifile; midifile.read(options.getArg(1)); if (!midifile.status()) { cerr << "Error reading MIDI file " << options.getArg(1) << endl; exit(1); } if (options.getBoolean("pitch")) { doPitchMirror(midifile, options.getDouble("pitch")); if (options.getBoolean("retrograde")) { doTimeMirror(midifile); } } else { doTimeMirror(midifile); } midifile.write(options.getArg(2)); return 0; } ////////////////////////////// // // doTimeMirror -- Reverse the timeline of the MIDI file. Note-ons // and their matching Note-offs are switched. // void doTimeMirror(MidiFile& midifile) { midifile.linkNotePairs(); midifile.joinTracks(); int maxtick = midifile[0].back().tick; int i; for (i=0; i<midifile[0].size(); i++) { midifile[0][i].tick = maxtick - midifile[0][i].tick; } for (i=0; i<midifile[0].size(); i++) { if (!midifile[0][i].isNoteOn()) { continue; } if (midifile[0][i].isLinked()) { swapLinks(midifile[0][i]); } } midifile.splitTracks(); midifile.sortTracks(); } ////////////////////////////// // // doPitchMirror -- Mirror the pitch around a particular note. // If the pivot point has a fractional part, then use the space // between notes as the pivot rather than a partcular note. For // example if the pivot is 60, then 60->60, 61->59, 62->58, etc. // If the pivot is 60.5 (or anything than 60.0 up to 61.0), then the // pivot will be between 60 and 61: 60->61, 61->60, 62->59, etc. // If a note goes out of range, it will be mirrored again off of // the limits of the range. void doPitchMirror(MidiFile& midifile, double pivot) { vector<int> mirror; setMirror(mirror, pivot); for (int i=0; i<midifile.size(); i++) { for (int j=0; j<midifile[i].size(); j++) { if (!midifile[i][j].isNote()) { continue; } if (midifile[i][j].getChannel() == 9) { continue; } midifile[i][j].setKeyNumber(mirror[midifile[i][j][1]]); } } } ////////////////////////////// // // swapLinks -- Reverse the time order of two linked events. // void swapLinks(MidiEvent& event) { MidiEvent* thisnote = &event; MidiEvent* linknote = event.getLinkedEvent(); if (linknote == NULL) { return; } int temptick = thisnote->tick; thisnote->tick = linknote->tick; linknote->tick = temptick; } ////////////////////////////// // // setMirror -- Set the mapping from a pitch to its mirrored pitch. // void setMirror(vector<int>& mirror, double pivot) { mirror.resize(128); double fraction = pivot - (int)pivot; for (int i=0; i<mirror.size(); i++) { if (fraction > 0.0) { mirror[i] = (int)(4 * (int)pivot - 2 * i)/2; } else { mirror[i] = 2 * pivot - i; } // check for out of bounds (but only one cycle on each side). if (mirror[i] < 0) { mirror[i] = -mirror[i]; } if (mirror[i] > 127) { mirror[i] = 127 - mirror[i]; } } }
-
MidiEvent::getLinkedEvent — Returns a linked event (such as for note-on/note-off pair).
MidiEvent* getLinkedEvent(void);
Returns a linked event. Usually this is the note-off message for a note-on message and vice-versa. Returns NULL if there are no links.Source code for getLinkedEvent on GitHub
////////////////////////////// // // MidiEvent::getLinkedEvent -- Returns a linked event. Usually // this is the note-off message for a note-on message and vice-versa. // Returns null if there are no links. // MidiEvent* MidiEvent::getLinkedEvent(void) { return eventlink; }
-
MidiEvent::isLinked — Returns true of the message is linked to another event.
Returns true if there is an event which is not NULL. This function is similar to MidiEvent::getLinkedEvent().Source code for isLinked on GitHub
////////////////////////////// // // MidiEvent::isLinked -- Returns true if there is an event which is not // NULL. This function is similar to getLinkedEvent(). // int MidiEvent::isLinked(void) { return eventlink == NULL ? 0 : 1; }
-
MidiEventList::back — Access the last element in a list.
MidiEvent& back(void);
Access the last element in a list.Source code for back on GitHub
////////////////////////////// // // MidiEventList::back -- Return the last element in the list. // MidiEvent& MidiEventList::back(void) { return *list.back(); } // Alias for back: MidiEvent& MidiEventList::last(void) { return back(); }
-
MidiEventList::operator[] — Access a MidiEvent in the event list.
MidiEvent& operator[](int index);
Access a MidiEvent in the event list.Source code for operator[] on GitHub
////////////////////////////// // // MidiEventList::operator[] -- // MidiEvent& MidiEventList::operator[](int index) { return *list[index]; }
-
MidiEventList::getSize — Return the number of events in the list.
int getSize(void);
Return the number of events in the list.Source code for getSize on GitHub
////////////////////////////// // // MidiEventList::getSize -- // int MidiEventList::getSize(void) { return (int)list.size(); } int MidiEventList::size(void) { return getSize(); }
-
MidiFile::joinTracks — Merge all tracks into a single stream of events.
void joinTracks(void);
Merge the data from all tracks, but keep the identify of the tracks unique so that the function splitTracks() can be called to split the tracks into separate streams again. The style of the MidiFile when read from a file is with the tracks split.Source code for joinTracks on GitHub
////////////////////////////// // // 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) { return; } MidiEventList* joinedTrack; joinedTrack = new MidiEventList; int messagesum = 0; int length = getNumTracks(); int i, j; for (i=0; i<length; i++) { messagesum += (*events[i]).size(); } joinedTrack->reserve((int)(messagesum + 32 + messagesum * 0.1)); int oldTimeState = getTickState(); if (oldTimeState == TIME_STATE_DELTA) { absoluteTicks(); } for (i=0; i<length; i++) { for (j=0; j<(int)events[i]->size(); j++) { joinedTrack->push_back_no_copy(&(*events[i])[j]); } } clear_no_deallocate(); delete events[0]; events.resize(0); events.push_back(joinedTrack); sortTracks(); if (oldTimeState == TIME_STATE_DELTA) { deltaTicks(); } theTrackState = TRACK_STATE_JOINED; }
-
MidiFile::linkNotePairs — Link corresponding note-ons and note-offs.
int linkNotePairs(void);
Link corresponding note-ons and note-offs.Source code for linkNotePairs on GitHub
////////////////////////////// // // 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; i<getTrackCount(); i++) { if (events[i] == NULL) { continue; } sum += events[i]->linkNotePairs(); } return sum; }
-
MidiFile::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]; }
-
MidiFile::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:-
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; }
-
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; }
-
-
MidiFile::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(); }
-
MidiFile::sortTracks — Sort all tracks in the MidiFile by event timestamps.
void sortTracks(void);
Sort all tracks in the MidiFile by event timestamps.Source code for sortTracks on GitHub
////////////////////////////// // // MidiFile::sortTracks -- sort all tracks in the MidiFile. // void MidiFile::sortTracks(void) { for (int i=0; i<getTrackCount(); i++) { sortTrack(*events[i]); } }
-
MidiFile::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; }
-
MidiFile::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; }
-
MidiFile::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; }
-
MidiMessage::isNoteOn — Returns true if MIDI message is a note-on message.
int isNoteOn(void);
Returns true if MIDI message is a note-on message.Source code for isNoteOn on GitHub
////////////////////////////// // // MidiMessage::isNoteOn -- Returns true if the command byte is in the 0x90 // range and the velocity is non-zero // int MidiMessage::isNoteOn(void) { if (size() != 3) { return 0; } else if (((*this)[0] & 0xf0) != 0x90) { return 0; } else if ((*this)[2] == 0) { return 0; } else { return 1; } }
-
MidiMessage::operator[] — Returns the specified byte of a MIDI message.
uchar& operator[](int index);
Returns the specified byte of a MIDI message. The first byte will always be a command byte in the range from 0x80 to 0xff for the first byte of a valid MIDI message (or meta message). In other words, running status is not allowed in MidiMessage data. This function is inherited from the Standard Template Library class vector<uchar>. -
MidiMessage::setKeyNumber — Set the MIDI key number for notes and aftertouch.
void setKeyNumber(int size);
Set the MIDI key number for notes and aftertouch.Source code for setKeyNumber on GitHub
////////////////////////////// // // MidiMessage::setKeyNumber -- Set the note on/off key number (or // aftertouch key). Ignore if not note or aftertouch message. // Limits the input value to the range from 0 to 127. // void MidiMessage::setKeyNumber(int value) { if (isNote() || isAftertouch()) { setP1(value & 0xff); } else { // don't do anything since this is not a note-related message. } }
-
Options::getArg — Return an argument string after options have been filtered out.
const string& getArg(int index);
Returns the specified argument. The first argument is accessed by .getArg(1).Source code for getArg on GitHub
////////////////////////////// // // Options::getArg -- returns the specified argument. // argurment 0 is the command name. // const string& Options::getArg(int index) { if (index < 0 || index >= (int)argument.size()) { cerr << "Error: argument " << index << " does not exist." << endl; exit(1); } return argument[index]; } // Alias: const string& Options::getArgument(int index) { return getArg(index); }
-
Options::getArgCount — Return the number of arguments.
int getArgCount(void);
Return the number of arguments. This count excludes the command name, and filtered options.Source code for getArgCount on GitHub
////////////////////////////// // // Options::getArgCount -- number of arguments on command line. // does not count the options or the command name. // int Options::getArgCount(void) { return argument.size() - 1; } // Alias: int Options::getArgumentCount(void) { return getArgCount(); }
-
Options::getBoolean — Returns true if the option was set on the command-line.
int getBoolean(const string& option);
Returns true if the option was set on the command-line.Source code for getBoolean on GitHub
////////////////////////////// // // Options::getBoolean -- returns true if the option was // used on the command line. // int Options::getBoolean(const string& optionName) { int index = getRegIndex(optionName); if (index < 0) { return 0; } return optionRegister[index]->isModified(); }
-
Options::getDouble — Return option value as a double float.
double getDouble(const string& option);
Returns the double float associated with the given option. Returns 0 if there is no number associated with the option.Source code for getDouble on GitHub
////////////////////////////// // // Options::getDouble -- returns the double float associated // with the given option. Returns 0 if there is no // number associated with the option. // double Options::getDouble(const string& optionName) { return strtod(getString(optionName).c_str(), (char**)NULL); }
-
Options::process — Parse the command-line string to identify options and arguments.
void process(int argc, char** argv, int error_check, int suppress);
void process(int error_check, int suppress);
Parse the command-line string to identify options and arguments.Source code for process on GitHub
////////////////////////////// // // Options::process -- Same as xverify. // default values: error_check = 1, suppress = 0; // void Options::process(int argc, char** argv, int error_check, int suppress) { setOptions(argc, argv); xverify(error_check, suppress); } void Options::process(int error_check, int suppress) { xverify(error_check, suppress); }
-