Midifile View on GitHub

programming tutorials

home   »   documentation

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;
}

Library functions used in this example:

  • MidiFile::read
  • MidiFile::status
  • MidiFile::write
  • Options::getArg
  • Options::getArgCount
  • Options::getCommand
  • Options::process

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;
}

Library functions used in this example:

  • MidiFile::getEventCount
  • MidiFile::getTrackCount
  • MidiFile::read
  • MidiFile::status
  • MidiFile::write
  • MidiMessage::isNoteOff
  • MidiMessage::setCommandNibble
  • Options::define
  • Options::getArg
  • Options::getArgCount
  • Options::getBoolean
  • Options::getCommand
  • Options::process

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;
   }
}

Library functions used in this example:

  • MidiEventList::operator[]
  • MidiFile::absoluteTime
  • MidiFile::joinTracks
  • MidiFile::operator[]
  • MidiFile::read
  • MidiFile::size
  • MidiFile::status
  • MidiMessage::isNoteOn
  • MidiMessage::operator[]
  • Options::getArg
  • Options::getArgCount
  • Options::process

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);
}

Library functions used in this example:

  • MidiEvent::getLinkedEvent
  • MidiEvent::isLinked
  • MidiEventList::operator[]
  • MidiFile::getEventCount
  • MidiFile::getTrackCount
  • MidiFile::linkNotePairs
  • MidiFile::operator[]
  • MidiFile::read
  • MidiFile::size
  • MidiFile::status
  • MidiFile::write
  • MidiMessage::getKeyNumber
  • MidiMessage::isNoteOn
  • MidiMessage::setKeyNumber
  • Options::getArg
  • Options::getArgCount
  • Options::process

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:

  1. Original MIDI file
  2. Retrograde
  3. Inverted pitch around middle-C
  4. Retrograde Inversion

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 sortTracks 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 setKeyNumber function has built in range checking which prevents values other than 0–127 from incorrectly being inserted into the MIDI 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
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];
      }
   }
}

Library functions used in this example:

  • MidiEvent::getLinkedEvent
  • MidiEvent::isLinked
  • MidiEventList::back
  • MidiEventList::operator[]
  • MidiEventList::size
  • MidiFile::joinTracks
  • MidiFile::linkNotePairs
  • MidiFile::operator[]
  • MidiFile::read
  • MidiFile::size
  • MidiFile::sortTracks
  • MidiFile::splitTracks
  • MidiFile::status
  • MidiFile::write
  • MidiMessage::isNoteOn
  • MidiMessage::operator[]
  • MidiMessage::setKeyNumber
  • Options::getArg
  • Options::getArgCount
  • Options::getBoolean
  • Options::getDouble
  • Options::process