#include "MidiMessageSequence.h" #include "Pattern/MidiPattern.h" #include "Singleton/PortAudioAPI.h" #include "Thread/MainThreadEventList.h" #include "Math.h" #include "Thread/ThreadMessage.h" DECLARE_THREAD_MESSAGE(FMainThreadEventList, OnMidiPatternChanged, FMidiPattern* Pattern; FMidiMessageSequence* MidiMessageSequence; ) { Args.Pattern->NotifyOnChanged_MainThread(Args.MidiMessageSequence); } DECLARE_THREAD_MESSAGE(FPortAudioAPI, SortMidiMessageSequence, FMidiMessageSequence* MidiMessageSequence; FMidiPattern* Pattern; ) { Args.MidiMessageSequence->sort(); Args.MidiMessageSequence->updateMatchedPairs(); Args.Pattern->NotifyOnChanged(Args.MidiMessageSequence); PUSH_THREAD_EVENT(OnMidiPatternChanged, Args.Pattern, Args.MidiMessageSequence); } FMidiMessageSequence::MidiEventHolder::MidiEventHolder (const FMidiMessage& mm) : message (mm) {} FMidiMessageSequence::MidiEventHolder::MidiEventHolder (FMidiMessage&& mm) : message (std::move (mm)) {} //============================================================================== FMidiMessageSequence::FMidiMessageSequence() { } FMidiMessageSequence::FMidiMessageSequence (const FMidiMessageSequence& other) { list.Append(other.list); for (int i = 0; i < list.Num(); ++i) { auto noteOffIndex = other.getIndexOfMatchingKeyUp (i); if (noteOffIndex >= 0) list.GetData()[i]->noteOffObject = list.GetData()[noteOffIndex]; } } FMidiMessageSequence& FMidiMessageSequence::operator= (const FMidiMessageSequence& other) { FMidiMessageSequence otherCopy (other); swapWith (otherCopy); return *this; } FMidiMessageSequence::FMidiMessageSequence (FMidiMessageSequence&& other) noexcept : list (std::move (other.list)) { } FMidiMessageSequence& FMidiMessageSequence::operator= (FMidiMessageSequence&& other) noexcept { list = std::move (other.list); return *this; } void FMidiMessageSequence::swapWith (FMidiMessageSequence& other) noexcept { Swap(list, other.list); } TRange FMidiMessageSequence::GetNoteRange() const { int32 MinNote = 127; int32 MaxNote = 0; for (const auto& MidiEventHolder : list) { const FMidiMessage& MidiMessage = MidiEventHolder->message; if (MidiMessage.isNoteOn()) { const int32 Note = MidiMessage.getNoteNumber(); MinNote = FMath::Min(MinNote, Note); MaxNote = FMath::Max(MaxNote, Note); } } return TRange(MinNote, MaxNote); } void FMidiMessageSequence::clear() { list.Empty(); } int FMidiMessageSequence::getNumEvents() const noexcept { return list.Num(); } FMidiMessageSequence::MidiEventHolder* FMidiMessageSequence::getEventPointer (int index) const noexcept { return list[index]; } FMidiMessageSequence::MidiEventHolder** FMidiMessageSequence::begin() noexcept { return list.GetData(); } FMidiMessageSequence::MidiEventHolder* const* FMidiMessageSequence::begin() const noexcept { return list.GetData(); } FMidiMessageSequence::MidiEventHolder** FMidiMessageSequence::end() noexcept { return list.GetData() + list.Num(); } FMidiMessageSequence::MidiEventHolder* const* FMidiMessageSequence::end() const noexcept { return list.GetData() + list.Num(); } double FMidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept { if (!list.IsValidIndex(index)) return 0; auto* meh = list[index]; if (auto* noteOff = meh->noteOffObject) return noteOff->message.getTimeStamp(); return 0; } int FMidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept { if (!list.IsValidIndex(index)) return -1; auto* meh = list[index]; if (auto* noteOff = meh->noteOffObject) { for (int i = index; i < list.Num(); ++i) if (list.GetData()[i] == noteOff) return i; check(0); // we've somehow got a pointer to a note-off object that isn't in the sequence } return -1; } int FMidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept { return list.Find(const_cast(event)); } int FMidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept { auto numEvents = list.Num(); int i; for (i = 0; i < numEvents; ++i) if (list.GetData()[i]->message.getTimeStamp() >= timeStamp) break; return i; } //============================================================================== double FMidiMessageSequence::getStartTime() const noexcept { return getEventTime (0); } double FMidiMessageSequence::getEndTime() const noexcept { return getEventTime (list.Num() - 1); } double FMidiMessageSequence::getEventTime (const int index) const noexcept { if (list.IsValidIndex(index)) return list[index]->message.getTimeStamp(); return 0; } //============================================================================== FMidiMessageSequence::MidiEventHolder* FMidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment) { newEvent->message.addToTimeStamp (timeAdjustment); auto time = newEvent->message.getTimeStamp(); int i; for (i = list.Num(); --i >= 0;) if (list.GetData()[i]->message.getTimeStamp() <= time) break; list.Insert(newEvent, i + 1); return newEvent; } FMidiMessageSequence::MidiEventHolder* FMidiMessageSequence::addEvent (const FMidiMessage& newMessage, double timeAdjustment) { return addEvent (new MidiEventHolder (newMessage), timeAdjustment); } FMidiMessageSequence::MidiEventHolder* FMidiMessageSequence::addEvent (FMidiMessage&& newMessage, double timeAdjustment) { return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment); } void FMidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp) { if (IsPositiveAndBelow(index, list.Num())) { if (deleteMatchingNoteUp) deleteEvent (getIndexOfMatchingKeyUp (index), false); list.RemoveAt(index); } } void FMidiMessageSequence::addSequence (const FMidiMessageSequence& other, double timeAdjustment) { for (auto* m : other) { auto newOne = new MidiEventHolder (m->message); newOne->message.addToTimeStamp (timeAdjustment); list.Add(newOne); } sort(); } void FMidiMessageSequence::addSequence (const FMidiMessageSequence& other, double timeAdjustment, double firstAllowableTime, double endOfAllowableDestTimes) { for (auto* m : other) { auto t = m->message.getTimeStamp() + timeAdjustment; if (t >= firstAllowableTime && t < endOfAllowableDestTimes) { auto newOne = new MidiEventHolder (m->message); newOne->message.setTimeStamp (t); list.Add(newOne); } } sort(); } void FMidiMessageSequence::sort() noexcept { Algo::StableSort(list, [](const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); }); } void FMidiMessageSequence::sortAudioThread() { PUSH_THREAD_EVENT(SortMidiMessageSequence, this, pattern); } void FMidiMessageSequence::updateMatchedPairs() noexcept { for (int i = 0; i < list.Num(); ++i) { auto* meh = list.GetData()[i]; auto& m1 = meh->message; if (m1.isNoteOn()) { meh->noteOffObject = nullptr; auto note = m1.getNoteNumber(); auto chan = m1.getChannel(); auto len = list.Num(); for (int j = i + 1; j < len; ++j) { auto* meh2 = list.GetData()[j]; auto& m = meh2->message; if (m.getNoteNumber() == note && m.getChannel() == chan) { if (m.isNoteOff()) { meh->noteOffObject = meh2; break; } if (m.isNoteOn()) { auto newEvent = new MidiEventHolder(FMidiMessage::noteOff(chan, note)); list.Insert(newEvent, j); newEvent->message.setTimeStamp (m.getTimeStamp()); meh->noteOffObject = newEvent; break; } } } } } } void FMidiMessageSequence::addTimeToMessages (double delta) noexcept { if (delta != 0) for (auto* m : list) m->message.addToTimeStamp (delta); } //============================================================================== void FMidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, FMidiMessageSequence& destSequence, const bool alsoIncludeMetaEvents) const { for (auto* meh : list) if (meh->message.isForChannel (channelNumberToExtract) || (alsoIncludeMetaEvents && meh->message.isMetaEvent())) destSequence.addEvent (meh->message); } void FMidiMessageSequence::extractSysExMessages (FMidiMessageSequence& destSequence) const { for (auto* meh : list) if (meh->message.isSysEx()) destSequence.addEvent (meh->message); } void FMidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) { for (int i = list.Num(); --i >= 0;) if (list.GetData()[i]->message.isForChannel (channelNumberToRemove)) list.RemoveAt(i); } void FMidiMessageSequence::deleteSysExMessages() { for (int i = list.Num(); --i >= 0;) if (list.GetData()[i]->message.isSysEx()) list.RemoveAt(i); } //============================================================================== class OptionalPitchWheel { TOptional value; public: void emit (int channel, TArray& out) const { if (value.IsSet()) out.Add(FMidiMessage::pitchWheel (channel, *value)); } void set (int v) { value = v; } }; class OptionalControllerValues { TOptional values[128]; public: void emit (int channel, TArray& out) const { for (auto it = std::begin (values); it != std::end (values); ++it) if (it->IsSet()) out.Add(FMidiMessage::controllerEvent (channel, (int) std::distance (std::begin (values), it), **it)); } void set (int controller, int value) { values[controller] = (char) value; } }; class OptionalProgramChange { TOptional value, bankLSB, bankMSB; public: void emit (int channel, double time, TArray& out) const { if (! value.IsSet()) return; if (bankLSB.IsSet() && bankMSB.IsSet()) { out.Add(FMidiMessage::controllerEvent (channel, 0x00, *bankMSB).withTimeStamp (time)); out.Add(FMidiMessage::controllerEvent (channel, 0x20, *bankLSB).withTimeStamp (time)); } out.Add(FMidiMessage::programChange (channel, *value).withTimeStamp (time)); } // Returns true if this is a bank number change, and false otherwise. bool trySetBank (int controller, int v) { switch (controller) { case 0x00: bankMSB = (char) v; return true; case 0x20: bankLSB = (char) v; return true; } return false; } void setProgram (int v) { value = (char) v; } }; class ParameterNumberState { enum class Kind { rpn, nrpn }; TOptional newestRpnLsb, newestRpnMsb, newestNrpnLsb, newestNrpnMsb, lastSentLsb, lastSentMsb; Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn; public: // If the effective parameter number has changed since the last time this function was called, // this will emit the current parameter in full (MSB and LSB). // This should be called before each data message (entry, increment, decrement: 0x06, 0x26, 0x60, 0x61) // to ensure that the data message operates on the correct parameter number. void sendIfNecessary (int channel, double time, TArray& out) { const auto newestMsb = newestKind == Kind::rpn ? newestRpnMsb : newestNrpnMsb; const auto newestLsb = newestKind == Kind::rpn ? newestRpnLsb : newestNrpnLsb; auto lastSent = std::tie (lastSentKind, lastSentMsb, lastSentLsb); const auto newest = std::tie (newestKind, newestMsb, newestLsb); if (lastSent == newest || ! newestMsb.IsSet() || ! newestLsb.IsSet()) return; out.Add(FMidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x65 : 0x63, *newestMsb).withTimeStamp (time)); out.Add(FMidiMessage::controllerEvent (channel, newestKind == Kind::rpn ? 0x64 : 0x62, *newestLsb).withTimeStamp (time)); lastSent = newest; } // Returns true if this is a parameter number change, and false otherwise. bool trySetProgramNumber (int controller, int value) { switch (controller) { case 0x65: newestRpnMsb = (char) value; newestKind = Kind::rpn; return true; case 0x64: newestRpnLsb = (char) value; newestKind = Kind::rpn; return true; case 0x63: newestNrpnMsb = (char) value; newestKind = Kind::nrpn; return true; case 0x62: newestNrpnLsb = (char) value; newestKind = Kind::nrpn; return true; } return false; } }; void FMidiMessageSequence::createControllerUpdatesForTime(int channel, double time, TArray& dest) { OptionalProgramChange programChange; OptionalControllerValues controllers; OptionalPitchWheel pitchWheel; ParameterNumberState parameterNumberState; for (const auto& item : list) { const auto& mm = item->message; if (! (mm.isForChannel (channel) && mm.getTimeStamp() <= time)) continue; if (mm.isController()) { const auto num = mm.getControllerNumber(); if (parameterNumberState.trySetProgramNumber (num, mm.getControllerValue())) continue; if (programChange.trySetBank (num, mm.getControllerValue())) continue; constexpr int passthroughs[] { 0x06, 0x26, 0x60, 0x61 }; if (std::find (std::begin (passthroughs), std::end (passthroughs), num) != std::end (passthroughs)) { parameterNumberState.sendIfNecessary (channel, mm.getTimeStamp(), dest); dest.Add(mm); } else { controllers.set (num, mm.getControllerValue()); } } else if (mm.isProgramChange()) { programChange.setProgram (mm.getProgramChangeNumber()); } else if (mm.isPitchWheel()) { pitchWheel.set (mm.getPitchWheelValue()); } } pitchWheel.emit (channel, dest); controllers.emit (channel, dest); // Also emits bank change messages if necessary. programChange.emit (channel, time, dest); // Set the parameter number to its final state. parameterNumberState.sendIfNecessary (channel, time, dest); }