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
undefined
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
undefined
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
- 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
-