diff --git a/include/midi/MidiEvent.h b/include/midi/MidiEvent.h index f8141d17bba..3f3df9a8e45 100644 --- a/include/midi/MidiEvent.h +++ b/include/midi/MidiEvent.h @@ -51,6 +51,7 @@ class MidiEvent : public MidiMessage { void linkEvent (MidiEvent& mev); void linkEvents (MidiEvent& mev); int isLinked (void) const; + int hasLink (void) const { return isLinked(); } MidiEvent* getLinkedEvent (void); const MidiEvent* getLinkedEvent (void) const; int getTickDuration (void) const; diff --git a/include/midi/MidiEventList.h b/include/midi/MidiEventList.h index f9b1e8538e2..006cc4b9f9b 100644 --- a/include/midi/MidiEventList.h +++ b/include/midi/MidiEventList.h @@ -44,7 +44,9 @@ class MidiEventList { int getSize (void) const; int size (void) const; void removeEmpties (void); - int linkNotePairs (void); + int linkNotePairsFIFO (void); + int linkNotePairsLIFO (void); + int linkNotePairs (void) { return linkNotePairsFIFO(); } int linkEventPairs (void); void clearLinks (void); void clearSequence (void); @@ -65,14 +67,18 @@ class MidiEventList { std::vector list; private: - void sort (void); + void sort (void) { return sortNoteOnsBeforeOffs(); } + void sortNoteOnsBeforeOffs (void); + void sortNoteOffsBeforeOns (void); // MidiFile class calls sort() friend class MidiFile; -}; + static int eventCompareNoteOffsBeforeOns(const void* a, const void* b); + static int eventCompareNoteOnsBeforeOffs(const void* a, const void* b); + static int eventCompare(const void* a, const void* b) { return eventCompareNoteOnsBeforeOffs(a, b); } +}; -int eventcompare(const void* a, const void* b); } // end of namespace smf diff --git a/include/midi/MidiFile.h b/include/midi/MidiFile.h index 4972218f627..bb7ca9e894b 100644 --- a/include/midi/MidiFile.h +++ b/include/midi/MidiFile.h @@ -107,12 +107,16 @@ class MidiFile { int getSplitTrack (int index) const; // track sorting funcionality: - void sortTrack (int track); - void sortTracks (void); - void markSequence (void); - void markSequence (int track, int sequence = 1); - void clearSequence (void); - void clearSequence (int track); + void sortTrack (int track) { sortTrackNoteOnsBeforeOffs(track); } + void sortTrackNoteOnsBeforeOffs (int track); + void sortTrackNoteOffsBeforeOns (int track); + void sortTracks (void) { sortTracksNoteOnsBeforeOffs(); } + void sortTracksNoteOnsBeforeOffs(void); + void sortTracksNoteOffsBeforeOns(void); + void markSequence (void); + void markSequence (int track, int sequence = 1); + void clearSequence (void); + void clearSequence (int track); // track manipulation functionality: int addTrack (void); @@ -139,7 +143,9 @@ class MidiFile { double getFileDurationInSeconds (void); // note-analysis functions: - int linkNotePairs (void); + int linkNotePairsFIFO (void); + int linkNotePairsLIFO (void); + int linkNotePairs (void) { return linkNotePairsFIFO(); } int linkEventPairs (void); void clearLinks (void); diff --git a/include/midi/MidiMessage.h b/include/midi/MidiMessage.h index b298e15b9a0..5e31a95e066 100644 --- a/include/midi/MidiMessage.h +++ b/include/midi/MidiMessage.h @@ -173,7 +173,7 @@ class MidiMessage : public std::vector { bool isKeySignature (void) const; bool isEndOfTrack (void) const; - std::string getMetaContent (void); + std::string getMetaContent (void) const; void setMetaContent (const std::string& content); void setTempo (double tempo); void setTempoMicroseconds (int microseconds); diff --git a/src/midi/MidiEventList.cpp b/src/midi/MidiEventList.cpp index f38fefbc2ca..3c035d04c39 100644 --- a/src/midi/MidiEventList.cpp +++ b/src/midi/MidiEventList.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -279,11 +280,103 @@ void MidiEventList::removeEmpties(void) { // int MidiEventList::linkEventPairs(void) { - return linkNotePairs(); + return linkNotePairsFIFO(); } -int MidiEventList::linkNotePairs(void) { +int MidiEventList::linkNotePairsFIFO(void) { + // Note-on states: + // dimension 1: MIDI channel (0-15) + // dimension 2: MIDI key (0-127) (but 0 not used for note-ons) + // dimension 3: List of active note-ons or note-offs (FIFO behavior). + std::vector>> noteons; + noteons.resize(16); + for (auto& noteon : noteons) { + noteon.resize(128); + } + + // Controller linking: The following General MIDI controller numbers are + // also monitored for linking within the track (but not between tracks). + // hex dec name range + // 40 64 Hold pedal (Sustain) on/off 0..63=off 64..127=on + // 41 65 Portamento on/off 0..63=off 64..127=on + // 42 66 Sustenuto Pedal on/off 0..63=off 64..127=on + // 43 67 Soft Pedal on/off 0..63=off 64..127=on + // 44 68 Legato Pedal on/off 0..63=off 64..127=on + // 45 69 Hold Pedal 2 on/off 0..63=off 64..127=on + // 50 80 General Purpose Button 0..63=off 64..127=on + // 51 81 General Purpose Button 0..63=off 64..127=on + // 52 82 General Purpose Button 0..63=off 64..127=on + // 53 83 General Purpose Button 0..63=off 64..127=on + // 54 84 Undefined on/off 0..63=off 64..127=on + // 55 85 Undefined on/off 0..63=off 64..127=on + // 56 86 Undefined on/off 0..63=off 64..127=on + // 57 87 Undefined on/off 0..63=off 64..127=on + // 58 88 Undefined on/off 0..63=off 64..127=on + // 59 89 Undefined on/off 0..63=off 64..127=on + // 5A 90 Undefined on/off 0..63=off 64..127=on + // 7A 122 Local Keyboard On/Off 0..63=off 64..127=on + + // first keep track of whether the controller is an on/off switch: + std::vector> contmap(128, {0, 0}); + contmap[64] = {1, 0}; contmap[65] = {1, 1}; contmap[66] = {1, 2}; + contmap[67] = {1, 3}; contmap[68] = {1, 4}; contmap[69] = {1, 5}; + contmap[80] = {1, 6}; contmap[81] = {1, 7}; contmap[82] = {1, 8}; + contmap[83] = {1, 9}; contmap[84] = {1, 10}; contmap[85] = {1, 11}; + contmap[86] = {1, 12}; contmap[87] = {1, 13}; contmap[88] = {1, 14}; + contmap[89] = {1, 15}; contmap[90] = {1, 16}; contmap[122] = {1, 17}; + + std::vector> contevents(18, std::vector(16, nullptr)); + std::vector> oldstates(18, std::vector(16, -1)); + + int counter = 0; + for (int i = 0; i < getSize(); i++) { + MidiEvent* mev = &getEvent(i); + mev->unlinkEvent(); + + if (mev->isNoteOn()) { + int key = mev->getKeyNumber(); + int channel = mev->getChannel(); + noteons[channel][key].push_back(mev); // Enqueue (FIFO) + } else if (mev->isNoteOff()) { + int key = mev->getKeyNumber(); + int channel = mev->getChannel(); + if (!noteons[channel][key].empty()) { // **Check before accessing** + MidiEvent* noteon = noteons[channel][key].front(); // Safely access first event + noteons[channel][key].pop_front(); // Remove first event (FIFO) + noteon->linkEvent(mev); + counter++; + } + } else if (mev->isController()) { + int contnum = mev->getP1(); + if (contmap[contnum].first) { + int conti = contmap[contnum].second; + int channel = mev->getChannel(); + int contval = mev->getP2(); + int contstate = contval < 64 ? 0 : 1; + + if ((oldstates[conti][channel] == -1) && contstate) { + contevents[conti][channel] = mev; + oldstates[conti][channel] = contstate; + } else if (oldstates[conti][channel] == contstate) { + // Redundant controller state, ignore. + } else if ((oldstates[conti][channel] == 0) && contstate) { + contevents[conti][channel] = mev; + oldstates[conti][channel] = contstate; + } else if ((oldstates[conti][channel] == 1) && (contstate == 0)) { + contevents[conti][channel]->linkEvent(mev); + oldstates[conti][channel] = contstate; + contevents[conti][channel] = mev; + } + } + } + } + return counter; +} + + + +int MidiEventList::linkNotePairsLIFO(void) { // Note-on states: // dimension 1: MIDI channel (0-15) @@ -526,8 +619,12 @@ MidiEventList& MidiEventList::operator=(MidiEventList& other) { // does not know about delta/absolute tick states of its contents). // -void MidiEventList::sort(void) { - qsort(data(), getEventCount(), sizeof(MidiEvent*), eventcompare); +void MidiEventList::sortNoteOnsBeforeOffs(void) { + qsort(data(), getEventCount(), sizeof(MidiEvent*), MidiEventList::eventCompareNoteOnsBeforeOffs); +} + +void MidiEventList::sortNoteOffsBeforeOns(void) { + qsort(data(), getEventCount(), sizeof(MidiEvent*), MidiEventList::eventCompareNoteOnsBeforeOffs); } @@ -539,7 +636,7 @@ void MidiEventList::sort(void) { ////////////////////////////// // -// eventcompare -- Event comparison function for sorting tracks. +// MidiEventList::eventCompare -- Event comparison function for sorting tracks. // // Sorting rules: // (1) sort by (absolute) tick value; otherwise, if tick values are the same: @@ -548,31 +645,130 @@ void MidiEventList::sort(void) { // (4) note-offs come after all other regular MIDI messages except note-ons. // (5) note-ons come after all other regular MIDI messages. // +// Note: If you load a MIDI file from a file, MidiMessage.seq numbers +// will automatically be added, and sorting will follow the sequence +// numbers. Use MidiFile::clearSequence() if you want to sort the +// MIDI events (occuring at the same time) using the rest of the sorting +// algorithm (such as to place note-offs before note-ons when they +// occur at the same time). +// -int eventcompare(const void* a, const void* b) { +int MidiEventList::eventCompareNoteOnsBeforeOffs(const void* a, const void* b) { MidiEvent& aevent = **((MidiEvent**)a); MidiEvent& bevent = **((MidiEvent**)b); - if (aevent.tick > bevent.tick) { + if (aevent.tick < bevent.tick) { + // aevent occurs before bevent + return -1; + } else if (aevent.tick > bevent.tick) { // aevent occurs after bevent return +1; - } else if (aevent.tick < bevent.tick) { - // aevent occurs before bevent + } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq < bevent.seq)) { + // aevent sequencing state occurs before bevent + // see MidiEventList::markSequence() return -1; } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq > bevent.seq)) { // aevent sequencing state occurs after bevent // see MidiEventList::markSequence() return +1; + } else if ((aevent.getP0() == 0xff) && (aevent.getP1() == 0x2f)) { + // end-of-track meta-message should always be last (but this won't really + // matter since the writing function ignores all end-of-track messages + // and writes its own. + return +1; + } else if ((bevent.getP0() == 0xff) && (bevent.getP1() == 0x2f)) { + // end-of-track meta-message should always be last (but won't really + // matter since the writing function ignores all end-of-track messages + // and writes its own. + return -1; + } else if (aevent.getP0() == 0xff && bevent.getP0() != 0xff) { + // other meta-messages are placed before real MIDI messages + return -1; + } else if ((aevent.getP0() != 0xff) && (bevent.getP0() == 0xff)) { + // other meta-messages are placed before real MIDI messages + return +1; + } else if ((aevent.getP0() == 0xff) && (bevent.getP0() == 0xff)) { + // could sort by meta-message number here + return 0; + } else if (aevent.isNote() && !bevent.isNote()) { + // notes come after all other message types + return +1; + } else if (!aevent.isNote() && bevent.isNote()) { + // notes come after all other message types + return -1; + } else if (aevent.isNoteOff() && bevent.isNoteOn()) { + // note-offs come after note-ons + return +1; + } else if (aevent.isNoteOn() && bevent.isNoteOff()) { + // note-ons come before all other types of MIDI messages + return -1; + } else if (aevent.isNoteOn() && bevent.isNoteOn()) { + // sorted further by key number + if (aevent.getP1() < bevent.getP1()) { + return -1; + } else if (aevent.getP1() > bevent.getP1()) { + return +1; + } else { + return 0; + } + return 0; + } else if (aevent.isNoteOff() && bevent.isNoteOff()) { + // sorted further by key number + if (aevent.getP1() < bevent.getP1()) { + return -1; + } else if (aevent.getP1() > bevent.getP1()) { + return +1; + } else { + return 0; + } + // could be sorted further by key number. + return 0; + } else if (((aevent.getP0() & 0xf0) == 0xb0) && ((bevent.getP0() & 0xf0) == 0xb0)) { + // both events are continuous controllers. Sort them by controller number and value if equal + if (aevent.getP1() > bevent.getP1()) { + return +1; + } if (aevent.getP1() < bevent.getP1()) { + return -1; + } else { + // same controller number, so sort by data value + // useful for sustain pedalling, for example. + if (aevent.getP2() > bevent.getP2()) { + return +1; + } if (aevent.getP2() < bevent.getP2()) { + return -1; + } else { + return 0; + } + } + } else { + return 0; + } +} + +int MidiEventList::eventCompareNoteOffsBeforeOns(const void* a, const void* b) { + MidiEvent& aevent = **((MidiEvent**)a); + MidiEvent& bevent = **((MidiEvent**)b); + + if (aevent.tick < bevent.tick) { + // aevent occurs before bevent + return -1; + } else if (aevent.tick > bevent.tick) { + // aevent occurs after bevent + return +1; } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq < bevent.seq)) { // aevent sequencing state occurs before bevent // see MidiEventList::markSequence() return -1; - } else if (aevent.getP0() == 0xff && aevent.getP1() == 0x2f) { - // end-of-track meta-message should always be last (but won't really + } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq > bevent.seq)) { + // aevent sequencing state occurs after bevent + // see MidiEventList::markSequence() + return +1; + } else if ((aevent.getP0() == 0xff) && (aevent.getP1() == 0x2f)) { + // end-of-track meta-message should always be last (but this won't really // matter since the writing function ignores all end-of-track messages // and writes its own. return +1; - } else if (bevent.getP0() == 0xff && bevent.getP1() == 0x2f) { + } else if ((bevent.getP0() == 0xff) && (bevent.getP1() == 0x2f)) { // end-of-track meta-message should always be last (but won't really // matter since the writing function ignores all end-of-track messages // and writes its own. @@ -580,29 +776,54 @@ int eventcompare(const void* a, const void* b) { } else if (aevent.getP0() == 0xff && bevent.getP0() != 0xff) { // other meta-messages are placed before real MIDI messages return -1; - } else if (aevent.getP0() != 0xff && bevent.getP0() == 0xff) { + } else if ((aevent.getP0() != 0xff) && (bevent.getP0() == 0xff)) { // other meta-messages are placed before real MIDI messages return +1; - } else if (((aevent.getP0() & 0xf0) == 0x90) && (aevent.getP2() != 0)) { - // note-ons come after all other types of MIDI messages + } else if ((aevent.getP0() == 0xff) && (bevent.getP0() == 0xff)) { + // could sort by meta-message number here + return 0; + } else if (aevent.isNote() && !bevent.isNote()) { + // notes come after all other message types return +1; - } else if (((bevent.getP0() & 0xf0) == 0x90) && (bevent.getP2() != 0)) { - // note-ons come after all other types of MIDI messages + } else if (!aevent.isNote() && bevent.isNote()) { + // notes come after all other message types return -1; - } else if (((aevent.getP0() & 0xf0) == 0x90) || ((aevent.getP0() & 0xf0) == 0x80)) { - // note-offs come after all other MIDI messages (except note-ons) - return +1; - } else if (((bevent.getP0() & 0xf0) == 0x90) || ((bevent.getP0() & 0xf0) == 0x80)) { - // note-offs come after all other MIDI messages (except note-ons) + } else if (aevent.isNoteOff() && bevent.isNoteOn()) { + // note-offs come before note-ons return -1; + } else if (aevent.isNoteOn() && bevent.isNoteOff()) { + // note-ons come after all other types of MIDI messages + return +1; + } else if (aevent.isNoteOn() && bevent.isNoteOn()) { + // sorted further by key number + if (aevent.getP1() < bevent.getP1()) { + return -1; + } else if (aevent.getP1() > bevent.getP1()) { + return +1; + } else { + return 0; + } + return 0; + } else if (aevent.isNoteOff() && bevent.isNoteOff()) { + // sorted further by key number + if (aevent.getP1() < bevent.getP1()) { + return -1; + } else if (aevent.getP1() > bevent.getP1()) { + return +1; + } else { + return 0; + } + // could be sorted further by key number. + return 0; } else if (((aevent.getP0() & 0xf0) == 0xb0) && ((bevent.getP0() & 0xf0) == 0xb0)) { - // both events are continuous controllers. Sort them by controller number + // both events are continuous controllers. Sort them by controller number and value if equal if (aevent.getP1() > bevent.getP1()) { return +1; } if (aevent.getP1() < bevent.getP1()) { return -1; } else { // same controller number, so sort by data value + // useful for sustain pedalling, for example. if (aevent.getP2() > bevent.getP2()) { return +1; } if (aevent.getP2() < bevent.getP2()) { diff --git a/src/midi/MidiFile.cpp b/src/midi/MidiFile.cpp index 55956c8ce79..3236558e9b8 100644 --- a/src/midi/MidiFile.cpp +++ b/src/midi/MidiFile.cpp @@ -1574,14 +1574,28 @@ double MidiFile::getAbsoluteTickTime(double starttime) { // that were linked. // -int MidiFile::linkNotePairs(void) { +int MidiFile::linkNotePairsFIFO(void) { int i; int sum = 0; for (i=0; ilinkNotePairs(); + sum += m_events[i]->linkNotePairsFIFO(); + } + m_linkedEventsQ = true; + return sum; +} + + +int MidiFile::linkNotePairsLIFO(void) { + int i; + int sum = 0; + for (i=0; ilinkNotePairsLIFO(); } m_linkedEventsQ = true; return sum; @@ -1592,7 +1606,7 @@ int MidiFile::linkNotePairs(void) { // int MidiFile::linkEventPairs(void) { - return linkNotePairs(); + return linkNotePairsFIFO(); } @@ -2477,9 +2491,18 @@ void MidiFile::setMillisecondTicks(void) { // was done. // -void MidiFile::sortTrack(int track) { + +void MidiFile::sortTrackNoteOnsBeforeOffs(int track) { if ((track >= 0) && (track < getTrackCount())) { - m_events.at(track)->sort(); + m_events.at(track)->sortNoteOnsBeforeOffs(); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + +void MidiFile::sortTrackNoteOffsBeforeOns(int track) { + if ((track >= 0) && (track < getTrackCount())) { + m_events.at(track)->sortNoteOffsBeforeOns(); } else { std::cerr << "Warning: track " << track << " does not exist." << std::endl; } @@ -2492,10 +2515,20 @@ void MidiFile::sortTrack(int track) { // MidiFile::sortTracks -- sort all tracks in the MidiFile. // -void MidiFile::sortTracks(void) { +void MidiFile::sortTracksNoteOnsBeforeOffs(void) { + if (m_theTimeState == TIME_STATE_ABSOLUTE) { + for (int i=0; isortNoteOnsBeforeOffs(); + } + } else { + std::cerr << "Warning: Sorting only allowed in absolute tick mode."; + } +} + +void MidiFile::sortTracksNoteOffsBeforeOns(void) { if (m_theTimeState == TIME_STATE_ABSOLUTE) { for (int i=0; isort(); + m_events.at(i)->sortNoteOffsBeforeOns(); } } else { std::cerr << "Warning: Sorting only allowed in absolute tick mode."; diff --git a/src/midi/MidiMessage.cpp b/src/midi/MidiMessage.cpp index 5b19a36eb8a..aa84a194c6e 100644 --- a/src/midi/MidiMessage.cpp +++ b/src/midi/MidiMessage.cpp @@ -1008,7 +1008,7 @@ void MidiMessage::setP3(int value) { void MidiMessage::setKeyNumber(int value) { if (isNote() || isAftertouch()) { - setP1(value & 0xff); + setP1(value & 0x7f); } else { // don't do anything since this is not a note-related message. } @@ -1025,7 +1025,7 @@ void MidiMessage::setKeyNumber(int value) { void MidiMessage::setVelocity(int value) { if (isNote()) { - setP2(value & 0xff); + setP2(value & 0x7f); } else { // don't do anything since this is not a note-related message. } @@ -1495,7 +1495,7 @@ void MidiMessage::getSpelling(int& base7, int& accidental) { // message after the length (which is a variable-length-value). // -std::string MidiMessage::getMetaContent(void) { +std::string MidiMessage::getMetaContent(void) const { std::string output; if (!isMetaMessage()) { return output;