13namespace tracktion {
inline namespace engine
18ArrangerLauncherSwitchingNode::ArrangerLauncherSwitchingNode (ProcessState& ps,
22 : TracktionEngineNode (ps),
24 arrangerNode (
std::
move (arrangerNode_)),
25 launcherNodes (
std::
move (launcherNodes_))
27 assert (arrangerNode || ! launcherNodes.empty());
28 setOptimisations ({ tracktion::graph::ClearBuffers::yes,
29 tracktion::graph::AllocateAudioBuffer::yes });
31 launcherNodesCopy.reserve (launcherNodes.size());
33 [] (
auto& n) { return n.get(); });
34 assert (launcherNodesCopy.size() == launcherNodes.size());
35 assert (! contains_v (launcherNodesCopy,
nullptr));
41 constexpr size_t seed = 7653239033668669842;
45 for (
auto n : getDirectInputNodes()) nodes.
push_back (n);
46 for (
auto n : getInternalNodes()) nodes.
push_back (n);
50 auto nodeProps = n->getNodeProperties();
51 props.hasAudio = props.hasAudio || nodeProps.hasAudio;
52 props.hasMidi = props.hasMidi || nodeProps.hasMidi;
53 props.numberOfChannels =
std::max (props.numberOfChannels, nodeProps.numberOfChannels);
54 props.latencyNumSamples =
std::max (props.latencyNumSamples, nodeProps.latencyNumSamples);
55 hash_combine (props.nodeID, nodeProps.nodeID);
75 for (
auto& n : launcherNodes)
83 const auto props = getNodeProperties();
84 const int numChannels = props.numberOfChannels;
86 if (
auto oldGraph = info.nodeGraphToReplace)
88 if (
auto oldNode = findNodeWithID<ArrangerLauncherSwitchingNode> (*oldGraph, props.nodeID))
90 if (oldNode->launcherSampleFader && oldNode->launcherSampleFader->getNumChannels() ==
static_cast<size_t> (numChannels))
91 launcherSampleFader = oldNode->launcherSampleFader;
93 if (oldNode->arrangerSampleFader && oldNode->arrangerSampleFader->getNumChannels() ==
static_cast<size_t> (numChannels))
94 arrangerSampleFader = oldNode->arrangerSampleFader;
96 if (oldNode->arrangerActiveNoteList)
97 arrangerActiveNoteList = oldNode->arrangerActiveNoteList;
99 if (oldNode->activeNode)
100 activeNode = oldNode->activeNode;
102 midiSourceID = oldNode->midiSourceID;
106 if (! launcherSampleFader)
109 if (! arrangerSampleFader)
112 if (! arrangerActiveNoteList)
118 for (
auto& launcherNode : launcherNodes)
119 launcherNode->initialise (info);
122bool ArrangerLauncherSwitchingNode::isReadyToProcess()
124 return ! arrangerNode || arrangerNode->hasProcessed();
129 for (
auto& launcherNode : launcherNodes)
130 launcherNode->prepareForNextBlock (referenceSampleRange);
137 auto destAudioView = pc.buffers.audio;
138 assert (destAudioView.getNumChannels() == launcherSampleFader->getNumChannels());
149 const auto editBeatRange = getEditBeatRange();
150 const auto playArranger = ! track->playSlotClips.get();
151 const auto slotStatus = getSlotsStatus (launcherNodes,
153 getProcessState().getSyncPoint().monotonicBeat);
155 launcherSampleFader->apply (destAudioView, SampleFader::FadeType::fadeOut);
156 arrangerSampleFader->apply (destAudioView, SampleFader::FadeType::fadeOut);
158 processLauncher (pc, slotStatus);
161 processArranger (pc, slotStatus);
165void ArrangerLauncherSwitchingNode::processLauncher (
ProcessContext& pc,
const SlotClipStatus& slotStatus)
167 auto destAudioView = pc.buffers.audio;
168 const auto numFrames = destAudioView.getNumFrames();
169 const auto editBeatRange = getEditBeatRange();
171 if (! launcherNodes.empty())
173 sortPlayingOrQueuedClipsFirst();
175 if (slotStatus.anyClipsPlaying || slotStatus.anyClipsQueued)
177 for (
auto& launcherNode : launcherNodes)
179 using enum LaunchHandle::PlayState;
180 using enum LaunchHandle::QueueState;
181 const auto& lh = launcherNode->getLaunchHandle();
182 const bool slotWasPlaying = lh.getPlayingStatus() == playing;
183 const bool slotWasQueued = lh.getQueuedStatus() == playQueued;
185 if (! (slotWasPlaying || slotWasQueued))
188 launcherNode->Node::process (pc.numSamples, pc.referenceSampleRange);
190 const bool slotIsPlaying = lh.getPlayingStatus() == playing;
191 auto sourceBuffers = launcherNode->getProcessedOutput();
192 const auto numSourceChannels = sourceBuffers.audio.getNumChannels();
195 choc::buffer::add (destAudioView.getFirstChannels (numSourceChannels), sourceBuffers.audio);
196 pc.buffers.midi.mergeFrom (sourceBuffers.midi);
198 if (slotWasPlaying && ! slotIsPlaying)
201 const auto endFrame = beatToSamplePosition (slotStatus.beatsUntilQueuedStopTrimmedToBlock,
202 editBeatRange.getLength(), numFrames);
203 launcherSampleFader->trigger (10);
204 launcherSampleFader->applyAt (destAudioView, endFrame, SampleFader::FadeType::fadeOut);
210 launcherSampleFader->push (destAudioView);
213void ArrangerLauncherSwitchingNode::processArranger (
ProcessContext& pc,
const SlotClipStatus& slotStatus)
218 auto destAudioView = pc.buffers.audio;
219 const auto editBeatRange = getEditBeatRange();
220 const auto numFrames = destAudioView.getNumFrames();
222 auto sourceBuffers = arrangerNode->getProcessedOutput();
223 const auto numSourceChannels = sourceBuffers.audio.getNumChannels();
225 if (slotStatus.beatsUntilQueuedStartTrimmedToBlock)
228 if (numSourceChannels > 0)
230 const auto endFrame = beatToSamplePosition (slotStatus.beatsUntilQueuedStartTrimmedToBlock,
231 editBeatRange.getLength(), numFrames);
233 auto destSubView = destAudioView.getFirstChannels (numSourceChannels).getStart (endFrame);
234 auto sourceSubView = sourceBuffers.audio.getStart (endFrame);
235 arrangerSampleFader->trigger (10);
237 if (sourceSubView.getNumFrames() > 0)
239 arrangerSampleFader->push (sourceSubView);
241 choc::buffer::add (destSubView, sourceSubView);
242 launcherSampleFader->applyAt (destAudioView, endFrame, SampleFader::FadeType::fadeOut);
245 const auto endTime = TimePosition::fromSamples (endFrame, getSampleRate());
247 if (sourceBuffers.midi.isNotEmpty())
249 pc.buffers.midi.isAllNotesOff = sourceBuffers.midi.isAllNotesOff;
251 for (
auto& m : sourceBuffers.midi)
253 if (m.getTimeStamp() > endTime.inSeconds())
256 pc.buffers.midi.add (m);
259 arrangerActiveNoteList->startNote (m.getChannel(), m.getNoteNumber());
260 else if (m.isNoteOff())
261 arrangerActiveNoteList->clearNote (m.getChannel(), m.getNoteNumber());
265 MidiNodeHelpers::createNoteOffs (*arrangerActiveNoteList,
269 getPlayHead().isPlaying());
274 if (numSourceChannels > 0)
276 arrangerSampleFader->push (sourceBuffers.audio);
277 choc::buffer::add (destAudioView.getFirstChannels (numSourceChannels), sourceBuffers.audio);
280 if (sourceBuffers.midi.isNotEmpty())
282 pc.buffers.midi.isAllNotesOff = sourceBuffers.midi.isAllNotesOff;
284 for (
auto& m : sourceBuffers.midi)
286 pc.buffers.midi.add (m);
289 arrangerActiveNoteList->startNote (m.getChannel(), m.getNoteNumber());
290 else if (m.isNoteOff())
291 arrangerActiveNoteList->clearNote (m.getChannel(), m.getNoteNumber());
297void ArrangerLauncherSwitchingNode::sortPlayingOrQueuedClipsFirst()
299 using enum LaunchHandle::PlayState;
300 using enum LaunchHandle::QueueState;
302 [](
auto& n1,
auto& n2)
304 auto& lh1 = n1->getLaunchHandle();
305 auto& lh2 = n2->getLaunchHandle();
307 if (lh1.getPlayingStatus() == playing)
310 if (
auto q1 = lh1.getQueuedStatus(); q1 == playQueued)
311 return lh2.getPlayingStatus() != playing;
317void ArrangerLauncherSwitchingNode::updatePlaySlotsState()
319 for (
auto& n : launcherNodesCopy)
321 if (
auto lh = n->getLaunchHandleIfNotUnique())
323 if (lh->getPlayingStatus() == LaunchHandle::PlayState::playing)
325 track->playSlotClips =
true;
333choc::buffer::FrameCount ArrangerLauncherSwitchingNode::beatToSamplePosition (
std::optional<BeatDuration> beat, BeatDuration numBeats, choc::buffer::FrameCount numFrames)
338 if (numBeats == 0_bd)
341 const auto framesPerBeats = numFrames / numBeats.inBeats();
342 return static_cast<choc::buffer::FrameCount
> (
std::round (beat->inBeats() * framesPerBeats));
346 BeatRange editBeatRange, MonotonicBeat monotonicBeat)
350 const BeatRange blockRange (monotonicBeat.v, editBeatRange.getLength());
352 for (
auto& n : launcherNodes)
354 const auto& lh = n->getLaunchHandle();
356 if (lh.getPlayingStatus() == LaunchHandle::PlayState::playing)
357 status.anyClipsPlaying =
true;
359 if (lh.getQueuedStatus() == LaunchHandle::QueueState::playQueued)
361 status.anyClipsQueued =
true;
363 const auto queuedPos = lh.getQueuedEventPosition();
367 status.beatsUntilQueuedStartTrimmedToBlock = 0_bd;
368 status.beatsUntilQueuedStart = 0_bd;
371 if (blockRange.contains (queuedPos->v))
372 status.beatsUntilQueuedStartTrimmedToBlock = queuedPos->v - blockRange.getStart();
375 if (lh.getQueuedStatus() == LaunchHandle::QueueState::stopQueued)
377 status.anyClipsQueued =
true;
378 const auto queuedPos = lh.getQueuedEventPosition();
381 status.beatsUntilQueuedStopTrimmedToBlock = 0_bd;
383 if (queuedPos && blockRange.contains (queuedPos->v))
384 status.beatsUntilQueuedStopTrimmedToBlock = queuedPos->v - blockRange.getStart();
392void ArrangerLauncherSwitchingNode::sharedTimerCallback()
395 updatePlaySlotsState();
T back_inserter(T... args)
Main graph Node processor class.
Struct to describe a single iteration of a process call.
Passed into AudioNodes when they are being initialised, to give them useful contextual information th...
RangeType< BeatPosition > BeatRange
A RangeType based on beats.
Holds some really basic properties of a node.