13namespace tracktion {
inline namespace engine
18 static bool shouldUseFineGrainAutomation (Plugin& p)
20 if (! p.isAutomationNeeded())
23 if (p.engine.getPluginManager().canUseFineGrainAutomation)
24 return p.engine.getPluginManager().canUseFineGrainAutomation (p);
33 double sampleRateToUse,
int blockSizeToUse,
36 bool rendering,
bool canBalanceLatency,
37 int maxNumChannelsToUse)
39 input (
std::move (inputNode)),
40 plugin (
std::move (pluginToProcess)),
41 trackMuteState (trackMuteStateToUse),
42 playHeadState (processStateToUse.playHeadState),
43 isRendering (rendering),
44 maxNumChannels (maxNumChannelsToUse),
45 balanceLatency (canBalanceLatency)
49 initialisePlugin (sampleRateToUse, blockSizeToUse);
54 if (isInitialised && ! plugin->baseClassNeedsInitialising())
55 plugin->baseClassDeinitialise();
61 if (cachedNodeProperties)
62 return *cachedNodeProperties;
64 auto props = input->getNodeProperties();
68 props.numberOfChannels =
juce::jmax (2, props.numberOfChannels, plugin->getNumOutputChannelsGivenInputs (
std::max (2, props.numberOfChannels)));
70 if (maxNumChannels > 0)
71 props.numberOfChannels =
std::min (maxNumChannels, props.numberOfChannels);
73 props.hasAudio = props.hasAudio || plugin->producesAudioWhenNoAudioInput();
74 props.hasMidi = props.hasMidi || plugin->takesMidiInput();
75 props.latencyNumSamples =
std::max (0, props.latencyNumSamples + latencyNumSamples);
76 props.nodeID = (
size_t) plugin->itemID.getRawID();
79 cachedNodeProperties = props;
87 jassert (sampleRate == info.sampleRate);
92 if (props.latencyNumSamples > 0)
93 automationAdjustmentTime = TimeDuration::fromSamples (-props.latencyNumSamples, sampleRate);
95 if (shouldUseFineGrainAutomation (*plugin))
98 canProcessBypassed = balanceLatency
100 && latencyNumSamples > 0;
102 if (canProcessBypassed)
104 replaceLatencyProcessorIfPossible (info.nodeGraphToReplace);
106 if (! latencyProcessor)
109 latencyProcessor->setLatencyNumSamples (latencyNumSamples);
110 latencyProcessor->prepareToPlay (info.sampleRate, info.blockSize, props.numberOfChannels);
116 if (info.enableNodeMemorySharing && input->numOutputNodes == 1)
118 const auto inputNumChannels = input->getNodeProperties().numberOfChannels;
119 const auto desiredNumChannels = props.numberOfChannels;
121 if (inputNumChannels >= desiredNumChannels)
123 canUseSourceBuffers =
true;
125 tracktion::graph::AllocateAudioBuffer::no });
137 if (canUseSourceBuffers)
143 auto inputBuffers = input->getProcessedOutput();
144 auto& inputAudioBlock = inputBuffers.audio;
146 auto& outputBuffers = pc.buffers;
147 auto outputAudioView = outputBuffers.audio;
148 const auto blockNumSamples = inputAudioBlock.getNumFrames();
149 jassert (inputAudioBlock.getNumFrames() == outputAudioView.getNumFrames());
151 const auto numInputChannelsToCopy =
std::min (inputAudioBlock.getNumChannels(),
152 outputAudioView.getNumChannels());
154 if (latencyProcessor)
156 if (numInputChannelsToCopy > 0)
157 latencyProcessor->writeAudio (inputAudioBlock.getFirstChannels (numInputChannelsToCopy));
159 latencyProcessor->writeMIDI (inputBuffers.midi);
164 if (numInputChannelsToCopy > 0)
165 tracktion::graph::copyIfNotAliased (outputAudioView.getFirstChannels (numInputChannelsToCopy),
166 inputAudioBlock.getFirstChannels (numInputChannelsToCopy));
169 auto subBlockSize = subBlockSizeToUse < 0 ? blockNumSamples
170 : (choc::buffer::FrameCount) subBlockSizeToUse;
172 choc::buffer::FrameCount numSamplesDone = 0;
173 auto numSamplesLeft = blockNumSamples;
175 bool shouldProcessPlugin = canProcessBypassed || plugin->isEnabled();
176 bool isAllNotesOff = inputBuffers.midi.isAllNotesOff;
179 isAllNotesOff =
true;
181 if (trackMuteState !=
nullptr)
188 isAllNotesOff =
true;
193 auto inputMidiIter = inputBuffers.midi.begin();
196 for (
int subBlockNum = 0;; ++subBlockNum)
198 auto numSamplesThisBlock =
std::min (subBlockSize, numSamplesLeft);
202 const auto blockPropStart = (numSamplesDone / (
double) blockNumSamples);
203 const auto blockPropEnd = ((numSamplesDone + numSamplesThisBlock) / (
double) blockNumSamples);
204 const auto subBlockTimeRange = TimeRange (toPosition (blockTimeRange.getLength()) * blockPropStart,
205 toPosition (blockTimeRange.getLength()) * blockPropEnd);
207 midiMessageArray.clear();
208 midiMessageArray.isAllNotesOff = isAllNotesOff;
210 for (
auto end = inputBuffers.midi.end(); inputMidiIter != end; ++inputMidiIter)
212 const auto timestamp = inputMidiIter->getTimeStamp();
215 if (! subBlockTimeRange.isEmpty()
216 && timestamp >= subBlockTimeRange.getEnd().inSeconds())
219 midiMessageArray.addMidiMessage (*inputMidiIter,
220 timestamp - subBlockTimeRange.getStart().inSeconds(),
221 inputMidiIter->mpeSourceID);
225 if (shouldProcessPlugin)
226 plugin->applyToBufferWithAutomation (getPluginRenderContext ({ blockTimeRange.getStart() + toDuration (subBlockTimeRange.getStart()),
227 blockTimeRange.getStart() + toDuration (subBlockTimeRange.getEnd()) },
231 if (subBlockNum == 0)
232 outputBuffers.midi.swapWith (midiMessageArray);
234 outputBuffers.midi.mergeFrom (midiMessageArray);
236 numSamplesDone += numSamplesThisBlock;
237 numSamplesLeft -= numSamplesThisBlock;
239 if (numSamplesLeft == 0)
242 isAllNotesOff =
false;
246 if (latencyProcessor)
249 if (plugin->isEnabled())
251 auto numSamples = (
int) blockNumSamples;
252 latencyProcessor->clearAudio (numSamples);
253 latencyProcessor->clearMIDI (numSamples);
257 outputBuffers.midi.clear();
260 if (numInputChannelsToCopy > 0)
261 latencyProcessor->readAudioOverwriting (outputAudioView);
263 latencyProcessor->readMIDI (outputBuffers.midi, (
int) blockNumSamples);
268 sanitise (outputAudioView);
272void PluginNode::initialisePlugin (
double sampleRateToUse,
int blockSizeToUse)
274 plugin->baseClassInitialise ({
TimePosition(), sampleRateToUse, blockSizeToUse });
275 isInitialised =
true;
277 sampleRate = sampleRateToUse;
278 latencyNumSamples =
juce::roundToInt (plugin->getLatencySeconds() * sampleRate);
283 return { &destBuffer,
286 &midiMessageArray, 0.0,
287 editTime + automationAdjustmentTime,
289 isRendering, canProcessBypassed };
292void PluginNode::replaceLatencyProcessorIfPossible (NodeGraph* nodeGraphToReplace)
294 if (nodeGraphToReplace ==
nullptr)
298 const auto nodeIDToLookFor = props.nodeID;
300 if (nodeIDToLookFor == 0)
303 if (
auto oldNode = findNodeWithID<PluginNode> (*nodeGraphToReplace, nodeIDToLookFor))
305 if (! oldNode->latencyProcessor)
308 if (! latencyProcessor)
310 if (oldNode->latencyProcessor->hasConfiguration (latencyNumSamples, sampleRate, props.numberOfChannels))
311 latencyProcessor = oldNode->latencyProcessor;
316 if (latencyProcessor->hasSameConfigurationAs (*oldNode->latencyProcessor))
317 latencyProcessor = oldNode->latencyProcessor;
int getNumChannels() const noexcept
int getNumSamples() const noexcept
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
ReferencedType * get() const noexcept
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
void preProcess(choc::buffer::FrameCount, juce::Range< int64_t >) override
Called when the node is to be processed, just before process.
void process(ProcessContext &) override
Called when the node is to be processed.
void prefetchBlock(juce::Range< int64_t >) override
Called before once on all Nodes before they are processed.
void prepareToPlay(const tracktion::graph::PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
PluginNode(std::unique_ptr< Node > input, tracktion::engine::Plugin::Ptr, double sampleRateToUse, int blockSizeToUse, const TrackMuteState *, ProcessState &, bool rendering, bool balanceLatency, int maxNumChannelsToUse)
Creates a PluginNode to process a plugin on a Track.
~PluginNode() override
Destructor.
Holds the state of a Track and if its contents/plugins should be played or not.
bool shouldTrackBeAudible() const
Returns true if the track's mix bus should be audible.
bool shouldTrackContentsBeProcessed() const
Returns true if the track's contents should be processed e.g.
bool wasJustMuted() const
Returns true if the last block was audible but this one isn't.
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.
void setOptimisations(NodeOptimisations)
This can be called to provide some hints about allocating or playing back a Node to improve efficienc...
void setBufferViewToUse(Node *sourceNode, const choc::buffer::ChannelArrayView< float > &)
This can be called during prepareToPlay to set a BufferView to use which can improve efficiency.
Struct to describe a single iteration of a process call.
bool didPlayheadJump() noexcept
Returns true if the play head jumped.
bool isUserDragging() const
Returns true if the user is dragging.
bool isPlaying() const noexcept
Returns true is the play head is currently playing.
constexpr Type jmax(Type a, Type b)
void ignoreUnused(Types &&...) noexcept
int roundToInt(const FloatType value) noexcept
juce::AudioBuffer< float > toAudioBuffer(choc::buffer::ChannelArrayView< float > view)
Creates a juce::AudioBuffer from a choc::buffer::BufferView.
choc::buffer::FrameRange frameRangeWithStartAndLength(choc::buffer::FrameCount start, choc::buffer::FrameCount length)
Returns a FrameRange with a start and length.
Represents a position in real-life time.
Holds the state of a process call.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...