11#include "../../model/clips/tracktion_LaunchHandle.h"
13namespace tracktion {
inline namespace engine
16SlotControlNode::SlotControlNode (ProcessState& ps,
22 : TracktionEngineNode (ps),
23 launchHandle (
std::
move (launchHandle_)),
24 stopDuration (stopDuration_),
25 stopFunction (
std::
move (stopFunction_)),
28 localPlayheadState (ps.playHeadState.playHead),
29 localProcessState (localPlayheadState, *ps.getTempoSequence())
31 assert (getProcessState().getTempoSequence() !=
nullptr);
34 for (
auto n : transformNodes (*input))
36 orderedNodes.push_back (n);
38 if (n->getDirectInputNodes().empty())
39 leafNodes.push_back (n);
41 if (
auto engineNode =
dynamic_cast<TracktionEngineNode*
> (n))
42 engineNode->setProcessState (localProcessState);
44 if (
auto dynamicNode =
dynamic_cast<DynamicallyOffsettableNodeBase*
> (n))
45 offsetNodes.push_back (dynamicNode);
47 if (
auto mn =
dynamic_cast<LoopingMidiNode*
> (n))
52const LaunchHandle& SlotControlNode::getLaunchHandle()
const
57const LaunchHandle* SlotControlNode::getLaunchHandleIfNotUnique()
const
59 return launchHandle.use_count() > 1 ? launchHandle.get() :
nullptr;
64 auto props = input->getNodeProperties();
65 props.nodeID =
static_cast<size_t> (slotID.getRawID());
78 info2.allocateAudioBuffer = {};
79 info2.deallocateAudioBuffer = {};
81 for (
auto& i : orderedNodes)
82 i->initialise (info2);
85 const auto numChans =
static_cast<size_t> (getNodeProperties().numberOfChannels);
90 if (
auto oldGraph = info.nodeGraphToReplace)
91 if (
auto oldNode = findNodeWithID<SlotControlNode> (*oldGraph, (
size_t) slotID.getRawID()))
92 if (oldNode->lastSamples && oldNode->lastSamples->size() == numChans)
93 lastSamples = oldNode->lastSamples;
101bool SlotControlNode::isReadyToProcess()
103 for (
auto& i : leafNodes)
104 if (! i->isReadyToProcess())
112 for (
auto& node : orderedNodes)
113 node->prepareForNextBlock (referenceSampleRange);
119 if (wasPlaying && ! getPlayHead().isPlaying())
122 processStop (pc, 0.0);
126 const auto syncRange = getProcessState().getSyncRange();
128 if (
const auto editBeatRange = getEditBeatRange(); ! editBeatRange.isEmpty())
132 if (
auto playedMonotonicRange = launchHandle->getPlayedMonotonicRange())
134 const auto blockRange =
MonotonicBeatRange { { syncRange.start.monotonicBeat.v, syncRange.end.monotonicBeat.v } };
135 const auto stopPoint =
MonotonicBeat { playedMonotonicRange->v.getStart() + *stopDuration };
137 if (blockRange.v.contains (stopPoint.v))
139 const auto stopQueued = launchHandle->getQueuedStatus() == LaunchHandle::QueueState::stopQueued;
140 launchHandle->stop (stopPoint);
143 if (! stopQueued && stopFunction)
144 stopFunction (stopPoint);
149 if (
auto splitStatus = launchHandle->advance (syncRange);
150 ! splitStatus.range1.isEmpty())
151 processSplitSection (pc, splitStatus);
153 else if (launchHandle->getQueuedStatus() == LaunchHandle::QueueState::stopQueued)
155 launchHandle->advance (syncRange);
162 const auto totalRange = status.range1.isEmpty() ? status.range2
163 : (status.range2.isEmpty() ? status.range1
164 : status.range1.getUnionWith (status.range2));
166 totalRange.getEnd().inBeats());
168 auto processSubSection = [
this, &pc, &blockRangeBeats, editBeatRange = getEditBeatRange(), editTimeRange = getEditTimeRange()] (
auto section,
bool isPlaying,
auto playStartTime)
170 const auto proportion =
juce::Range (blockRangeBeats.convertTo0to1 (section.getStart().inBeats()),
171 blockRangeBeats.convertTo0to1 (section.getEnd().inBeats()));
172 const auto startFrame = (choc::buffer::FrameCount)
std::llround (proportion.getStart() * pc.numSamples);
173 const auto endFrame = (choc::buffer::FrameCount)
std::llround (proportion.getEnd() * pc.numSamples);
175 const auto sectionNumFrames = endFrame - startFrame;
177 if (sectionNumFrames == 0)
180 const auto numRefSamples = pc.referenceSampleRange.getLength();
181 const auto startRefSample = pc.referenceSampleRange.getStart() + (
int64_t)
std::llround (proportion.getStart() * numRefSamples);
182 const auto endRefSample = pc.referenceSampleRange.getStart() + (
int64_t)
std::llround (proportion.getEnd() * numRefSamples);
184 const juce::Range subSectionReferenceSampleRange (startRefSample, endRefSample);
186 auto sectionBufferView = pc.buffers.audio.getFrameRange ({ startFrame, endFrame });
188 sectionNumFrames, subSectionReferenceSampleRange,
189 { sectionBufferView, pc.buffers.midi }
192 const auto startBeat = editBeatRange.getStart() + editBeatRange.getLength() * proportion.getStart();
193 const auto endBeat = editBeatRange.getStart() + editBeatRange.getLength() * proportion.getEnd();
194 const auto startTime = editTimeRange.getStart() + editTimeRange.getLength() * proportion.getStart();
195 const auto endTime = editTimeRange.getStart() + editTimeRange.getLength() * proportion.getEnd();
196 processSection (subSection, { startBeat, endBeat }, { startTime, endTime }, section, isPlaying, playStartTime);
210void SlotControlNode::processSection (
ProcessContext& pc, BeatRange editBeatRange, TimeRange editTimeRange, BeatRange unloopedClipBeatRange,
215 wasPlaying = isPlaying;
216 localPlayheadState.playheadJumped =
false;
217 localPlayheadState.firstBlockOfLoop =
false;
222 if (wasPlaying != isPlaying)
223 processStop (pc, (editTimeRange.getStart() - getEditTimeRange().getStart()).inSeconds());
225 pc.buffers.audio.clear();
229 else if (! wasPlaying)
232 localPlayheadState.playheadJumped =
true;
235 localPlayheadState.firstBlockOfLoop =
true;
238 localProcessState.setPlaybackSpeedRatio (getPlaybackSpeedRatio());
239 localProcessState.update (getSampleRate(), pc.referenceSampleRange,
240 ProcessState::UpdateContinuityFlags::no);
241 auto& ps = getProcessState();
242 localProcessState.setSyncRange (ps.getSyncRange());
247 const auto clipEditOffset = editBeatRange.getStart() - unloopedClipBeatRange.getStart();
248 const auto offset = clipEditOffset +
toDuration (*playStartTime);
250 if (! almostEqual (lastOffset.inBeats(), offset.inBeats(), 0.0000001))
255 localPlayheadState.playheadJumped =
true;
257 for (
auto n : offsetNodes)
258 n->setDynamicOffsetBeats (offset);
263 for (
auto& node : orderedNodes)
264 node->prepareForNextBlock (pc.referenceSampleRange);
267 for (
auto& node : orderedNodes)
268 node->process (pc.numSamples, pc.referenceSampleRange);
271 auto sourceBuffers = input->getProcessedOutput();
272 assert (sourceBuffers.audio.size == pc.buffers.audio.size);
273 copyIfNotAliased (pc.buffers.audio, sourceBuffers.audio);
274 pc.buffers.midi.copyFrom (sourceBuffers.midi);
279 const auto numChannels = pc.buffers.audio.size.numChannels;
280 const auto numFrames = pc.buffers.audio.size.numFrames;
281 jassert (lastSamples->size() ==
static_cast<size_t> (numChannels));
283 for (choc::buffer::ChannelCount channel = 0; channel < numChannels; ++channel)
285 const auto dest = pc.buffers.audio.getIterator (channel).sample;
286 auto& lastSample = (*lastSamples)[(
size_t) channel];
287 lastSample = dest[numFrames - 1];
292void SlotControlNode::processStop (
ProcessContext& pc,
double timestampForMidiNoteOffs)
296 midiNode->killActiveNotes (pc.buffers.midi, timestampForMidiNoteOffs);
302 auto& buffer = pc.buffers.audio;
303 const auto numChannels = buffer.size.numChannels;
304 const auto numFrames = buffer.size.numFrames;
306 if (
const auto lastSampleFadeLength =
std::min (numFrames, 40u);
307 lastSampleFadeLength > 0)
309 for (choc::buffer::ChannelCount channel = 0; channel < numChannels; ++channel)
311 if (channel < (choc::buffer::ChannelCount) lastSamples->size())
313 const auto dest = buffer.getIterator (channel).sample;
314 auto& lastSample = (*lastSamples)[(
size_t) channel];
316 for (uint32_t i = 0; i < lastSampleFadeLength; ++i)
318 auto alpha = i / (
float) lastSampleFadeLength;
319 dest[i] = lastSample * (1.0f - alpha);
326 buffer.getChannel (channel).clear ();
332 if (launchHandle->getQueuedStatus() == LaunchHandle::QueueState::stopQueued)
333 launchHandle->stop ({});
335 launchHandle->advance (getProcessState().getSyncRange());
Represents two beat ranges where the play state can be different in each.
Struct to describe a single iteration of a process call.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...