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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_RenderOptions.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
14TimeDuration RenderOptions::findEndAllowance (Edit& edit,
16 juce::Array<Clip*>* clips)
17{
18 auto allTracks = getAllTracks (edit);
19 Plugin::Array plugins;
20
21 if (tracks != nullptr)
22 for (auto t : allTracks)
23 if (tracks->contains (t->itemID))
24 for (auto p : t->pluginList)
25 plugins.addIfNotAlreadyThere (p);
26
27 if (clips != nullptr)
28 for (auto c : *clips)
29 if (auto pl = c->getPluginList())
30 for (auto p : *pl)
31 plugins.addIfNotAlreadyThere (p);
32
33 TimeDuration allowance;
34
35 for (auto p : plugins)
36 {
37 auto tailLength = p->getTailLength();
38
39 if (tailLength == std::numeric_limits<double>::infinity())
40 tailLength = 0.0;
41
42 allowance = std::max (allowance, TimeDuration::fromSeconds (tailLength));
43 }
44
45 return allowance;
46}
47
48static TimeRange findTimeFromClips (const juce::Array<Clip*>& clips, TimeDuration endAllowance) noexcept
49{
50 auto time = findUnionOfEditTimeRanges (clips);
51 time = time.withEnd (time.getEnd() + endAllowance);
52 return time;
53}
54
55static bool isParentTrackSelected (SelectionManager& sm, Track& t)
56{
57 if (auto ft = t.getParentFolderTrack())
58 return sm.isSelected (ft) || isParentTrackSelected (sm, *ft);
59
60 return false;
61}
62
63static juce::BigInteger getSelectedTracks (Edit& edit, SelectionManager& sm)
64{
66 int i = 0;
67
68 for (auto t : getAllTracks (edit))
69 {
70 if (sm.isSelected (t))
71 b.setBit (i);
72
73 if (! t->isPartOfSubmix() && isParentTrackSelected (sm, *t))
74 b.setBit (i);
75
76 ++i;
77 }
78
79 return b;
80}
81
82//==============================================================================
83void RenderOptions::loadFromUserSettings()
84{
85 auto& storage = engine.getPropertyStorage();
86
87 if (isRender())
88 {
89 format = (TargetFileFormat) static_cast<int> (storage.getProperty (SettingID::renderFormat, (int) wav));
90
91 if (format != wav || format != aiff)
92 format = wav;
93
94 sampleRate = engine.getDeviceManager().getSampleRate();
95 bitDepth = storage.getProperty (SettingID::trackRenderBits, 16);
96 usePlugins = storage.getProperty (SettingID::bypassFilters, true);
97
98 if (isTrackRender())
99 markedRegion = storage.getProperty (SettingID::markedRegion, false);
100 }
101 else if (isExportAll())
102 {
103 format = (TargetFileFormat) static_cast<int> (storage.getProperty (SettingID::exportFormat, (int) wav));
104 selectedClips = storage.getProperty (SettingID::renderOnlySelectedClips, false);
105 markedRegion = storage.getProperty (SettingID::renderOnlyMarked, false);
106 normalise = storage.getProperty (SettingID::renderNormalise, false);
107 adjustBasedOnRMS = storage.getProperty (SettingID::renderRMS, false);
108 rmsLevelDb = storage.getProperty (SettingID::renderRMSLevelDb, -12.0);
109 peakLevelDb = storage.getProperty (SettingID::renderPeakLevelDb, 0.0);
110 removeSilence = storage.getProperty (SettingID::renderTrimSilence, false);
111 stereo = storage.getProperty (SettingID::renderStereo, true);
112 sampleRate = engine.getDeviceManager().getSampleRate();
113 bitDepth = storage.getProperty (SettingID::renderBits, 16);
114 dither = storage.getProperty (SettingID::renderDither, true);
115 qualityIndex = storage.getProperty (SettingID::quality, 5);
116 addMetadata = storage.getProperty (SettingID::addId3Info, false);
117 addAcidMetadata = storage.getProperty (SettingID::addAcidMetadata, false);
118 realTime = storage.getProperty (SettingID::realtime, false);
119 usePlugins = storage.getProperty (SettingID::passThroughFilters, true);
120 }
121 else if (isEditClipRender())
122 {
123 sampleRate = engine.getDeviceManager().getSampleRate();
124 bitDepth = storage.getProperty (SettingID::editClipRenderBits, 16);
125 dither = storage.getProperty (SettingID::editClipRenderDither, true);
126 realTime = storage.getProperty (SettingID::editClipRealtime, false);
127 stereo = storage.getProperty (SettingID::editClipRenderStereo, true);
128 normalise = storage.getProperty (SettingID::editClipRenderNormalise, false);
129 adjustBasedOnRMS = storage.getProperty (SettingID::editClipRenderRMS, false);
130 rmsLevelDb = storage.getProperty (SettingID::editClipRenderRMSLevelDb, -12.0);
131 peakLevelDb = storage.getProperty (SettingID::editClipRenderPeakLevelDb, 0.0);
132 usePlugins = storage.getProperty (SettingID::editClipPassThroughFilters, true);
133 addAcidMetadata = storage.getProperty (SettingID::addAcidMetadata, false);
134
135 return;
136 }
137
138 updateDefaultFilename (nullptr);
139}
140
141void RenderOptions::saveToUserSettings()
142{
143 auto& storage = engine.getPropertyStorage();
144
145 if (isTrackRender() || isClipRender() || isMidiRender())
146 {
147 storage.setProperty (SettingID::renderFormat, (int) format);
148 storage.setProperty (SettingID::trackRenderSampRate, sampleRate.get());
149 storage.setProperty (SettingID::trackRenderBits, bitDepth.get());
150 storage.setProperty (SettingID::bypassFilters, usePlugins.get());
151
152 if (isTrackRender())
153 storage.setProperty (SettingID::markedRegion, markedRegion.get());
154 }
155 else if (isEditClipRender())
156 {
157 storage.setProperty (SettingID::editClipRenderSampRate, sampleRate.get());
158 storage.setProperty (SettingID::editClipRenderBits, bitDepth.get());
159 storage.setProperty (SettingID::editClipRenderDither, dither.get());
160 storage.setProperty (SettingID::editClipRealtime, realTime.get());
161 storage.setProperty (SettingID::editClipRenderStereo, stereo.get());
162 storage.setProperty (SettingID::editClipRenderNormalise, normalise.get());
163 storage.setProperty (SettingID::editClipRenderRMS, adjustBasedOnRMS.get());
164 storage.setProperty (SettingID::editClipRenderRMSLevelDb, rmsLevelDb.get());
165 storage.setProperty (SettingID::editClipRenderPeakLevelDb, peakLevelDb.get());
166 storage.setProperty (SettingID::editClipPassThroughFilters, usePlugins.get());
167 storage.setProperty (SettingID::addAcidMetadata, addAcidMetadata.get());
168 }
169 else if (isExportAll())
170 {
171 storage.setProperty (SettingID::exportFormat, (int) format);
172 storage.setProperty (SettingID::renderOnlySelectedClips, selectedClips.get());
173 storage.setProperty (SettingID::renderOnlyMarked, markedRegion.get());
174 storage.setProperty (SettingID::renderNormalise, normalise.get());
175 storage.setProperty (SettingID::renderRMS, adjustBasedOnRMS.get());
176 storage.setProperty (SettingID::renderRMSLevelDb, rmsLevelDb.get());
177 storage.setProperty (SettingID::renderPeakLevelDb, peakLevelDb.get());
178 storage.setProperty (SettingID::renderTrimSilence, removeSilence.get());
179 storage.setProperty (SettingID::renderSampRate, sampleRate.get());
180 storage.setProperty (SettingID::renderStereo, stereo.get());
181 storage.setProperty (SettingID::renderBits, bitDepth.get());
182 storage.setProperty (SettingID::renderDither, dither.get());
183 storage.setProperty (SettingID::quality, qualityIndex.get());
184 storage.setProperty (SettingID::addId3Info, addMetadata.get());
185 storage.setProperty (SettingID::addAcidMetadata, addAcidMetadata.get());
186 storage.setProperty (SettingID::realtime, realTime.get());
187 storage.setProperty (SettingID::passThroughFilters, usePlugins.get());
188 }
189}
190
191//==============================================================================
193 TimeRange timeRangeToRender)
194{
195 Renderer::Parameters p (edit);
196
197 // First get the correct renderer params
198 if (isMidiRender())
199 {
200 if (auto mc = dynamic_cast<MidiClip*> (allowedClips.getFirst()))
201 p = getRenderParameters (*mc);
202 else
203 return {};
204 }
205 else
206 {
207 p = getRenderParameters (edit, sm, timeRangeToRender);
208 }
209
210 // And then fill in any specific params
211 p.category = isRender() ? ProjectItem::Category::rendered
212 : ProjectItem::Category::exports;
213
214 if (isClipRender())
215 p.allowedClips = allowedClips;
216
217 if (isTrackRender())
218 p.endAllowance = markedRegion ? 0.0s : 10.0s;
219
220 if (p.addAcidMetadata)
221 addAcidInfo (edit, p);
222
223 return (p.audioFormat != nullptr || p.createMidiFile)
224 ? EditRenderJob::getOrCreateRenderJob (edit.engine, p, false, false, false)
225 : nullptr;
226}
227
228void RenderOptions::relinkCachedValues (juce::UndoManager* um)
229{
230 type.referTo (state, IDs::renderType, um, RenderType::allExport);
231 tracksProperty.referTo (state, IDs::renderTracks, um, {});
232 createMidiFile.referTo (state, IDs::renderCreateMidiFile, um, false);
233 format.referTo (state, IDs::renderFormat, um, wav);
234
235 stereo.referTo (state, IDs::renderStereo, um, true);
236
237 sampleRate.referTo (state, IDs::renderSampleRate, um, 44100.0);
238 bitDepth.referTo (state, IDs::renderBitDepth, um, 32);
239 qualityIndex.referTo (state, IDs::renderQualityIndex, um, 5);
240 rmsLevelDb.referTo (state, IDs::renderRMSLevelDb, um, 0.0);
241 peakLevelDb.referTo (state, IDs::renderPeakLevelDb, um, 0.0);
242
243 removeSilence.referTo (state, IDs::renderRemoveSilence, um, false);
244 normalise.referTo (state, IDs::renderNormalise, um, false);
245 dither.referTo (state, IDs::renderDither, um, false);
246 adjustBasedOnRMS.referTo (state, IDs::renderAdjustBasedOnRMS, um, false);
247 markedRegion.referTo (state, IDs::renderMarkedRegion, um, false);
248 selectedTracks.referTo (state, IDs::renderSelectedTracks, um, false);
249 selectedClips.referTo (state, IDs::renderSelectedClips, um, false);
250 tracksToSeparateFiles.referTo (state, IDs::renderTracksToSeparateFiles, um, false);
251 realTime.referTo (state, IDs::renderRealTime, um, false);
252 usePlugins.referTo (state, IDs::renderPlugins, um, true);
253
254 addRenderOptions.referTo (state, IDs::renderOptions, um, none);
255 addRenderToLibrary.referTo (state, IDs::addRenderToLibrary, um, false);
256 reverseRender.referTo (state, IDs::reverseRender, um, false);
257 addMetadata.referTo (state, IDs::renderAddMetadata, um, false);
258 addAcidMetadata.referTo (state, IDs::addAcidMetadata, um, false);
259
260 sampleRate = engine.getDeviceManager().getSampleRate();
261 tracks = EditItemID::parseStringList (tracksProperty);
262 updateHash();
263}
264
265void RenderOptions::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i)
266{
267 if (v == state && i == IDs::renderTracks)
268 {
269 tracks = EditItemID::parseStringList (tracksProperty);
270 updateHash();
271 }
272}
273
274//==============================================================================
275static juce::StringPairArray getMetadata (Edit& edit)
276{
277 juce::StringPairArray metadataList;
278 auto metadata = edit.getEditMetadata();
279
280 if (metadata.album.isNotEmpty()) metadataList.set ("id3album", metadata.album);
281 if (metadata.artist.isNotEmpty()) metadataList.set ("id3artist", metadata.artist);
282 if (metadata.comment.isNotEmpty()) metadataList.set ("id3comment", metadata.comment);
283 if (metadata.date.isNotEmpty()) metadataList.set ("id3date", metadata.date);
284 if (metadata.genre.isNotEmpty()) metadataList.set ("id3genre", metadata.genre);
285 if (metadata.title.isNotEmpty()) metadataList.set ("id3title", metadata.title);
286 if (metadata.trackNumber.isNotEmpty()) metadataList.set ("id3trackNumber", metadata.trackNumber);
287
288 return metadataList;
289}
290
291Renderer::Parameters RenderOptions::getRenderParameters (Edit& edit)
292{
293 return getRenderParameters (edit, nullptr, {});
294}
295
297 TimeRange markedRegionTime)
298{
299 Renderer::Parameters params (edit);
300
301 params.destFile = destFile;
302 params.createMidiFile = format == midi;
303 params.audioFormat = getAudioFormat();
304
305 params.bitDepth = bitDepth;
306 params.blockSizeForAudio = edit.engine.getDeviceManager().getBlockSize();
307 params.sampleRateForAudio = sampleRate;
308 params.shouldNormalise = normalise;
309 params.trimSilenceAtEnds = removeSilence;
310 params.shouldNormaliseByRMS = adjustBasedOnRMS;
311 params.normaliseToLevelDb = (float) (adjustBasedOnRMS ? rmsLevelDb : peakLevelDb);
312 params.canRenderInMono = true;
313 params.mustRenderInMono = ! stereo;
314 params.usePlugins = (params.createMidiFile || (isTrackRender() || isClipRender() || isEditClipRender())) ? usePlugins : true;
315 params.useMasterPlugins = isRender() ? false : ((params.createMidiFile || (isEditClipRender())) ? usePlugins : true);
316 params.realTimeRender = realTime;
317 params.ditheringEnabled = dither;
318 params.quality = qualityIndex;
319 params.category = ProjectItem::Category::rendered;
320 params.separateTracks = tracksToSeparateFiles;
321 params.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (edit.engine);
322
323 if (! isMarkedRegionBigEnough (markedRegionTime))
324 markedRegion = false;
325
326 if (markedRegion)
327 params.time = markedRegionTime;
328 else
329 params.time = { TimePosition(), edit.getLength() };
330
331 auto allTracks = getAllTracks (edit);
332
333 if (isRender())
334 {
335 for (int i = allTracks.size(); --i >= 0;)
336 if (tracks.contains (allTracks.getUnchecked (i)->itemID))
337 params.tracksToDo.setBit (i);
338
339 if (isClipRender() || isMidiRender())
340 {
341 params.allowedClips = allowedClips;
342 params.endAllowance = usePlugins ? findEndAllowance (edit, &tracks, &allowedClips) : 0.0s;
343 params.time = params.time.getIntersectionWith (findTimeFromClips (params.allowedClips, params.endAllowance));
344 }
345 }
346 else if (selectedClips)
347 {
348 if (selectionManager != nullptr)
349 {
350 if (selectionManager->containsType<Clip>())
351 {
352 for (int i = allTracks.size(); --i >= 0;)
353 if (allTracks.getUnchecked (i)->isAudioTrack())
354 params.tracksToDo.setBit (i);
355
356 auto clips = selectionManager->getItemsOfType<Clip>();
357
358 for (auto clip : clips)
359 params.allowedClips.add (clip);
360
361 auto time = findTimeFromClips (clips, params.endAllowance);
362 params.time = params.time.getIntersectionWith (time);
363 }
364 }
365 else
366 {
367 jassertfalse; // need a selection manager if the user can enable this option!
368 }
369 }
370 else if (selectedTracks)
371 {
372 if (selectionManager != nullptr)
373 params.tracksToDo = getSelectedTracks (edit, *selectionManager);
374 else
375 jassertfalse; // need a selection manager if the user can enable this option!
376 }
377
378 if (params.tracksToDo.isZero())
379 params.tracksToDo.setRange (0, allTracks.size(), true);
380
381 if (addMetadata)
382 params.metadata = getMetadata (edit);
383
384 params.addAcidMetadata = addAcidMetadata;
385
386 return params;
387}
388
390{
391 Renderer::Parameters params (clip.edit);
392
393 params.destFile = destFile;
394 params.createMidiFile = format == midi;
395 params.audioFormat = getAudioFormat();
396
397 params.bitDepth = bitDepth;
398 params.blockSizeForAudio = clip.edit.engine.getDeviceManager().getBlockSize();
399 params.sampleRateForAudio = sampleRate;
400 params.shouldNormalise = normalise;
401 params.trimSilenceAtEnds = removeSilence;
402 params.shouldNormaliseByRMS = adjustBasedOnRMS;
403 params.normaliseToLevelDb = (float) (adjustBasedOnRMS ? rmsLevelDb : peakLevelDb);
404 params.canRenderInMono = true;
405 params.mustRenderInMono = ! stereo;
406 params.usePlugins = usePlugins;
407 params.useMasterPlugins = usePlugins;
408 params.realTimeRender = realTime;
409 params.ditheringEnabled = dither;
410 params.quality = qualityIndex;
411 params.category = ProjectItem::Category::rendered;
412 params.separateTracks = tracksToSeparateFiles;
413 params.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (clip.edit.engine);
414
415 if (const EditSnapshot::Ptr snapshot = clip.getEditSnapshot())
416 {
417 params.time = { TimePosition(), snapshot->getLength() };
418 params.tracksToDo.setRange (0, snapshot->getNumTracks(), true);
419
420 const bool markedBigEnough = snapshot->getMarkIn() < snapshot->getMarkOut() + 0.05s;
421
422 if (markedRegion && snapshot->areMarksActive() && markedBigEnough)
423 params.time = { snapshot->getMarkIn(), snapshot->getMarkOut() };
424 }
425
426 return params;
427}
428
429Renderer::Parameters RenderOptions::getRenderParameters (MidiClip& clip)
430{
431 Renderer::Parameters params (clip.edit);
432
433 params.destFile = destFile;
434 params.createMidiFile = format == midi;
435 params.audioFormat = getAudioFormat();
436
437 params.bitDepth = bitDepth;
438 params.blockSizeForAudio = clip.edit.engine.getDeviceManager().getBlockSize();
439 params.sampleRateForAudio = sampleRate;
440 params.endAllowance = endAllowance;
441 params.shouldNormalise = normalise;
442 params.trimSilenceAtEnds = removeSilence;
443 params.shouldNormaliseByRMS = adjustBasedOnRMS;
444 params.normaliseToLevelDb = (float) (adjustBasedOnRMS ? rmsLevelDb : peakLevelDb);
445 params.canRenderInMono = true;
446 params.mustRenderInMono = ! stereo;
447 params.usePlugins = true;
448 params.useMasterPlugins = false;
449 params.realTimeRender = realTime;
450 params.ditheringEnabled = dither;
451 params.quality = qualityIndex;
452 params.category = ProjectItem::Category::rendered;
453 params.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (clip.edit.engine);
454 params.time = clip.getEditTimeRange();
455 params.tracksToDo = getTrackIndexes (clip.edit);
456 params.allowedClips = allowedClips;
457
458 return params;
459}
460
462{
463 auto& afm = engine.getAudioFileFormatManager();
464
465 if (format == midi) return {};
466 if (format == wav) return afm.getWavFormat();
467 if (format == aiff) return afm.getAiffFormat();
468 if (format == flac) return afm.getFlacFormat();
469 if (format == ogg) return afm.getOggFormat();
470
471 if (format == mp3)
472 {
474
475 if (auto af = afm.getLameFormat())
476 return af;
477 }
478
479 format = wav;
480 uiNeedsRefresh = true;
481
482 return afm.getWavFormat();
483}
484
486 const ProjectItem::Ptr projectItem,
487 TimeRange time,
488 SelectionManager* selectionManager) const
489{
491
492 const AddRenderOptions addMode = addRenderOptions;
493
494 if (projectItem == nullptr || addMode == RenderOptions::none)
495 return {};
496
497 projectItem->verifyLength();
498 auto trackIndexes = getTrackIndexes (edit);
499 auto alltracks = getAllTracks (edit);
500 Track::Array tracksArray;
501
502 for (int track = -1;;)
503 {
504 track = trackIndexes.findNextSetBit (++track);
505
506 if (track < 0)
507 break;
508
509 if (auto t = alltracks[track])
510 tracksArray.add (t);
511 }
512
513 auto lastTrack = tracksArray.getLast();
514
515 if (lastTrack == nullptr)
516 return {};
517
518 AudioTrack::Ptr trackToUse;
519
520 switch (addMode)
521 {
522 case nextTrack:
523 trackToUse = dynamic_cast<AudioTrack*> (lastTrack->getSiblingTrack (1, false));
524 break;
525
526 case thisTrack: [[ fallthrough ]];
527 case replaceClips:
528 trackToUse = dynamic_cast<AudioTrack*> (lastTrack.get());
529 break;
530
531 case addTrack: [[ fallthrough ]];
532 case replaceTrack: [[ fallthrough ]];
533 case none: [[ fallthrough ]];
534 default:
535 break;
536 }
537
538 if (trackToUse == nullptr)
539 {
540 if (selectionManager != nullptr)
541 selectionManager->deselectAll();
542
543 trackToUse = edit.insertNewAudioTrack (TrackInsertPoint (lastTrack == nullptr ? nullptr : lastTrack->getParentTrack(),
544 lastTrack.get()),
545 selectionManager);
546
547 if (trackToUse == nullptr)
548 return {};
549
550 if (addMode == replaceTrack)
551 if (auto t = tracksArray.getFirst())
552 trackToUse->setColour (t->getColour());
553
554 for (auto t : tracksArray)
555 {
556 if (auto at = dynamic_cast<AudioTrack*> (t))
557 {
558 if (auto targetTrack = at->getOutput().getDestinationTrack())
559 trackToUse->getOutput().setOutputToTrack (targetTrack);
560 else
561 trackToUse->getOutput().setOutputByName (at->getOutput().getOutputName());
562
563 break;
564 }
565 }
566
567 if (tracksArray.size() > 1)
568 trackToUse->setName (TRANS("Rendered tracks"));
569 }
570
571 auto startTime = reverseRender ? (time.getEnd() - TimeDuration::fromSeconds (projectItem->getLength()))
572 : time.getStart();
573
574 const TimeRange insertPos (startTime, startTime + TimeDuration::fromSeconds (projectItem->getLength()));
575
576 auto newClipName = isTrackRender() ? TRANS("Rendered track")
577 : TRANS("Rendered clip");
578
579 Clip::Ptr newClip;
580
581 if (isMidiRender() || format == midi)
582 {
583 newClip = trackToUse->insertMIDIClip (newClipName, insertPos, nullptr);
584 }
585 else
586 {
587 bool allowAutoTempo = true;
588 bool allowAutoPitch = true;
589
590 for (auto c : allowedClips)
591 {
592 if (auto ac = dynamic_cast<WaveAudioClip*> (c))
593 {
594 if (! ac->getAutoTempo()) allowAutoTempo = false;
595 if (! ac->getAutoPitch()) allowAutoPitch = false;
596 }
597 else
598 {
599 allowAutoTempo = false;
600 allowAutoPitch = false;
601 }
602 }
603
604 newClip = trackToUse->insertWaveClip (newClipName, projectItem->getID(), { insertPos, {} }, false);
605 if (auto ac = dynamic_cast<WaveAudioClip*> (newClip.get()))
606 {
607 // We only want to enable auto tempo is the rendered clip has tempo information
608 // We can't rely on the LoopInfo to determine if tempo information is present or not,
609 // since if it is not present in the source file, then the WaveAudioClip will calculate a
610 // bpm for the clip based on it's length and edit bpm. So we need to go to the source file
611 // and check the metadata
612 AudioFile af (edit.engine, ac->getOriginalFile());
613 auto metadata = af.getInfo().metadata;
614 if (metadata[juce::WavAudioFormat::acidBeats].getIntValue() > 0)
615 ac->setAutoTempo (allowAutoTempo);
616
617 // Only enable auto pitch, if we have pitch information
618 if (ac->getLoopInfo().getRootNote() != -1)
619 ac->setAutoPitch (allowAutoPitch);
620 }
621 }
622
623 if (newClip == nullptr)
624 {
626 return {};
627 }
628
629 if (addMode == replaceTrack)
630 {
631 for (auto t : tracksArray)
632 edit.deleteTrack (t);
633 }
634 else if (addMode == replaceClips)
635 {
636 if (isMidiRender())
637 {
638 if (auto c = allowedClips.getFirst())
639 if (c->getPosition().time.overlaps (time))
640 c->getClipTrack()->deleteRegionOfClip (c, time, nullptr);
641 }
642 else
643 {
644 if (markedRegion)
645 {
646 if (selectionManager != nullptr)
647 deleteRegionOfSelectedClips (*selectionManager, time, CloseGap::no, false);
648
649 newClip->setStart (time.getStart(), false, true);
650 }
651 else
652 {
653 for (auto c : allowedClips)
654 c->removeFromParent();
655 }
656 }
657 }
658
659 if (format == midi)
660 {
661 if (auto mc = dynamic_cast<MidiClip*> (newClip.get()))
662 {
664 BeatDuration length;
665 juce::Array<BeatPosition> tempoChangeBeatNumbers;
667 juce::Array<int> numerators, denominators;
668
669 MidiList::readSeparateTracksFromFile (projectItem->getSourceFile(), lists, tempoChangeBeatNumbers,
670 bpms, numerators, denominators, length, false);
671
672 if (auto list = lists.getFirst())
673 mc->getSequence().copyFrom (*list, nullptr);
674 }
675 }
676
677 if (selectionManager != nullptr)
678 {
679 if (isTrackRender())
680 selectionManager->selectOnly (trackToUse.get());
681 else
682 selectionManager->selectOnly (*newClip);
683 }
684
685 if (reverseRender)
686 if (auto acb = dynamic_cast<AudioClipBase*> (newClip.get()))
687 acb->setIsReversed (true);
688
689 return newClip;
690}
691
692//==============================================================================
693void RenderOptions::setToDefault()
694{
695 type = RenderType::allExport;
696
697 createMidiFile = false;
698 format = wav;
699 stereo = true;
700 sampleRate = 44100.0;
701 bitDepth = 32;
702 qualityIndex = 5;
703 rmsLevelDb = 0.0;
704 peakLevelDb = 0.0;
705 endAllowance = {};
706
707 removeSilence = false;
708 normalise = false;
709 dither = false;
710 adjustBasedOnRMS = false;
711 markedRegion = false;
712 selectedTracks = false;
713 selectedClips = false;
714 tracksToSeparateFiles = false;
715 realTime = false;
716 usePlugins = true;
717
718 addRenderOptions = none;
719 addRenderToLibrary = false;
720 reverseRender = false;
721
722 addMetadata = false;
723 addAcidMetadata = false;
724}
725
726void RenderOptions::updateLastUsedRenderPath (RenderOptions& renderOptions, const juce::String& itemID)
727{
728 auto& storage = renderOptions.engine.getPropertyStorage();
729 auto lastEditID = storage.getProperty (SettingID::lastEditRender).toString();
730
731 if (itemID == lastEditID)
732 renderOptions.destFile = storage.getDefaultLoadSaveDirectory ("exportRender");
733
734 storage.setProperty (SettingID::lastEditRender, itemID);
735}
736
738{
740 ro->setToDefault();
741
742 updateLastUsedRenderPath (*ro, edit.getProjectItemID().toString());
743
744 for (auto t : getAllTracks (edit))
745 ro->tracks.add (t->itemID);
746
747 ro->addMetadata = getMetadata (edit).size() != 0;
748 ro->updateDefaultFilename (&edit);
749 ro->updateHash();
750
751 return ro;
752}
753
754std::unique_ptr<RenderOptions> RenderOptions::forTrackRender (juce::Array<Track*> tracks,
755 AddRenderOptions addOption)
756{
757 if (auto first = tracks.getFirst())
758 {
759 auto& edit = first->edit;
760
761 std::unique_ptr<RenderOptions> ro (new RenderOptions (edit.engine));
762 ro->setToDefault();
763 ro->type = RenderType::track;
764 updateLastUsedRenderPath (*ro, edit.getProjectItemID().toString());
765
766 for (auto t : tracks)
767 ro->tracks.add (t->itemID);
768
769 ro->addRenderOptions = addOption;
770 ro->updateDefaultFilename (&edit);
771 ro->updateHash();
772 return ro;
773 }
774
776 return {};
777}
778
779std::unique_ptr<RenderOptions> RenderOptions::forClipRender (juce::Array<Clip*> clips, bool midiNotes)
780{
781 if (auto first = clips.getFirst())
782 {
783 auto& edit = first->edit;
784
785 std::unique_ptr<RenderOptions> ro (new RenderOptions (edit.engine));
786 ro->setToDefault();
787 updateLastUsedRenderPath (*ro, edit.getProjectItemID().toString());
788
789 ro->allowedClips = clips;
790 bool areAllClipsMono = true;
791
792 for (auto c : clips)
793 {
794 if (auto t = c->getTrack())
795 {
796 ro->tracks.addIfNotAlreadyThere (t->itemID);
797
798 if (auto at = dynamic_cast<AudioTrack*> (t))
799 if (auto dest = at->getOutput().getDestinationTrack())
800 ro->tracks.addIfNotAlreadyThere (dest->itemID);
801 }
802
803 // Assume any non-audio clips should be rendered in stereo
804 if (auto audioClip = dynamic_cast<AudioClipBase*> (c))
805 {
806 if (audioClip->getWaveInfo().numChannels > 1)
807 areAllClipsMono = false;
808 }
809 else
810 {
811 areAllClipsMono = false;
812 }
813 }
814
815 ro->type = midiNotes ? RenderType::midi
816 : RenderType::clip;
817
818 ro->stereo = ! areAllClipsMono;
819 ro->selectedClips = false;
820 ro->endAllowance = ro->usePlugins ? findEndAllowance (edit, &ro->tracks, &clips) : TimeDuration();
821 ro->removeSilence = midiNotes;
822 ro->addRenderOptions = nextTrack;
823 ro->updateDefaultFilename (&edit);
824 ro->updateHash();
825 return ro;
826 }
827
829 return {};
830}
831
832std::unique_ptr<RenderOptions> RenderOptions::forEditClip (Clip& clip)
833{
834 std::unique_ptr<RenderOptions> ro (new RenderOptions (clip.edit.engine, clip.state, &clip.edit.getUndoManager()));
835 ro->type = RenderType::editClip;
836 ro->updateHash();
837
838 return ro;
839}
840
841//==============================================================================
842RenderOptions::RenderOptions (Engine& e)
843 : RenderOptions (e, juce::ValueTree (IDs::RENDER), nullptr)
844{
845}
846
847RenderOptions::RenderOptions (Engine& e, const juce::ValueTree& s, juce::UndoManager* um)
848 : engine (e), state (s)
849{
850 state.addListener (this);
851 relinkCachedValues (um);
852}
853
854RenderOptions::RenderOptions (const RenderOptions& other, juce::UndoManager* um)
855 : RenderOptions (other.engine, other.state.createCopy(), um)
856{
857}
858
859RenderOptions::~RenderOptions()
860{
861 state.removeListener (this);
862
863 if (! isEditClipRender())
864 engine.getPropertyStorage().setDefaultLoadSaveDirectory ("exportRender", destFile);
865}
866
867void RenderOptions::setUINeedsRefresh()
868{
869 uiNeedsRefresh = true;
870}
871
873{
874 if (! uiNeedsRefresh)
875 return false;
876
877 uiNeedsRefresh = false;
878 return true;
879}
880
881//==============================================================================
882RenderOptions::TargetFileFormat RenderOptions::setFormat (TargetFileFormat f)
883{
884 auto& afm = engine.getAudioFileFormatManager();
885 format = f;
886
888
889 if (format == mp3 && afm.getLameFormat() == nullptr)
890 format = wav;
891
892 updateFileName();
893 updateOptionsForFormat();
894
895 return format;
896}
897
898void RenderOptions::setFormatType (const juce::String& typeString)
899{
900 auto& am = engine.getAudioFileFormatManager();
901
902 if (typeString == am.getWavFormat()->getFormatName()) setFormat (wav);
903 else if (typeString == am.getAiffFormat()->getFormatName()) setFormat (aiff);
904 else if (typeString == am.getFlacFormat()->getFormatName()) setFormat (flac);
905 else if (typeString == am.getOggFormat()->getFormatName()) setFormat (ogg);
906 else if (typeString == "MP3 file") setFormat (mp3);
907 else if (typeString == "MIDI file") setFormat (midi);
908 else setFormat (wav);
909}
910
911juce::String RenderOptions::getFormatTypeName (TargetFileFormat fmt)
912{
913 auto& am = engine.getAudioFileFormatManager();
914
915 switch (fmt)
916 {
917 case wav: return am.getWavFormat()->getFormatName();
918 case aiff: return am.getAiffFormat()->getFormatName();
919 case flac: return am.getFlacFormat()->getFormatName();
920 case ogg: return am.getOggFormat()->getFormatName();
921 case mp3: return am.getLameFormat() == nullptr ? juce::String() : am.getLameFormat()->getFormatName();
922 case midi: return "MIDI file";
923 case numFormats: return "MIDI file";
924 default: return {};
925 }
926}
927
928void RenderOptions::setTrackIDs (const juce::Array<EditItemID>& trackIDs)
929{
930 if (trackIDs.isEmpty())
931 tracksProperty.resetToDefault();
932 else
933 tracksProperty = EditItemID::listToString (trackIDs);
934}
935
936HashCode RenderOptions::getTracksHash() const
937{
938 HashCode tracksHash = 0;
939
940 for (auto& t : tracks)
941 tracksHash ^= static_cast<HashCode> (t.getRawID()); // TODO: this will probably be buggy if the IDs are all low sequential integers!
942
943 return tracksHash;
944}
945
946void RenderOptions::setSampleRate (int sr)
947{
948 if (auto af = getAudioFormat())
949 {
950 auto rates = af->getPossibleSampleRates();
951
952 if (! rates.contains (sr))
953 sr = rates.contains (44100) ? 44100 : rates[(int) std::floor (rates.size() / 2.0)];
954
955 sampleRate = sr;
956 }
957}
958
959juce::BigInteger RenderOptions::getTrackIndexes (const Edit& ed) const
960{
962
963 auto trks = getAllTracks (ed);
964
965 for (int i = tracks.size(); --i >= 0;)
966 if (auto t = findTrackForID (ed, tracks.getUnchecked (i)))
967 res.setBit (trks.indexOf (t));
968
969 return res;
970}
971
972void RenderOptions::setSelected (bool onlySelectedTrackAndClips)
973{
974 selectedTracks = onlySelectedTrackAndClips;
975 selectedClips = onlySelectedTrackAndClips;
976}
977
978void RenderOptions::setFilename (juce::String value, bool canPromptToOverwriteExisting)
979{
981 auto recentList = engine.getPropertyStorage().getProperty (SettingID::renderRecentFilesList).toString();
982 recent.restoreFromString (recentList);
983
984 juce::File f;
985
986 #if JUCE_MODAL_LOOPS_PERMITTED
987 if (value.startsWithIgnoreCase ("$browse"))
988 {
989 enum
990 {
991 cancel = 0,
992 browse,
993 projectDefault,
994 recentOffset
995 };
996
997 juce::PopupMenu m, m2;
998
999 m.addItem (browse, TRANS("Browse") + "...");
1000 m.addItem (projectDefault, TRANS("Set to project default"));
1001
1002 int i = recentOffset;
1003
1004 for (auto& s : recent.getAllFilenames())
1005 m2.addItem (i++, s);
1006
1007 if (recent.getNumFiles() > 0)
1008 m.addSubMenu (TRANS("Recent"), m2);
1009
1010 auto res = m.show();
1011
1012 if (res == cancel)
1013 return;
1014
1015 if (res == browse)
1016 {
1017 juce::FileChooser chooser (TRANS("Select the file to use"), destFile, "*" + getCurrentFileExtension());
1018
1019 if (! chooser.browseForFileToSave (false))
1020 return;
1021
1022 f = chooser.getResult();
1023 }
1024 else if (res == projectDefault)
1025 {
1026 f = juce::File();
1027 }
1028 else
1029 {
1030 f = recent.getAllFilenames()[res - recentOffset];
1031 }
1032 }
1033 else
1034 #endif
1035 {
1036 f = juce::File (value);
1037 }
1038
1039 if (f.existsAsFile()
1040 #if JUCE_MODAL_LOOPS_PERMITTED
1041 && canPromptToOverwriteExisting
1042 && engine.getUIBehaviour().showOkCancelAlertBox (TRANS("File Already Exists"),
1043 TRANS("The file you have chosen already exists, do you want to delete it?")
1045 + TRANS("If you choose to cancel, a non existent file name will be chosen automatically.")
1047 + "(" + TRANS("This operation can't be undone") + ")")
1048 #endif
1049 )
1050 {
1051 AudioFile (engine, f).deleteFile();
1052
1053 if (f.existsAsFile())
1054 f.moveToTrash();
1055 }
1056
1057 destFile = f.getFullPathName();
1058 updateDefaultFilename (nullptr);
1059
1060 if (destFile != juce::File())
1061 {
1062 recent.addFile (destFile);
1063 auto newRecentList = recent.toString();
1064
1065 if (recentList != newRecentList)
1066 engine.getPropertyStorage().setProperty (SettingID::renderRecentFilesList, newRecentList);
1067 }
1068}
1069
1070//==============================================================================
1071juce::StringArray RenderOptions::getFormatTypes()
1072{
1073 auto& am = engine.getAudioFileFormatManager();
1074
1075 juce::StringArray formats;
1076 formats.add (am.getWavFormat()->getFormatName());
1077 formats.add (am.getAiffFormat()->getFormatName());
1078
1079 if (! isRender())
1080 {
1081 formats.add (am.getFlacFormat()->getFormatName());
1082 formats.add (am.getOggFormat()->getFormatName());
1083
1084 #if JUCE_USE_LAME_AUDIO_FORMAT
1085 auto& afm = engine.getAudioFileFormatManager();
1087
1088 if (LAMEManager::lameIsAvailable() && am.getLameFormat() != nullptr)
1089 formats.add (am.getLameFormat()->getFormatName());
1090 #endif
1091
1092 formats.add ("MIDI file");
1093 }
1094
1095 return formats;
1096}
1097
1098juce::StringArray RenderOptions::getAddRenderOptionText()
1099{
1100 static const char* renderOptionsText[] = { NEEDS_TRANS("Replace Rendered Tracks"),
1101 NEEDS_TRANS("Add Rendered Tracks"),
1102 NEEDS_TRANS("Insert Into Next Track"),
1103 NEEDS_TRANS("Insert Into This Track"),
1104 NEEDS_TRANS("Replace Clips"),
1105 NEEDS_TRANS("None") };
1106
1107 juce::StringArray renderOptions;
1108
1109 renderOptions.add (TRANS(renderOptionsText[replaceTrack]));
1110 renderOptions.add (TRANS(renderOptionsText[addTrack]));
1111
1112 if (! isTrackRender())
1113 {
1114 renderOptions.add (TRANS(renderOptionsText[nextTrack]));
1115 renderOptions.add (TRANS(renderOptionsText[thisTrack]));
1116 renderOptions.add (TRANS(renderOptionsText[replaceClips]));
1117 renderOptions.add (TRANS(renderOptionsText[none]));
1118 }
1119
1120 return renderOptions;
1121}
1122
1123//==============================================================================
1124bool RenderOptions::isMarkedRegionBigEnough (TimeRange region)
1125{
1126 return region.getLength() > 0.05s;
1127}
1128
1129juce::StringArray RenderOptions::getQualitiesList() const
1130{
1131 auto& af = engine.getAudioFileFormatManager();
1132
1133 if (format == flac)
1134 return af.getFlacFormat()->getQualityOptions();
1135
1136 if (format == ogg)
1137 return af.getOggFormat()->getQualityOptions();
1138
1139 if (auto lameFormat = af.getLameFormat())
1140 if (format == mp3)
1141 return lameFormat->getQualityOptions();
1142
1143 return {};
1144}
1145
1146//==============================================================================
1147void RenderOptions::updateHash()
1148{
1149 hash = (tracks.isEmpty() ? 0 : getTracksHash())
1150 ^ (((HashCode) createMidiFile) << 0)
1151 ^ (((HashCode) format) << 1)
1152 ^ (((HashCode) stereo) << 2)
1153 ^ (((HashCode) sampleRate) << 3)
1154 ^ (((HashCode) bitDepth) << 4)
1155 ^ (((HashCode) qualityIndex) << 5)
1156 ^ (((HashCode) removeSilence) << 6)
1157 ^ (((HashCode) normalise) << 7)
1158 ^ (((HashCode) dither) << 8)
1159 ^ (((HashCode) adjustBasedOnRMS) << 9)
1160 ^ ((HashCode) (rmsLevelDb * -4567.2))
1161 ^ ((HashCode) (peakLevelDb * 2453.1))
1162 ^ (((HashCode) markedRegion) << 10)
1163 ^ (((HashCode) selectedTracks) << 12)
1164 ^ (((HashCode) selectedClips) << 12)
1165 ^ (((HashCode) tracksToSeparateFiles) << 13)
1166 ^ (((HashCode) realTime) << 14)
1167 ^ (((HashCode) usePlugins) << 15)
1168 ^ (((HashCode) addMetadata) << 16)
1169 ^ (((HashCode) addAcidMetadata) << 17);
1170}
1171
1172void RenderOptions::updateFileName()
1173{
1174 if (format == midi)
1175 destFile = destFile.withFileExtension ("mid");
1176 else if (auto af = getAudioFormat())
1177 destFile = destFile.withFileExtension (af->getFileExtensions()[0]);
1178 else
1179 destFile = juce::File();
1180
1181 setFilename (destFile.getFullPathName(), false);
1182 updateHash();
1183}
1184
1185void RenderOptions::updateOptionsForFormat()
1186{
1187 if (auto af = getAudioFormat())
1188 {
1189 // sample rate
1190 auto rates = af->getPossibleSampleRates();
1192
1193 for (auto rate : rates)
1194 r.add (juce::String (rate));
1195
1196 if (! rates.contains ((int) sampleRate))
1197 sampleRate = 44100;
1198
1199 if (! rates.contains ((int) sampleRate))
1200 sampleRate = rates[0];
1201
1202 // bit-depth
1203 r.clear();
1204 auto depths = af->getPossibleBitDepths();
1205
1206 for (auto depth : depths)
1207 r.add (TRANS("32-bit").replace ("32", juce::String (depth)));
1208
1209 if (! depths.contains (bitDepth))
1210 bitDepth = 16;
1211
1212 if (! depths.contains (bitDepth))
1213 bitDepth = depths[0];
1214
1215 // quality
1216 auto qualities = af->getQualityOptions();
1217
1218 if (! juce::isPositiveAndBelow (qualityIndex.get(), qualities.size()))
1219 qualityIndex = (int) std::ceil (qualities.size() / 2.0);
1220
1221 if (! juce::isPositiveAndBelow (qualityIndex.get(), qualities.size()))
1222 qualityIndex = 0;
1223 }
1224}
1225
1226void RenderOptions::updateDefaultFilename (Edit* edit)
1227{
1228 if (destFile == juce::File()
1229 || destFile.existsAsFile()
1230 || destFile.getFileExtension() != getCurrentFileExtension())
1231 {
1232 const ProjectItem::Category category = isRender() ? ProjectItem::Category::rendered
1233 : ProjectItem::Category::exports;
1234 juce::String nameStem;
1235
1236 if (selectedTracks || selectedClips)
1237 {
1238 if (auto sm = engine.getUIBehaviour().getCurrentlyFocusedSelectionManager())
1239 {
1240 if (auto clip = sm->getFirstItemOfType<Clip>())
1241 nameStem = clip->getName();
1242
1243 if (auto track = sm->getFirstItemOfType<Track>())
1244 nameStem = track->getName();
1245 }
1246 }
1247
1248 if (nameStem.trim().isEmpty())
1249 {
1250 if (edit == nullptr)
1251 edit = engine.getUIBehaviour().getCurrentlyFocusedEdit();
1252
1253 if (edit != nullptr)
1254 {
1255 nameStem = edit->getName();
1256
1257 if (isTrackRender() && tracks.size() > 0)
1258 if (auto t = findTrackForID (*edit, tracks[0]))
1259 nameStem += " " + t->getName();
1260
1261 nameStem += " " + (category == ProjectItem::Category::exports ? TRANS("Export")
1262 : TRANS("Render")) + " 1";
1263 }
1264 }
1265
1266 const auto dir = destFile.existsAsFile() ? destFile.getParentDirectory()
1267 : engine.getPropertyStorage().getDefaultLoadSaveDirectory (category);
1268
1269 destFile = dir.getChildFile (juce::File::createLegalFileName (nameStem)
1270 + getCurrentFileExtension());
1271
1272 destFile = getNonExistentSiblingWithIncrementedNumberSuffix (destFile, false);
1273 }
1274}
1275
1276juce::String RenderOptions::getCurrentFileExtension()
1277{
1278 if (format == midi)
1279 return ".mid";
1280
1281 if (auto af = getAudioFormat())
1282 return af->getFileExtensions()[0];
1283
1284 return {};
1285}
1286
1287}} // namespace tracktion { inline namespace engine
T ceil(T... args)
ElementType getUnchecked(int index) const
bool isEmpty() const noexcept
int size() const noexcept
ElementType getFirst() const noexcept
void add(const ElementType &newElement)
bool contains(ParameterType elementToLookFor) const
bool addIfNotAlreadyThere(ParameterType newElement)
virtual StringArray getQualityOptions()
const String & getFormatName() const
BigInteger & setRange(int startBit, int numBits, bool shouldBeSet)
bool isZero() const noexcept
BigInteger & setBit(int bitNumber)
void referTo(ValueTree &tree, const Identifier &property, UndoManager *um)
Type get() const noexcept
String getFileExtension() const
bool existsAsFile() const
const String & getFullPathName() const noexcept
File getChildFile(StringRef relativeOrAbsolutePath) const
File getParentDirectory() const
File withFileExtension(StringRef newExtension) const
bool moveToTrash() const
static String createLegalFileName(const String &fileNameToFix)
const String & toString() const noexcept
ObjectClass * getFirst() const noexcept
void restoreFromString(const String &stringifiedVersion)
void addFile(const File &file)
const StringArray & getAllFilenames() const noexcept
int size() const noexcept
ObjectClassPtr getFirst() const noexcept
ObjectClassPtr getLast() const noexcept
ObjectClass * add(ObjectClass *newObject)
ReferencedType * get() const noexcept
void add(String stringToAdd)
void set(const String &key, const String &value)
int size() const noexcept
String trim() const
bool isEmpty() const noexcept
void addListener(Listener *listener)
void removeListener(Listener *listener)
static const char *const acidBeats
Base class for Clips that produce some kind of audio e.g.
A clip in an edit.
This is the main source of an Edit clip and is responsible for managing its properties.
static Ptr getOrCreateRenderJob(Engine &, Renderer::Parameters &, bool deleteEdit, bool silenceOnBackup, bool reverse)
Returns a job that will have been started to generate the Render described by the params.
The Tracktion Edit class!
void deleteTrack(Track *)
Deletes a Track.
TimeDuration getLength() const
Returns the end time of last clip.
juce::ReferenceCountedObjectPtr< AudioTrack > insertNewAudioTrack(TrackInsertPoint, SelectionManager *)
Inserts a new AudioTrack in the Edit.
ProjectItemID getProjectItemID() const noexcept
Returns the ProjectItemID of the Edit.
Engine & engine
A reference to the Engine.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
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.
static bool lameIsAvailable()
Returns true if a valid LAME/FFmpeg file is found.
static void registerAudioFormat(AudioFileFormatManager &)
Add the LAMEAudioFormat to the AudioFileFormatManager.
Represents a set of user properties used to control a render operation, using a ValueTree to hold the...
static std::unique_ptr< RenderOptions > forGeneralExporter(Edit &)
Creates a default RenderOptions object for a general purpose exporter.
juce::AudioFormat * getAudioFormat()
Returns an AudioFormat to use for the current render properties.
AddRenderOptions
Enum to set the options for renders.
bool getUINeedsRefresh()
If you've chnaged a property that will cause the UI configuration to change this will return true whi...
RenderManager::Job::Ptr performBackgroundRender(Edit &, SelectionManager *, TimeRange timeRangeToRender)
Performs the render operation on a background thread.
Renderer::Parameters getRenderParameters(Edit &, SelectionManager *, TimeRange markedRegion)
Returns a set of renderer parameters which can be used to describe a render operation.
Clip::Ptr applyRenderToEdit(Edit &, ProjectItem::Ptr, TimeRange time, SelectionManager *) const
Adds the given ProjectItem to the Edit using the render properties for positioning info.
Manages a list of items that are currently selected.
virtual bool showOkCancelAlertBox(const juce::String &title, const juce::String &message, const juce::String &ok={}, const juce::String &cancel={})
Should display a dismissable alert window.
An audio clip that uses an audio file as its source.
T floor(T... args)
T format(T... args)
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
#define jassertfalse
typedef int
typedef float
T max(T... args)
NewLine newLine
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
@ no
Don't move up subsequent track content.
juce::Array< Track * > getAllTracks(const Edit &edit)
Returns all the tracks in an Edit.
void deleteRegionOfSelectedClips(SelectionManager &selectionManager, TimeRange rangeToDelete, CloseGap closeGap, bool moveAllSubsequentClipsOnTrack)
Deletes a time range of a Clip selection, optionally closing the gap.
Track * findTrackForID(const Edit &edit, EditItemID id)
Returns the Track with a given ID if contained in the Edit.
TimeRange findUnionOfEditTimeRanges(const ArrayType &items)
Returns the the time range that covers all the given TrackItems.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
T replace(T... args)
Represents a duration in beats.
Represents a position in real-life time.
Defines the place to insert Track[s].
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.