13namespace tracktion {
inline namespace engine
20 Plugin::Array plugins, insideRacks;
23 if (auto pluginNode = dynamic_cast<PluginNode*> (n))
24 plugins.add (&pluginNode->getPlugin());
26 for (
auto plugin : plugins)
27 if (auto rack = dynamic_cast<RackInstance*> (plugin))
28 if (auto type = rack->type)
29 for (auto p : type->getPlugins())
30 insideRacks.addIfNotAlreadyThere (p);
32 plugins.addArray (insideRacks);
46 r (p), originalParams (p),
47 playHead (
std::move (playHead_)),
48 playHeadState (
std::move (playHeadState_)),
49 processState (
std::move (processState_)),
50 status (
juce::Result::ok()),
51 ditherers (256, r.bitDepth),
52 sourceToUpdate (sourceToUpdate_)
55 TRACKTION_ASSERT_MESSAGE_THREAD
58 jassert (r.time.getLength() > 0.0s);
62 nodePlayer->setNumThreads ((
size_t) p.engine->getEngineBehaviour().getNumberOfCPUsToUseForAudio() - 1);
64 numLatencySamplesToDrop = nodePlayer->getNode()->getNodeProperties().latencyNumSamples;
65 r.time = r.time.withEnd (r.time.getEnd() + TimeDuration::fromSamples (numLatencySamplesToDrop, r.sampleRateForAudio));
70 TRACKTION_LOG_ERROR(
"Rendering whilst attached to audio device");
73 if (r.shouldNormalise || r.trimSilenceAtEnds || r.shouldNormaliseByRMS)
75 needsToNormaliseAndTrim =
true;
80 r.destFile = intermediateFile->getFile();
82 r.shouldNormalise =
false;
83 r.trimSilenceAtEnds =
false;
84 r.shouldNormaliseByRMS =
false;
90 auto props = nodePlayer->getNode()->getNodeProperties();
92 if (p.checkNodesForAudio && ! props.hasAudio)
98 if (r.mustRenderInMono || (r.canRenderInMono && (props.numberOfChannels < 2)))
102 AudioFileUtils::addBWAVStartToMetadata (r.metadata, toSamples (r.time.getStart(), r.sampleRateForAudio));
105 r.audioFormat, numOutputChans, r.sampleRateForAudio,
106 r.bitDepth, r.metadata, r.quality);
108 if (r.destFile !=
juce::File() && ! writer->isOpen())
114 blockLength = TimeDuration::fromSamples (r.blockSizeForAudio, r.sampleRateForAudio);
117 numPreRenderBlocks = (
int) ((r.sampleRateForAudio / 2) / r.blockSizeForAudio + 1);
120 realTimePerBlock = (
int) (blockLength.
inSeconds() * 1000.0 + 0.99);
129 streamTime = r.time.getStart();
131 precount = numPreRenderBlocks;
132 streamTime = streamTime - (blockLength * precount);
134 plugins = findAllPlugins (*nodePlayer->getNode());
137 Renderer::RenderTask::setAllPluginsRealtime (plugins, r.realTimeRender);
138 nodePlayer->prepareToPlay (r.sampleRateForAudio, r.blockSizeForAudio);
139 Renderer::RenderTask::flushAllPlugins (plugins, r.sampleRateForAudio, r.blockSizeForAudio);
142 hasStartedSavingToFile = ! r.trimSilenceAtEnds;
145 playHead->setPosition (toSamples (r.time.getStart(), r.sampleRateForAudio));
147 samplesToWrite = tracktion::toSamples ((r.time.getLength() + r.endAllowance), r.sampleRateForAudio);
149 if (sourceToUpdate !=
nullptr)
150 sourceToUpdate->reset (numOutputChans, r.sampleRateForAudio, samplesToWrite);
156 r.resultMagnitude = owner.params.resultMagnitude = peak;
157 r.resultRMS = owner.params.resultRMS = rmsNumSamps > 0 ? (
float) (rmsTotal / rmsNumSamps) : 0.0f;
158 r.resultAudioDuration = owner.params.resultAudioDuration =
float (numSamplesWrittenToSource / owner.params.sampleRateForAudio);
161 Renderer::RenderTask::setAllPluginsRealtime (plugins,
true);
163 if (writer !=
nullptr)
164 writer->closeForWriting();
166 callBlocking ([
this] { nodePlayer.reset(); });
168 if (needsToNormaliseAndTrim)
169 owner.performNormalisingAndTrimming (originalParams, r);
177 if (--sleepCounter <= 0)
179 sleepCounter = sleepCounterMax;
180 juce::Thread::sleep (1);
185 writer->closeForWriting();
189 Renderer::RenderTask::setAllPluginsRealtime (plugins,
true);
194 auto blockEnd = streamTime + blockLength;
197 blockEnd =
juce::jmin (r.time.getStart(), blockEnd);
199 if (precount > numPreRenderBlocks / 2)
200 playHead->setPosition (toSamples (streamTime, r.sampleRateForAudio));
201 else if (precount == numPreRenderBlocks / 2)
206 streamTime = r.time.getStart();
207 blockEnd = streamTime + blockLength;
209 playHead->playSyncedToRange (toSamples (TimeRange (streamTime,
Edit::getMaximumLength()), r.sampleRateForAudio));
210 playHeadState->update (toSamples (TimeRange (streamTime, blockEnd), r.sampleRateForAudio));
213 if (r.realTimeRender)
216 auto timeToWait = (
int) (realTimePerBlock - (timeNow - lastTime));
220 juce::Thread::sleep (timeToWait);
223 currentTempoPosition->set (streamTime);
227 const TimeRange streamTimeRange (streamTime, blockEnd);
234 auto leafNodesReady = [
this, referenceSampleRange]
236 for (
auto node :
getNodes (*nodePlayer->getNode(), VertexOrdering::postordering))
248 while (! (leafNodesReady || owner.
shouldExit()))
252 renderingBuffer.
clear();
257 (choc::buffer::FrameCount) referenceSampleRange.getLength());
259 nodePlayer->process ({ (choc::buffer::FrameCount) referenceSampleRange.getLength(), referenceSampleRange, { destView, midiBuffer} });
263 jassert (playHeadState->isContiguousWithPreviousBlock());
266 samplesToWrite -= numSamplesDone;
268 auto blockSize = (
uint32_t) numSamplesDone;
271 if (numLatencySamplesToDrop > 0)
273 auto numToDrop =
std::min ((
uint32_t) numLatencySamplesToDrop, numSamplesDone);
274 numLatencySamplesToDrop -= (
int) numToDrop;
275 numSamplesDone -= numToDrop;
277 blockSize = numSamplesDone;
278 blockOffset = destView.getNumFrames() - blockSize;
283 jassert (blockSize <= destView.getNumFrames());
285 if (writeAudioBlock (destView.getFrameRange ({ blockOffset, blockOffset + blockSize })) == WriteResult::failed)
292 juce::Thread::sleep ((
int) (blockLength.inSeconds() * 1000));
295 if (streamTime > r.time.getEnd() + r.endAllowance)
300 else if (streamTime > r.time.getEnd()
301 && renderingBuffer.
getMagnitude (0, r.blockSizeForAudio) <= thresholdForStopping)
307 auto prog = (
float) ((streamTime - r.time.getStart()) /
juce::jmax (1_td, r.time.getLength()));
309 if (needsToNormaliseAndTrim)
315 streamTime = blockEnd;
321NodeRenderContext::WriteResult NodeRenderContext::writeAudioBlock (choc::buffer::ChannelArrayView<float> block)
325 auto blockSizeSamples = (
int) block.getNumFrames();
330 if (r.ditheringEnabled && r.bitDepth < 32)
331 ditherers.apply (buffer, blockSizeSamples);
333 auto mag = buffer.getMagnitude (0, blockSizeSamples);
336 if (! hasStartedSavingToFile)
337 hasStartedSavingToFile = (mag > 0.0f);
339 for (
int i = buffer.getNumChannels(); --i >= 0;)
341 rmsTotal += buffer.getRMSLevel (i, 0, blockSizeSamples);
345 if (! hasStartedSavingToFile)
346 samplesTrimmed += blockSizeSamples;
349 if (sourceToUpdate !=
nullptr && blockSizeSamples > 0)
350 sourceToUpdate->addBlock (numSamplesWrittenToSource, buffer, 0, blockSizeSamples);
352 numSamplesWrittenToSource += blockSizeSamples;
356 if (blockSizeSamples > 0 && hasStartedSavingToFile
358 && ! writer->appendBuffer (buffer, blockSizeSamples))
359 return WriteResult::failed;
361 return WriteResult::succeeded;
373 const int samplesPerBlock = r.blockSizeForAudio;
374 const double sampleRate = r.sampleRateForAudio;
375 const auto blockLength = TimeDuration::fromSamples (samplesPerBlock, sampleRate);
376 auto streamTime = r.time.getStart();
382 sampleRate, samplesPerBlock,
388 nodePlayer->setNumThreads ((
size_t) r.engine->
getEngineBehaviour().getNumberOfCPUsToUseForAudio() - 1);
393 playHead->setPosition (toSamples (streamTime, sampleRate));
396 playHeadState->update (toSamples ({ streamTime, streamTime + blockLength }, sampleRate));
399 auto leafNodesReady = [nodes =
getNodes (*nodePlayer->getNode(), VertexOrdering::postordering)]
401 for (
auto node : nodes)
408 while (! leafNodesReady())
410 juce::Thread::sleep (100);
413 return TRANS(
"Render cancelled");
426 return TRANS(
"Render cancelled");
428 if (streamTime > r.time.getEnd())
431 auto blockEnd = streamTime + blockLength;
432 const TimeRange streamTimeRange (streamTime, blockEnd);
438 currentTempoPosition.set (streamTime);
440 renderingBuffer.
clear();
441 blockMidiBuffer.clear();
442 const auto referenceSampleRange = toSamples (streamTimeRange, sampleRate);
444 (choc::buffer::ChannelCount) renderingBuffer.
getNumChannels(), (choc::buffer::FrameCount) referenceSampleRange.getLength());
446 nodePlayer->process ({ (choc::buffer::FrameCount) referenceSampleRange.getLength(), referenceSampleRange, { destView, blockMidiBuffer} });
449 for (
auto& m : blockMidiBuffer)
452 eventPos.
set (TimePosition::fromSeconds (m.getTimeStamp()) + (streamTime - r.time.getStart()));
457 streamTime = blockEnd;
459 progress =
juce::jlimit (0.0f, 1.0f, (
float) ((streamTime - r.time.getStart()) / r.time.getLength()));
465 return TRANS(
"No MIDI found to render");
467 if (! Renderer::RenderTask::addMidiMetaDataAndWriteToFile (r.destFile, std::move (outputSequence), r.edit->
tempoSequence))
468 return TRANS(
"Unable to write to destination file");
Type getMagnitude(int channel, int startSample, int numSamples) const noexcept
int getNumChannels() const noexcept
Type *const * getArrayOfWritePointers() noexcept
File withFileExtension(StringRef newExtension) const
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
int getNumEvents() const noexcept
static Range withStartAndLength(const ValueType startValue, const ValueType length) noexcept
static Result fail(const String &errorMessage) noexcept
bool shouldExit() const noexcept
static double getMillisecondCounterHiRes() noexcept
static int getThreadPoolStrategy()
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
static const int ticksPerQuarterNote
The number of ticks per quarter note.
static TimeDuration getMaximumLength()
Returns the maximum length an Edit can be.
void updateModifierTimers(TimePosition editTime, int numSamples) const
Updates all the ModifierTimers with a given edit time and number of samples.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
static juce::String renderMidi(Renderer::RenderTask &, Renderer::Parameters &, std::unique_ptr< tracktion::graph::Node >, std::unique_ptr< tracktion::graph::PlayHead >, std::unique_ptr< tracktion::graph::PlayHeadState >, std::unique_ptr< ProcessState >, std::atomic< float > &progressToUpdate)
Renders the MIDI of an Edit to a sequence.
bool renderNextBlock(std::atomic< float > &progressToUpdate)
Renders the next block of audio.
NodeRenderContext(Renderer::RenderTask &, Renderer::Parameters &, std::unique_ptr< tracktion::graph::Node >, std::unique_ptr< tracktion::graph::PlayHead >, std::unique_ptr< tracktion::graph::PlayHeadState >, std::unique_ptr< ProcessState >, juce::AudioFormatWriter::ThreadedWriter::IncomingDataReceiver *sourceToUpdate)
Creates a context to render a Node.
~NodeRenderContext()
Destructor.
Task that actually performs the render operation in blocks.
bool isPlayContextActive() const
Returns true if this Edit is attached to the DeviceManager for playback.
Main graph Node processor class.
virtual std::vector< Node * > getDirectInputNodes()
Should return all the inputs directly feeding in to this node.
virtual bool isReadyToProcess()=0
Should return true when this node is ready to be processed.
void prepareForNextBlock(juce::Range< int64_t > referenceSampleRange)
Call before processing the next block, used to reset the process status.
#define TRANS(stringLiteral)
constexpr Type jmin(Type a, Type b)
constexpr Type jmax(Type a, Type b)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
LockFreeMultiThreadedNodePlayer::ThreadPoolCreator getPoolCreatorFunction(ThreadPoolStrategy poolType)
Returns a function to create a ThreadPool for the given stategy.
ThreadPoolStrategy
Available strategies for thread pools.
VertexOrdering
Specifies the ordering algorithm.
std::vector< Node * > getNodes(Node &node, VertexOrdering vertexOrdering)
Returns all the nodes in a Node graph in the order given by vertexOrdering.
constexpr double inSeconds() const
Returns the TimeDuration as a number of seconds.
A Sequence::Position is an iterator through a Sequence.
double getPPQTime() const noexcept
Returns the position as a PPQ time.
void set(TimePosition)
Sets the Position to a new time.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.