11namespace tracktion {
inline namespace engine
17 while (! f->baseClassNeedsInitialising())
18 f->baseClassDeinitialise();
36 cnp.blockSize = r.blockSizeForAudio;
37 cnp.allowedClips = r.allowedClips.
isEmpty() ? nullptr : &r.allowedClips;
38 cnp.allowedTracks = r.tracksToDo.
isZero() ? nullptr : &tracksToDo;
39 cnp.forRendering =
true;
40 cnp.includePlugins = r.usePlugins;
41 cnp.includeMasterPlugins = r.useMasterPlugins;
42 cnp.addAntiDenormalisationNoise = r.addAntiDenormalisationNoise;
43 cnp.includeBypassedPlugins =
false;
53 std::move (node), std::move (playHead), std::move (playHeadState), std::move (processState),
54 progressToUpdate, thumbnail);
63 while (ditherers.size() < num)
66 for (
auto& d : ditherers)
74 for (
int i = 0; i < numChannels; ++i)
75 ditherers.getReference (i).process (buffer.
getWritePointer (i), numSamples);
82static void addAcidInfo (
Edit& edit, Renderer::Parameters& r)
84 if (r.destFile.hasFileExtension (
".wav") && r.endAllowance == 0s)
86 auto& pitch = edit.
pitchSequence.getPitchAt (r.time.getStart());
96 auto beats = tempo.getBpm() * (r.time.getLength().inSeconds() / 60);
98 if (std::abs (beats -
int (beats)) < 0.001)
114Renderer::RenderTask::RenderTask (
const juce::String& taskDescription,
115 const Renderer::Parameters& r,
118 : ThreadPoolJobWithProgress (taskDescription),
120 progress (progressToUpdate == nullptr ? progressInternal : *progressToUpdate),
121 sourceToUpdate (source)
130 CreateNodeParams cnp { *processState };
131 cnp.sampleRate = r.sampleRateForAudio;
132 cnp.blockSize = r.blockSizeForAudio;
133 cnp.allowedClips = r.allowedClips.isEmpty() ? nullptr : &r.allowedClips;
134 cnp.allowedTracks = r.tracksToDo.isZero() ? nullptr : &tracksToDo;
135 cnp.forRendering =
true;
136 cnp.includePlugins = r.usePlugins;
137 cnp.includeMasterPlugins = r.useMasterPlugins;
138 cnp.addAntiDenormalisationNoise = r.addAntiDenormalisationNoise;
139 cnp.includeBypassedPlugins =
false;
140 cnp.allowClipSlots = r.edit->engine.getEngineBehaviour().areClipSlotsEnabled();
142 callBlocking ([
this, &r, &cnp] { graphNode =
createNodeForEdit (*r.edit, cnp); });
145Renderer::RenderTask::RenderTask (
const juce::String& taskDescription,
146 const Renderer::Parameters& rp,
153 : ThreadPoolJobWithProgress (taskDescription),
155 graphNode (
std::
move (n)), playHead (
std::
move (playHead_)), playHeadState (
std::
move (playHeadState_)), processState (
std::
move (processState_)),
156 progress (progressToUpdate == nullptr ? progressInternal : *progressToUpdate),
157 sourceToUpdate (source)
161Renderer::RenderTask::~RenderTask()
170 if (params.createMidiFile)
172 else if (! renderAudio (params))
173 return jobNeedsRunningAgain;
175 return jobHasFinished;
179bool Renderer::RenderTask::performNormalisingAndTrimming (
const Renderer::Parameters& target,
180 const Renderer::Parameters& intermediate)
184 if (target.trimSilenceAtEnds)
186 setJobName (
TRANS(
"Trimming silence") +
"...");
189 auto doneRange = AudioFileUtils::trimSilence (params.edit->engine, intermediate.destFile, -70.0f);
191 if (doneRange.getLength() == 0)
193 errorMessage =
TRANS(
"The rendered section was completely silent - no file was produced");
197 AudioFileUtils::applyBWAVStartTime (intermediate.destFile,
198 (SampleCount) tracktion::toSamples (intermediate.time.getStart(), intermediate.sampleRateForAudio)
199 + doneRange.getStart());
202 if (target.shouldNormalise || target.shouldNormaliseByRMS)
203 setJobName (
TRANS(
"Normalising") +
"...");
206 intermediate.destFile));
208 if (reader ==
nullptr)
210 errorMessage =
TRANS(
"Couldn't read intermediate file");
214 AudioFileWriter writer (AudioFile (params.edit->engine, target.destFile),
215 target.audioFormat, (
int) reader->numChannels, target.sampleRateForAudio,
216 target.bitDepth, target.metadata, target.quality);
218 if (! writer.isOpen())
220 errorMessage =
TRANS(
"Couldn't write to target file");
227 if (target.shouldNormaliseByRMS)
228 gain =
juce::jlimit (0.0f, 100.0f, dbToGain (target.normaliseToLevelDb) / (intermediate.resultRMS + 2.0f / 32768.0f));
229 else if (target.shouldNormalise)
230 gain =
juce::jlimit (0.0f, 100.0f, dbToGain (target.normaliseToLevelDb) * (1.0f / (intermediate.resultMagnitude * 1.005f + 2.0f / 32768.0f)));
232 Ditherers ditherers ((
int) reader->numChannels, target.bitDepth);
234 const int blockSize = 16384;
237 for (SampleCount pos = 0; pos < reader->lengthInSamples;)
239 auto numLeft = reader->lengthInSamples - pos;
242 reader->read (&tempBuffer, 0, samps, pos,
true, reader->numChannels > 1);
244 tempBuffer.applyGain (0, samps, gain);
246 if (target.ditheringEnabled && target.bitDepth < 32)
247 ditherers.apply (tempBuffer, samps);
249 writer.appendBuffer (tempBuffer, samps);
257bool Renderer::RenderTask::renderAudio (Renderer::Parameters& r)
261 if (! nodeRenderContext)
264 std::move (graphNode),
265 std::move (playHead),
266 std::move (playHeadState),
267 std::move (processState),
270 if (! nodeRenderContext)
272 errorMessage =
NEEDS_TRANS(
"Quit message or timeout occurred during render initialisation");
276 if (! nodeRenderContext->getStatus().wasOk())
278 errorMessage = nodeRenderContext->getStatus().getErrorMessage();
283 if (! nodeRenderContext->renderNextBlock (progress))
286 nodeRenderContext.reset();
292void Renderer::RenderTask::flushAllPlugins (
const Plugin::Array& plugins,
293 double sampleRate,
int samplesPerBlock)
298 for (
int i = 0; i < plugins.size(); ++i)
300 if (
auto ep =
dynamic_cast<ExternalPlugin*
> (plugins.getUnchecked (i).get()))
302 if (! ep->baseClassNeedsInitialising() && ep->isEnabled())
306 if (ep->getNumInputs() == 0 && ep->getNumOutputs() == 0)
309 auto blockLength = samplesPerBlock / (
double) sampleRate;
310 auto blocks = (
int) (20 / blockLength + 1);
312 for (
int j = 0; j < blocks; j++)
314 buffer.setSize (
std::max (ep->getNumInputs(), ep->getNumOutputs()), samplesPerBlock);
318 ep->applyToBuffer (PluginRenderContext (&buffer, channels, 0, samplesPerBlock,
322 if (isAudioDataAlmostSilent (buffer.getReadPointer (0), samplesPerBlock))
330void Renderer::RenderTask::setAllPluginsRealtime (
const Plugin::Array& plugins,
bool realtime)
333 for (
auto af : plugins)
334 if (auto ep = dynamic_cast<ExternalPlugin*> (af))
336 if (auto p = ep->getAudioPluginInstance())
337 p->setNonRealtime (! realtime);
341bool Renderer::RenderTask::renderMidi (Renderer::Parameters& r)
344 errorMessage = NodeRenderContext::renderMidi (*
this, r,
345 std::move (graphNode),
346 std::move (playHead),
347 std::move (playHeadState),
348 std::move (processState),
350 return errorMessage.isEmpty();
366 for (
int i = 0; i < tempoSequence.getNumTempos(); ++i)
368 auto ts = tempoSequence.getTempo (i);
369 auto& matchingTimeSig = ts->getMatchingTimeSig();
371 currentTempoPosition.set (ts->getStartTime());
373 const double time = Edit::ticksPerQuarterNote * currentTempoPosition.getPPQTime();
374 const double beatLengthMicrosecs = 60000000.0 / ts->getBpm();
375 const double microsecondsPerQuarterNote = beatLengthMicrosecs * matchingTimeSig.denominator / 4.0;
378 matchingTimeSig.denominator);
379 m.setTimeStamp (time);
383 m.setTimeStamp (time);
388 if (name.startsWith (
"."))
389 name = name.fromFirstOccurrenceOf (
".",
false,
false).upToLastOccurrenceOf (
"_temp",
false,
false);
420 auto& engine = edit.
engine;
430 TransportControl::stopAllTransports (engine,
false,
true);
436 r.destFile = outputFile;
442 r.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (engine);
443 r.usePlugins = usePlugins;
444 r.useMasterPlugins = usePlugins;
445 r.tracksToDo = tracksToDo;
446 r.allowedClips = clips;
450 addAcidInfo (edit, r);
452 if (
auto task = render_utils::createRenderTask (r, taskDescription,
nullptr,
nullptr))
456 engine.getUIBehaviour().runTaskWithProgressBar (*task);
480 jassert (r.sampleRateForAudio > 7000);
484 TransportControl::stopAllTransports (*r.engine,
false,
true);
494 if (
auto task = render_utils::createRenderTask (r, taskDescription,
nullptr,
nullptr))
501 if (task->errorMessage.isNotEmpty())
504 ui.showWarningMessage (task->errorMessage);
511 if (task->getCurrentTaskProgress() >= 0.9f && task->errorMessage.isNotEmpty())
512 ui.showWarningMessage (task->errorMessage);
516 ui.showWarningMessage (
TRANS(
"Couldn't render, as the selected region was empty"));
537 if (proj->isReadOnly())
543 if (r.category == ProjectItem::Category::none)
555 if (renderedFile.existsAsFile())
561 return proj->createNewItem (renderedFile,
562 r.createMidiFile ? ProjectItem::midiItemType()
563 : ProjectItem::waveItemType(),
564 renderedFile.getFileNameWithoutExtension().trim(),
576 int blockSizeForAudio,
double sampleRateForAudio)
582 TransportControl::stopAllTransports (edit.
engine,
false,
true);
590 r.blockSizeForAudio = blockSizeForAudio;
591 r.sampleRateForAudio = sampleRateForAudio;
593 r.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (edit.
engine);
594 r.tracksToDo = tracksToDo;
596 if (
auto task = render_utils::createRenderTask (r, taskDescription,
nullptr,
nullptr))
600 result.peak = task->params.resultMagnitude;
601 result.average = task->params.resultRMS;
602 result.audioDuration = task->params.resultAudioDuration;
615 if (file.isDirectory() || ! file.hasWriteAccess())
618 TRANS(
"Couldn't write to this file - check that it's not read-only and that you have permission to access it"));
622 if (! file.getParentDirectory().createDirectory())
624 ui.showWarningAlert (
TRANS(
"Rendering"),
625 TRANS(
"Couldn't render - couldn't create the directory specified"));
631 if (! ui.showOkCancelAlertBox (
TRANS(
"Rendering"),
632 TRANS(
"The file\n\nXZZX\n\nalready exists - are you sure you want to overwrite it?")
633 .replace (
"XZZX", file.getFullPathName()),
638 if (! (file.deleteFile() && file.hasWriteAccess()))
640 ui.showWarningAlert (
TRANS(
"Rendering"),
641 TRANS(
"Couldn't render - the file chosen didn't have write permission"));
bool isEmpty() const noexcept
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
int findNextSetBit(int startIndex) const noexcept
bool isZero() const noexcept
int countNumberOfSetBits() const noexcept
bool openedOk() const noexcept
bool hasWriteAccess() const
bool existsAsFile() const
String getFileNameWithoutExtension() const
bool hasFileExtension(StringRef extensionToTest) const
static void JUCE_CALLTYPE disableDenormalisedNumberSupport(bool shouldDisable=true) noexcept
void addTrack(const MidiMessageSequence &trackSequence)
void setTicksPerQuarterNote(int ticksPerQuarterNote) noexcept
bool writeTo(OutputStream &destStream, int midiFileType=1) const
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
void updateMatchedPairs() noexcept
int getNumEvents() const noexcept
static MidiMessage tempoMetaEvent(int microsecondsPerQuarterNote) noexcept
static MidiMessage timeSignatureMetaEvent(int numerator, int denominator)
static MidiMessage textMetaEvent(int type, StringRef text)
ObjectClass * add(ObjectClass *newObject)
String quoted(juce_wchar quoteCharacter='"') const
static Time JUCE_CALLTYPE getCurrentTime() noexcept
String toString(bool includeDate, bool includeTime, bool includeSeconds=true, bool use24HourClock=false) const
The Tracktion Edit class!
PitchSequence pitchSequence
The global PitchSequence of this Edit.
TimeDuration getLength() const
Returns the end time of last clip.
juce::String getName()
Returns the name of the Edit if a ProjectItem can be found for it.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
Engine & engine
A reference to the Engine.
virtual bool areClipSlotsEnabled()
If this returns false, ClipSlot Clips won't be included in the playback graph and arranger track clip...
The Engine is the central class for all tracktion sessions.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
static void turnOffAllPlugins(Edit &)
Deinitialises all the plugins for the Edit.
static juce::File renderToFile(const juce::String &taskDescription, const Parameters ¶ms)
Renders an Edit to a file given by the Parameters.
TimeSigSetting & getTimeSigAt(TimePosition) const
Returns the TimeSigSetting at a given position.
TempoSetting & getTempoAt(TimePosition) const
Returns the TempoSetting at the given position.
virtual void runTaskWithProgressBar(ThreadPoolJobWithProgress &)
Should run this task in the current window, with a progress bar, blocking until the task is done.
virtual void showWarningAlert(const juce::String &title, const juce::String &message)
Should display a dismissable alert window.
virtual void showWarningMessage(const juce::String &message)
Should display a temporary warning message.
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
juce::Array< Track * > toTrackArray(Edit &edit, const juce::BigInteger &tracksToAdd)
Returns an Array of Track[s] corresponding to the set bits of all tracks in an Edit.
double sampleRate
The sample rate to use.
juce::Array< Track * > getAllTracks(const Edit &edit)
Returns all the tracks in an Edit.
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
std::unique_ptr< tracktion::graph::Node > createNodeForEdit(EditPlaybackContext &epc, std::atomic< double > &audibleTimeToUpdate, const CreateNodeParams ¶ms)
Creates a Node to play back an Edit with live inputs and outputs.
juce::BigInteger toBitSet(const juce::Array< Track * > &tracks)
Returns the set of tracks as a BigInteger with each bit corresponding to the array of all tracks in a...
Project::Ptr getProjectForEdit(const Edit &e)
Tries to find the project that contains this edit (but may return nullptr!)
Plugin::Array getAllPlugins(const Edit &edit, bool includeMasterVolume)
Returns all the plugins in a given Edit.
Contains options for Edit Node content creation.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
Temporarily removes an Edit from the device manager, optionally re-adding it on destruction.
Temporarily solo isolates and unmutes some tracks.
Temporarily disables clip slots.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.