13#define USE_PARTITION_INSERTION 1
15namespace tracktion {
inline namespace engine
18namespace combining_node_utils
22 static constexpr BeatDuration decayTimeAllowance { 8_bd };
23 static constexpr int secondsPerGroup = 8;
25 static inline constexpr int timeToGroupIndex (TimePosition t)
noexcept
27 return static_cast<int> (t.inSeconds()) / secondsPerGroup;
35 : time (t), node (std::move (sourceNode))
37 for (
auto n = node.get();;)
39 nodesToProcess.insert (nodesToProcess.begin(), n);
40 auto inputNodes = n->getDirectInputNodes();
42 if (inputNodes.empty())
46 assert (inputNodes.size() == 1);
47 n = inputNodes.front();
53 return nodesToProcess;
57 choc::buffer::ChannelArrayView<float> view)
62 jassert (size.numFrames == view.getNumFrames());
63 jassert (size.numChannels <= view.getNumChannels());
65 return { view.getFirstChannels (size.numChannels), {} };
67 info2.deallocateAudioBuffer =
nullptr;
69 for (
auto n : nodesToProcess)
70 n->initialise (info2);
73 bool isReadyToProcess()
const
75 return nodesToProcess.front()->isReadyToProcess();
80 for (
auto n : nodesToProcess)
81 n->prepareForNextBlock (referenceSampleRange);
93 for (
auto n : nodesToProcess)
94 n->process (pc.numSamples, pc.referenceSampleRange);
97 auto nodeOutput = node->getProcessedOutput();
98 const auto numDestChannels = pc.buffers.audio.getNumChannels();
99 const auto numChannelsToAdd =
std::min (nodeOutput.audio.getNumChannels(), numDestChannels);
101 if (numChannelsToAdd > 0)
102 add (pc.buffers.audio.getFirstChannels (numChannelsToAdd),
103 nodeOutput.audio.getFirstChannels (numChannelsToAdd));
105 pc.buffers.midi.mergeFrom (nodeOutput.midi);
108 hasPrefetched =
false;
112 size_t getAllocatedBytes()
const
116 for (
auto n : nodesToProcess)
117 size += n->getAllocatedBytes();
122 const BeatRange time;
128 bool hasPrefetched =
false;
140 hash_combine (nodeProperties.nodeID, itemID);
143CombiningNode::~CombiningNode() {}
147 jassert (
time.getEnd() <= Edit::getMaximumEditEnd());
153 assert (input !=
nullptr);
155 if (beatRange.isEmpty())
158 auto props = input->getNodeProperties();
160 nodeProperties.hasAudio |= props.hasAudio;
161 nodeProperties.hasMidi |= props.hasMidi;
162 nodeProperties.numberOfChannels =
std::max (nodeProperties.numberOfChannels, props.numberOfChannels);
163 nodeProperties.latencyNumSamples =
std::max (nodeProperties.latencyNumSamples, props.latencyNumSamples);
164 hash_combine (nodeProperties.nodeID, props.nodeID);
165 hash_combine (nodeProperties.nodeID, beatRange);
167 #if USE_PARTITION_INSERTION
171 return i->time.getStart() < beatRange.getStart();
173 int i =
static_cast<int> (
std::distance (inputs.begin(), lower));
176 for (i = 0; i < inputs.size(); ++i)
177 if (inputs.getUnchecked (i)->time.getStart() >= beatRange.getStart())
181 beatRange = BeatRange (beatRange.getStart(), beatRange.getLength() + combining_node_utils::decayTimeAllowance);
182 auto tan = inputs.insert (i,
new TimedNode (std::move (input), beatRange));
186 const auto overlapTime = TimeDuration::fromSeconds (combining_node_utils::secondsPerGroup / 2 + 2);
187 const auto timeRange =
toTime (ts, beatRange).expanded (overlapTime);
188 const auto start =
std::max (0, combining_node_utils::timeToGroupIndex (timeRange.getStart()));
189 const auto end =
std::max (0, combining_node_utils::timeToGroupIndex (timeRange.getEnd()));
191 while (groups.size() <= end)
194 for (i = start; i <= end; ++i)
196 auto g = groups.getUnchecked (i);
198 #if USE_PARTITION_INSERTION
202 return in->time.getStart() < beatRange.getStart();
204 const int j =
static_cast<int> (
std::distance (g->begin(), lowerGroup));
207 for (j = 0; j < g->size(); ++j)
208 if (g->getUnchecked (j)->time.getStart() >= beatRange.getStart())
219 return inputs.size();
226 for (
auto i : inputs)
227 for (
auto n : i->getNodes())
240 return nodeProperties;
246 tempAudioBuffer.resize (choc::buffer::Size::create ((choc::buffer::ChannelCount) nodeProperties.numberOfChannels,
247 (choc::buffer::FrameCount) info.blockSize));
249 for (
auto& i : inputs)
251 i->prepareToPlay (info, tempAudioBuffer.getView());
253 if (! i->isReadyToProcess())
258 if (info.nodeGraphToReplace !=
nullptr)
260 if (
auto oldNode = findNode<CombiningNode> (*info.nodeGraphToReplace,
261 [itemID = itemID] (
auto& cn) { return cn.itemID == itemID; }))
263 queueNoteOffsForClipsNoLongerPresent (*oldNode);
275 SCOPED_REALTIME_CHECK
283 if (
auto g = groups[combining_node_utils::timeToGroupIndex (editTime.getStart())])
287 if (!
tan->isReadyToProcess())
300 SCOPED_REALTIME_CHECK
301 const auto initialEvents = pc.buffers.midi.size();
304 pc.buffers.midi.mergeFromAndClear (noteOffEventsToSend);
307 if (
auto g = groups[combining_node_utils::timeToGroupIndex (
getEditTimeRange().getStart())])
311 if (
tan->time.getEnd() > editBeats.getStart())
313 if (
tan->time.getStart() >= editBeats.getEnd())
317 tempAudioBuffer.clear();
326 if (pc.buffers.midi.size() > initialEvents)
327 pc.buffers.midi.sortByTimestamp();
330size_t CombiningNode::getAllocatedBytes()
const
332 size_t size = tempAudioBuffer.getView().data.getBytesNeeded (tempAudioBuffer.getSize());
334 for (
const auto& i : inputs)
335 size += i->getAllocatedBytes();
340void CombiningNode::prefetchGroup (
juce::Range<int64_t> referenceSampleRange, TimeRange editTime, BeatRange editBeats)
342 if (
auto g = groups[combining_node_utils::timeToGroupIndex (editTime.getStart())])
346 if (
tan->time.getEnd() > editBeats.getStart())
348 if (
tan->time.getStart() >= editBeats.getEnd())
351 tan->prefetchBlock (referenceSampleRange);
357void CombiningNode::queueNoteOffsForClipsNoLongerPresent (
const CombiningNode& oldCombiningNode)
363 for (
auto timedNode : inputs)
364 for (auto node : timedNode->
getNodes())
365 if (auto loopingMidiNode = dynamic_cast<LoopingMidiNode*> (node))
366 currentNodeIDs.push_back (loopingMidiNode->getItemID());
368 for (
auto oldTimedNode : oldCombiningNode.inputs)
370 for (
auto oldNode : oldTimedNode->
getNodes())
372 if (
auto oldLoopingMidiNode =
dynamic_cast<LoopingMidiNode*
> (oldNode))
374 if (
std::find (currentNodeIDs.
begin(), currentNodeIDs.
end(), oldLoopingMidiNode->getItemID())
375 == currentNodeIDs.
end())
377 oldLoopingMidiNode->getActiveNoteList()->iterate ([
this, mpeSourceID = oldLoopingMidiNode->getMPESourceID()]
380 noteOffEventsToSend.addMidiMessage (juce::MidiMessage::noteOff (chan, note), mpeSourceID);
void process(ProcessContext &) override
Called when the node is to be processed.
void addInput(std::unique_ptr< Node >, TimeRange)
Adds an input node to be played at a given time range.
int getNumInputs() const
Returns the number of inputs added.
void prefetchBlock(juce::Range< int64_t >) override
Called before once on all Nodes before they are processed.
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
std::vector< Node * > getDirectInputNodes() override
Should return all the inputs directly feeding in to this node.
void prepareToPlay(const tracktion::graph::PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
std::vector< Node * > getInternalNodes() override
Returns the inputs that have been added.
Base class for Nodes that provides information about the current process call.
TimeRange getEditTimeRange() const
Returns the edit time range of the current process block.
ProcessState & getProcessState()
Returns the ProcessState in use.
BeatRange getEditBeatRange() const
Returns the edit beat range of the current process block.
Struct to describe a single iteration of a process call.
BeatPosition toBeats(TimePosition tp, const TempoSequence &ts)
Converts a TimePosition to a BeatPosition given a TempoSequence.
TimePosition toTime(BeatPosition bp, const TempoSequence &ts)
Converts a BeatPosition to a TimePosition given a TempoSequence.
std::vector< Node * > getNodes(Node &node, VertexOrdering vertexOrdering)
Returns all the nodes in a Node graph in the order given by vertexOrdering.
T partition_point(T... args)
ID for objects of type EditElement - e.g.
Holds the state of a process call.
const tempo::Sequence * getTempoSequence() const
Returns the tempo::Sequence this state has been initialised with one.
Holds a view over some data and optionally some storage for that data.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...