tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Renderer.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15{
16 for (auto f : getAllPlugins (edit, true))
17 while (! f->baseClassNeedsInitialising())
18 f->baseClassDeinitialise();
19}
20
21namespace render_utils
22{
24 std::atomic<float>* progressToUpdate,
26 {
27 auto tracksToDo = toTrackArray (*r.edit, r.tracksToDo);
28
29 // Initialise playhead and continuity
31 auto playHeadState = std::make_unique<tracktion::graph::PlayHeadState> (*playHead);
32 auto processState = std::make_unique<ProcessState> (*playHeadState, r.edit->tempoSequence);
33
34 CreateNodeParams cnp { *processState };
35 cnp.sampleRate = r.sampleRateForAudio;
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;
44 cnp.allowClipSlots = r.edit->engine.getEngineBehaviour().areClipSlotsEnabled();
45
47 callBlocking ([&r, &node, &cnp] { node = createNodeForEdit (*r.edit, cnp); });
48
49 if (! node)
50 return {};
51
53 std::move (node), std::move (playHead), std::move (playHeadState), std::move (processState),
54 progressToUpdate, thumbnail);
55 }
56}
57
58//==============================================================================
60{
61 Ditherers (int num, int bitDepth)
62 {
63 while (ditherers.size() < num)
64 ditherers.add ({});
65
66 for (auto& d : ditherers)
67 d.reset (bitDepth);
68 }
69
70 void apply (juce::AudioBuffer<float>& buffer, int numSamples)
71 {
72 auto numChannels = buffer.getNumChannels();
73
74 for (int i = 0; i < numChannels; ++i)
75 ditherers.getReference (i).process (buffer.getWritePointer (i), numSamples);
76 }
77
78 juce::Array<Ditherer> ditherers;
79};
80
81//==============================================================================
82static void addAcidInfo (Edit& edit, Renderer::Parameters& r)
83{
84 if (r.destFile.hasFileExtension (".wav") && r.endAllowance == 0s)
85 {
86 auto& pitch = edit.pitchSequence.getPitchAt (r.time.getStart());
87 auto& tempo = edit.tempoSequence.getTempoAt (r.time.getStart());
88 auto& timeSig = edit.tempoSequence.getTimeSigAt (r.time.getStart());
89
90 r.metadata.set (juce::WavAudioFormat::acidOneShot, "0");
91 r.metadata.set (juce::WavAudioFormat::acidRootSet, "1");
92 r.metadata.set (juce::WavAudioFormat::acidDiskBased, "1");
93 r.metadata.set (juce::WavAudioFormat::acidizerFlag, "1");
94 r.metadata.set (juce::WavAudioFormat::acidRootNote, juce::String (pitch.getPitch()));
95
96 auto beats = tempo.getBpm() * (r.time.getLength().inSeconds() / 60);
97
98 if (std::abs (beats - int (beats)) < 0.001)
99 {
100 r.metadata.set (juce::WavAudioFormat::acidStretch, "1");
102 r.metadata.set (juce::WavAudioFormat::acidDenominator, juce::String (timeSig.denominator.get()));
103 r.metadata.set (juce::WavAudioFormat::acidNumerator, juce::String (timeSig.numerator.get()));
104 r.metadata.set (juce::WavAudioFormat::acidTempo, juce::String (tempo.getBpm()));
105 }
106 else
107 {
108 r.metadata.set (juce::WavAudioFormat::acidStretch, "0");
109 }
110 }
111}
112
113//==============================================================================
114Renderer::RenderTask::RenderTask (const juce::String& taskDescription,
115 const Renderer::Parameters& r,
116 std::atomic<float>* progressToUpdate,
118 : ThreadPoolJobWithProgress (taskDescription),
119 params (r),
120 progress (progressToUpdate == nullptr ? progressInternal : *progressToUpdate),
121 sourceToUpdate (source)
122{
123 auto tracksToDo = toTrackArray (*r.edit, r.tracksToDo);
124
125 // Initialise playhead and continuity
127 playHeadState = std::make_unique<tracktion::graph::PlayHeadState> (*playHead);
128 processState = std::make_unique<ProcessState> (*playHeadState, r.edit->tempoSequence);
129
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();
141
142 callBlocking ([this, &r, &cnp] { graphNode = createNodeForEdit (*r.edit, cnp); });
143}
144
145Renderer::RenderTask::RenderTask (const juce::String& taskDescription,
146 const Renderer::Parameters& rp,
150 std::unique_ptr<ProcessState> processState_,
151 std::atomic<float>* progressToUpdate,
153 : ThreadPoolJobWithProgress (taskDescription),
154 params (rp),
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)
158{
159}
160
161Renderer::RenderTask::~RenderTask()
162{
163}
164
165juce::ThreadPoolJob::JobStatus Renderer::RenderTask::runJob()
166{
169
170 if (params.createMidiFile)
171 renderMidi (params);
172 else if (! renderAudio (params))
173 return jobNeedsRunningAgain;
174
175 return jobHasFinished;
176}
177
178//==============================================================================
179bool Renderer::RenderTask::performNormalisingAndTrimming (const Renderer::Parameters& target,
180 const Renderer::Parameters& intermediate)
181{
183
184 if (target.trimSilenceAtEnds)
185 {
186 setJobName (TRANS("Trimming silence") + "...");
187 progress = 0.94f;
188
189 auto doneRange = AudioFileUtils::trimSilence (params.edit->engine, intermediate.destFile, -70.0f);
190
191 if (doneRange.getLength() == 0)
192 {
193 errorMessage = TRANS("The rendered section was completely silent - no file was produced");
194 return false;
195 }
196
197 AudioFileUtils::applyBWAVStartTime (intermediate.destFile,
198 (SampleCount) tracktion::toSamples (intermediate.time.getStart(), intermediate.sampleRateForAudio)
199 + doneRange.getStart());
200 }
201
202 if (target.shouldNormalise || target.shouldNormaliseByRMS)
203 setJobName (TRANS("Normalising") + "...");
204
205 std::unique_ptr<juce::AudioFormatReader> reader (AudioFileUtils::createReaderFor (params.edit->engine,
206 intermediate.destFile));
207
208 if (reader == nullptr)
209 {
210 errorMessage = TRANS("Couldn't read intermediate file");
211 return false;
212 }
213
214 AudioFileWriter writer (AudioFile (params.edit->engine, target.destFile),
215 target.audioFormat, (int) reader->numChannels, target.sampleRateForAudio,
216 target.bitDepth, target.metadata, target.quality);
217
218 if (! writer.isOpen())
219 {
220 errorMessage = TRANS("Couldn't write to target file");
221 return false;
222 }
223
224 progress = 0.96f;
225 float gain = 1.0f;
226
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)));
231
232 Ditherers ditherers ((int) reader->numChannels, target.bitDepth);
233
234 const int blockSize = 16384;
235 juce::AudioBuffer<float> tempBuffer ((int) reader->numChannels, blockSize + 256);
236
237 for (SampleCount pos = 0; pos < reader->lengthInSamples;)
238 {
239 auto numLeft = reader->lengthInSamples - pos;
240 auto samps = int (std::min (juce::int64 (tempBuffer.getNumSamples()), std::min (juce::int64 (blockSize), numLeft)));
241
242 reader->read (&tempBuffer, 0, samps, pos, true, reader->numChannels > 1);
243
244 tempBuffer.applyGain (0, samps, gain);
245
246 if (target.ditheringEnabled && target.bitDepth < 32)
247 ditherers.apply (tempBuffer, samps);
248
249 writer.appendBuffer (tempBuffer, samps);
250 pos += samps;
251 }
252
253 return true;
254}
255
256//==============================================================================
257bool Renderer::RenderTask::renderAudio (Renderer::Parameters& r)
258{
260
261 if (! nodeRenderContext)
262 {
263 callBlocking ([&, this] { nodeRenderContext = std::make_unique<NodeRenderContext> (*this, r,
264 std::move (graphNode),
265 std::move (playHead),
266 std::move (playHeadState),
267 std::move (processState),
268 sourceToUpdate); });
269
270 if (! nodeRenderContext)
271 {
272 errorMessage = NEEDS_TRANS("Quit message or timeout occurred during render initialisation");
273 return true;
274 }
275
276 if (! nodeRenderContext->getStatus().wasOk())
277 {
278 errorMessage = nodeRenderContext->getStatus().getErrorMessage();
279 return true;
280 }
281 }
282
283 if (! nodeRenderContext->renderNextBlock (progress))
284 return false;
285
286 nodeRenderContext.reset();
287 progress = 1.0f;
288
289 return true;
290}
291
292void Renderer::RenderTask::flushAllPlugins (const Plugin::Array& plugins,
293 double sampleRate, int samplesPerBlock)
294{
296 juce::AudioBuffer<float> buffer (1, samplesPerBlock);
297
298 for (int i = 0; i < plugins.size(); ++i)
299 {
300 if (auto ep = dynamic_cast<ExternalPlugin*> (plugins.getUnchecked (i).get()))
301 {
302 if (! ep->baseClassNeedsInitialising() && ep->isEnabled())
303 {
304 ep->reset();
305
306 if (ep->getNumInputs() == 0 && ep->getNumOutputs() == 0)
307 continue;
308
309 auto blockLength = samplesPerBlock / (double) sampleRate;
310 auto blocks = (int) (20 / blockLength + 1);
311
312 for (int j = 0; j < blocks; j++)
313 {
314 buffer.setSize (std::max (ep->getNumInputs(), ep->getNumOutputs()), samplesPerBlock);
315 buffer.clear();
316 auto channels = juce::AudioChannelSet::canonicalChannelSet (buffer.getNumChannels());
317
318 ep->applyToBuffer (PluginRenderContext (&buffer, channels, 0, samplesPerBlock,
319 nullptr, 0.0,
320 TimeRange(), false, false, true, true));
321
322 if (isAudioDataAlmostSilent (buffer.getReadPointer (0), samplesPerBlock))
323 break;
324 }
325 }
326 }
327 }
328}
329
330void Renderer::RenderTask::setAllPluginsRealtime (const Plugin::Array& plugins, bool realtime)
331{
333 for (auto af : plugins)
334 if (auto ep = dynamic_cast<ExternalPlugin*> (af))
335 if (ep->isEnabled())
336 if (auto p = ep->getAudioPluginInstance())
337 p->setNonRealtime (! realtime);
338}
339
340//==============================================================================
341bool Renderer::RenderTask::renderMidi (Renderer::Parameters& r)
342{
344 errorMessage = NodeRenderContext::renderMidi (*this, r,
345 std::move (graphNode),
346 std::move (playHead),
347 std::move (playHeadState),
348 std::move (processState),
349 progress);
350 return errorMessage.isEmpty();
351}
352
353bool Renderer::RenderTask::addMidiMetaDataAndWriteToFile (juce::File destFile, juce::MidiMessageSequence outputSequence, const TempoSequence& tempoSequence)
354{
355 outputSequence.updateMatchedPairs();
356
357 if (outputSequence.getNumEvents() == 0)
358 return false;
359
360 if (juce::FileOutputStream out (destFile);
361 out.openedOk())
362 {
363 juce::MidiMessageSequence midiTempoSequence;
364 auto currentTempoPosition = createPosition (tempoSequence);
365
366 for (int i = 0; i < tempoSequence.getNumTempos(); ++i)
367 {
368 auto ts = tempoSequence.getTempo (i);
369 auto& matchingTimeSig = ts->getMatchingTimeSig();
370
371 currentTempoPosition.set (ts->getStartTime());
372
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;
376
377 auto m = juce::MidiMessage::timeSignatureMetaEvent (matchingTimeSig.numerator,
378 matchingTimeSig.denominator);
379 m.setTimeStamp (time);
380 midiTempoSequence.addEvent (m);
381
382 m = juce::MidiMessage::tempoMetaEvent (juce::roundToInt (microsecondsPerQuarterNote));
383 m.setTimeStamp (time);
384 midiTempoSequence.addEvent (m);
385 }
386
387 auto name = destFile.getFileNameWithoutExtension();
388 if (name.startsWith ("."))
389 name = name.fromFirstOccurrenceOf (".", false, false).upToLastOccurrenceOf ("_temp", false, false);
390
391 midiTempoSequence.addEvent (juce::MidiMessage::textMetaEvent (3, name));
392 outputSequence.addEvent (juce::MidiMessage::textMetaEvent (3, name));
393
395 mf.addTrack (midiTempoSequence);
396 mf.addTrack (outputSequence);
397
398 mf.setTicksPerQuarterNote (Edit::ticksPerQuarterNote);
399 mf.writeTo (out);
400
401 return true;
402 }
403
404 return false;
405}
406
407
408//==============================================================================
409bool Renderer::renderToFile (const juce::String& taskDescription,
410 const juce::File& outputFile,
411 Edit& edit,
412 TimeRange range,
413 const juce::BigInteger& tracksToDo,
414 bool usePlugins,
415 bool useACID,
416 juce::Array<Clip*> clips,
417 bool useThread)
418{
420 auto& engine = edit.engine;
421 const Edit::ScopedRenderStatus srs (edit, true);
422 Track::Array tracks;
423
424 for (auto bit = tracksToDo.findNextSetBit (0); bit != -1; bit = tracksToDo.findNextSetBit (bit + 1))
425 tracks.add (getAllTracks (edit)[bit]);
426
427 const FreezePointPlugin::ScopedTrackSoloIsolator isolator (edit, tracks);
428 const Renderer::ScopedClipSlotDisabler slotDisabler (edit, tracks);
429
430 TransportControl::stopAllTransports (engine, false, true);
431 turnOffAllPlugins (edit);
432
433 if (tracksToDo.countNumberOfSetBits() > 0)
434 {
435 Parameters r (edit);
436 r.destFile = outputFile;
437 r.audioFormat = edit.engine.getAudioFileFormatManager().getDefaultFormat();
438 r.bitDepth = 24;
439 r.sampleRateForAudio = edit.engine.getDeviceManager().getSampleRate();
440 r.blockSizeForAudio = edit.engine.getDeviceManager().getBlockSize();
441 r.time = range;
442 r.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (engine);
443 r.usePlugins = usePlugins;
444 r.useMasterPlugins = usePlugins;
445 r.tracksToDo = tracksToDo;
446 r.allowedClips = clips;
447 r.createMidiFile = outputFile.hasFileExtension (".mid");
448
449 if (useACID)
450 addAcidInfo (edit, r);
451
452 if (auto task = render_utils::createRenderTask (r, taskDescription, nullptr, nullptr))
453 {
454 if (useThread)
455 {
456 engine.getUIBehaviour().runTaskWithProgressBar (*task);
457 }
458 else
459 {
460 while (task->runJob() == juce::ThreadPoolJob::jobNeedsRunningAgain)
461 {}
462 }
463 }
464 }
465
466 turnOffAllPlugins (edit);
467
468 return outputFile.existsAsFile();
469}
470
471bool Renderer::renderToFile (Edit& edit, const juce::File& f, bool useThread)
472{
473 return renderToFile ({}, f, edit, { 0_tp, edit.getLength() }, toBitSet (getAllTracks (edit)), true, true, {}, useThread);
474}
475
476juce::File Renderer::renderToFile (const juce::String& taskDescription, const Parameters& r)
477{
479
480 jassert (r.sampleRateForAudio > 7000);
481 jassert (r.edit != nullptr);
482 jassert (r.engine != nullptr);
483
484 TransportControl::stopAllTransports (*r.engine, false, true);
485
486 turnOffAllPlugins (*r.edit);
487
488 if (r.tracksToDo.countNumberOfSetBits() > 0
489 && r.destFile.hasWriteAccess()
490 && ! r.destFile.isDirectory())
491 {
492 auto& ui = r.edit->engine.getUIBehaviour();
493
494 if (auto task = render_utils::createRenderTask (r, taskDescription, nullptr, nullptr))
495 {
496 ui.runTaskWithProgressBar (*task);
497 turnOffAllPlugins (*r.edit);
498
499 if (r.destFile.existsAsFile())
500 {
501 if (task->errorMessage.isNotEmpty())
502 {
503 r.destFile.deleteFile();
504 ui.showWarningMessage (task->errorMessage);
505 return {};
506 }
507
508 return r.destFile;
509 }
510
511 if (task->getCurrentTaskProgress() >= 0.9f && task->errorMessage.isNotEmpty())
512 ui.showWarningMessage (task->errorMessage);
513 }
514 else
515 {
516 ui.showWarningMessage (TRANS("Couldn't render, as the selected region was empty"));
517 }
518 }
519
520 return {};
521}
522
523ProjectItem::Ptr Renderer::renderToProjectItem (const juce::String& taskDescription, const Parameters& r)
524{
526
527 auto proj = getProjectForEdit (*r.edit);
528
529 if (proj == nullptr)
530 {
531 jassertfalse; // can't use this option if the edit isn't a member of a project
532 return {};
533 }
534
535 auto& ui = r.edit->engine.getUIBehaviour();
536
537 if (proj->isReadOnly())
538 {
539 ui.showWarningMessage (TRANS("Couldn't add the new file to the project (because this project is read-only)"));
540 return {};
541 }
542
543 if (r.category == ProjectItem::Category::none)
544 {
545 // This test comes from some very old code which leaks the file and doesn't add the project item
546 // if the category is 'none'... I've left the check in here but think it may be redundant - if you
547 // hit this assertion, please figure out why it's happening and refactor; otherwise, if you see this
548 // comment in a couple of years and the assertion never failed, please remove this whole check.
550 return {};
551 }
552
553 auto renderedFile = renderToFile (taskDescription, r);
554
555 if (renderedFile.existsAsFile())
556 {
557 juce::String desc;
558 desc << TRANS("Rendered from edit") << r.edit->getName().quoted() << " "
559 << TRANS("On") << " " << juce::Time::getCurrentTime().toString (true, true);
560
561 return proj->createNewItem (renderedFile,
562 r.createMidiFile ? ProjectItem::midiItemType()
563 : ProjectItem::waveItemType(),
564 renderedFile.getFileNameWithoutExtension().trim(),
565 desc,
566 r.category,
567 true);
568 }
569
570 return {};
571}
572
573//==============================================================================
574Renderer::Statistics Renderer::measureStatistics (const juce::String& taskDescription, Edit& edit,
575 TimeRange range, const juce::BigInteger& tracksToDo,
576 int blockSizeForAudio, double sampleRateForAudio)
577{
579 Statistics result;
580
581 const Edit::ScopedRenderStatus srs (edit, true);
582 TransportControl::stopAllTransports (edit.engine, false, true);
583
584 turnOffAllPlugins (edit);
585
586 if (tracksToDo.countNumberOfSetBits() > 0)
587 {
588 Parameters r (edit);
589 r.audioFormat = edit.engine.getAudioFileFormatManager().getDefaultFormat();
590 r.blockSizeForAudio = blockSizeForAudio;
591 r.sampleRateForAudio = sampleRateForAudio;
592 r.time = range;
593 r.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (edit.engine);
594 r.tracksToDo = tracksToDo;
595
596 if (auto task = render_utils::createRenderTask (r, taskDescription, nullptr, nullptr))
597 {
599
600 result.peak = task->params.resultMagnitude;
601 result.average = task->params.resultRMS;
602 result.audioDuration = task->params.resultAudioDuration;
603 }
604 }
605
606 turnOffAllPlugins (edit);
607
608 return result;
609}
610
611bool Renderer::checkTargetFile (Engine& e, const juce::File& file)
612{
613 auto& ui = e.getUIBehaviour();
614
615 if (file.isDirectory() || ! file.hasWriteAccess())
616 {
617 ui.showWarningAlert (TRANS("Couldn't render"),
618 TRANS("Couldn't write to this file - check that it's not read-only and that you have permission to access it"));
619 return false;
620 }
621
622 if (! file.getParentDirectory().createDirectory())
623 {
624 ui.showWarningAlert (TRANS("Rendering"),
625 TRANS("Couldn't render - couldn't create the directory specified"));
626 return false;
627 }
628
629 if (file.exists())
630 {
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()),
634 TRANS("Overwrite")))
635 return false;
636 }
637
638 if (! (file.deleteFile() && file.hasWriteAccess()))
639 {
640 ui.showWarningAlert (TRANS("Rendering"),
641 TRANS("Couldn't render - the file chosen didn't have write permission"));
642 return false;
643 }
644
645 return true;
646}
647
648}} // namespace tracktion { inline namespace engine
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 isDirectory() const
bool hasWriteAccess() const
bool existsAsFile() const
String getFileNameWithoutExtension() const
bool deleteFile() 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
static const char *const acidRootSet
static const char *const acidDiskBased
static const char *const acidOneShot
static const char *const acidizerFlag
static const char *const acidDenominator
static const char *const acidBeats
static const char *const acidNumerator
static const char *const acidStretch
static const char *const acidTempo
static const char *const acidRootNote
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 &params)
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.
T is_pointer_v
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
#define jassert(expression)
#define jassertfalse
typedef int
typedef double
T max(T... args)
T min(T... args)
T move(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
long long int64
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.
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 &params)
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.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.