Midifile View on GitHub

MIDI Mirror

home   »   documentation   »   tutorials

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 </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 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
    </details>