13#define TRACKTION_SANITY_CHECK_MIDI_BUFFERS 0
15namespace tracktion {
inline namespace engine
23 LiveClipLevel liveClipLevel,
24 ProcessState& processStateToUse,
25 EditItemID editItemIDToUse,
27 : TracktionEngineNode (processStateToUse),
30 channelNumbers (midiChannelNumbers),
31 useMPEChannelMode (useMPE),
32 editRange (editTimeRange),
33 clipLevel (liveClipLevel),
34 editItemID (editItemIDToUse),
35 shouldBeMutedDelegate (
std::
move (shouldBeMuted)),
36 wasMute (liveClipLevel.isMute())
39 jassert (channelNumbers.getStart() > 0 && (channelNumbers.getEnd() - 1) <= 16);
42 s.updateMatchedPairs();
44 controllerMessagesScratchBuffer.ensureStorageAllocated (32);
51 props.nodeID = (
size_t) editItemID.getRawID();
57 sampleRate = info.sampleRate;
59 if (info.nodeGraphToReplace !=
nullptr)
60 shouldCreateMessagesForTime = findNodeWithID<MidiNode> (*info.nodeGraphToReplace, getNodeProperties().nodeID) ==
nullptr;
63bool MidiNode::isReadyToProcess()
71 const auto timelineRange = getTimelineSampleRange();
73 if (timelineRange.isEmpty())
76 if (shouldBeMutedDelegate && shouldBeMutedDelegate())
79 if (ms.size() > 0 && timelineRange.getStart() < lastStart)
80 if (++currentSequence >= ms.size())
83 lastStart = timelineRange.getStart();
85 if (timeBase == MidiList::TimeBase::beats)
87 const auto sectionEditTime = getEditBeatRange();
89 if (sectionEditTime.isEmpty()
90 || sectionEditTime.getEnd().inBeats() <= editRange.getStart()
91 || sectionEditTime.getStart().inBeats() >= editRange.getEnd())
94 const auto secondsPerBeat = getEditTimeRange().getLength().inSeconds() / sectionEditTime.getLength().inBeats();
97 { sectionEditTime.getStart().inBeats(), sectionEditTime.getEnd().inBeats() },
98 secondsPerBeat, ms[currentSequence]);
102 const auto sectionEditTime = getEditTimeRange();
104 if (sectionEditTime.isEmpty()
105 || sectionEditTime.getEnd().inSeconds() <= editRange.getStart()
106 || sectionEditTime.getStart().inSeconds() >= editRange.getEnd())
110 { sectionEditTime.getStart().inSeconds(), sectionEditTime.getEnd().inSeconds() },
111 1.0, ms[currentSequence]);
117 double secondsPerTimeBase,
121 || sectionEditRange.
getEnd() <= editRange.getStart()
122 || sectionEditRange.
getStart() >= editRange.getEnd())
125 const auto localTime = sectionEditRange - editRange.
getStart();
126 const bool mute = clipLevel.isMute();
133 MidiNodeHelpers::createNoteOffs (pc.buffers.midi, sequence, midiSourceID, localTime.getStart(), {}, getPlayHead().isPlaying());
139 if (! getPlayHeadState().isContiguousWithPreviousBlock() || localTime.getStart() <= 0.00001 || shouldCreateMessagesForTime)
141 MidiNodeHelpers::createMessagesForTime (pc.buffers.midi, sequence, localTime.getStart(),
142 channelNumbers, clipLevel, useMPEChannelMode, midiSourceID,
143 controllerMessagesScratchBuffer);
144 shouldCreateMessagesForTime =
false;
147 auto numEvents = sequence.getNumEvents();
151 currentIndex =
juce::jlimit (0, numEvents - 1, currentIndex);
153 if (sequence.getEventTime (currentIndex) >= localTime.getStart())
155 while (currentIndex > 0 && sequence.getEventTime (currentIndex - 1) >= localTime.getStart())
160 while (currentIndex < numEvents && sequence.getEventTime (currentIndex) < localTime.getStart())
165 auto volScale = clipLevel.getGain();
166 const auto lastBlockOfLoop = getPlayHeadState().isLastBlockOfLoop();
167 const double durationOfOneSample = sectionEditRange.
getLength() / pc.numSamples;
171 if (
auto meh = sequence.getEventPointer (currentIndex++))
173 auto eventTime = meh->message.getTimeStamp();
176 const auto timeCorrection = lastBlockOfLoop ? (meh->message.isNoteOff() ? 0.0 : durationOfOneSample) : 0.0;
178 if (eventTime >= (localTime.getEnd() - timeCorrection))
181 eventTime -= localTime.getStart();
183 if (eventTime >= 0.0)
186 m.multiplyVelocity (volScale);
187 const auto eventTimeSeconds = eventTime * secondsPerTimeBase;
188 pc.buffers.midi.addMidiMessage (m, eventTimeSeconds, midiSourceID);
199 if (getPlayHeadState().isLastBlockOfLoop())
200 MidiNodeHelpers::createNoteOffs (pc.buffers.midi, sequence, midiSourceID,
202 localTime.getLength() - 0.00001,
203 getPlayHead().isPlaying());
205 #if TRACKTION_SANITY_CHECK_MIDI_BUFFERS
206 MidiNodeHelpers::sanityCheckMidiBuffer (pc.buffers.midi, localTime.getLength());
constexpr ValueType getStart() const noexcept
constexpr bool isEmpty() const noexcept
constexpr ValueType getEnd() const noexcept
constexpr ValueType getLength() const noexcept
TimeBase
Determines MIDI event timing.
Struct to describe a single iteration of a process call.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...