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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Edit.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
14Edit::GlobalMacros::GlobalMacros (Edit& e)
15 : MacroParameterElement (e, e.state),
16 edit (e)
17{
18}
19
20//==============================================================================
23{
25 : edit (e)
26 {
27 // Add the change listener asyncronously to avoid messages coming in
28 // from the Edit initialisation phase
29 juce::MessageManager::callAsync ([ref = juce::WeakReference<UndoTransactionTimer> (this)]
30 {
31 if (ref != nullptr)
32 ref->edit.getUndoManager().addChangeListener (ref.get());
33 });
34 }
35
36 ~UndoTransactionTimer() override
37 {
38 edit.getUndoManager().removeChangeListener (this);
39 }
40
41 void timerCallback() override
42 {
43 if (edit.numUndoTransactionInhibitors > 0)
44 return;
45
46 if (! juce::Component::isMouseButtonDownAnywhere())
47 {
48 stopTimer();
49 edit.getUndoManager().beginNewTransaction();
50 }
51 }
52
53 void changeListenerCallback (juce::ChangeBroadcaster*) override
54 {
55 edit.markAsChanged();
56 startTimer (350);
57 }
58
59 Edit& edit;
60
63};
64
65//==============================================================================
67{
68 EditChangeResetterTimer (Edit& ed) : edit (ed)
69 {
70 startTimer (200);
71 }
72
73 void timerCallback() override
74 {
75 stopTimer();
76 edit.resetChangedStatus();
77 edit.changeResetterTimer.reset();
78 }
79
80 Edit& edit;
81};
82
83//==============================================================================
85{
86 TreeWatcher (Edit& ed, const juce::ValueTree& v) : edit (ed), state (v)
87 {
88 state.addListener (this);
89 }
90
91 Edit& edit;
92 juce::ValueTree state;
93
94 void valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i) override
95 {
96 if (v.hasType (IDs::TRANSPORT))
97 {
98 if (i == IDs::recordPunchInOut)
99 {
100 auto& ecm = edit.engine.getExternalControllerManager();
101
102 if (ecm.isAttachedToEdit (edit))
103 ecm.updatePunchLights();
104 }
105 else if (i == IDs::endToEnd)
106 {
107 if (! edit.getTransport().isPlaying())
108 {
109 if (v[IDs::endToEnd])
111 else
113
114 auto& ecm = edit.engine.getExternalControllerManager();
115
116 if (ecm.isAttachedToEdit (edit))
117 ecm.updateAllDevices();
118 }
119 }
120 }
121 else if (TrackList::isTrack (v))
122 {
123 if (i == IDs::frozenIndividually || i == IDs::compGroup)
124 {
125 restart();
126 }
127 else if (i == IDs::frozen)
128 {
129 edit.needToUpdateFrozenTracks();
130 }
131 else if (i == IDs::process)
132 {
133 restart();
134 }
135 else if (i == IDs::mute || i == IDs::solo || i == IDs::soloIsolate)
136 {
138 edit.markAsChanged();
139 }
140 }
141 else if (Clip::isClipState (v))
142 {
143 if (i == IDs::start || i == IDs::length || i == IDs::offset)
144 {
145 clipMovedOrAdded (v);
146 }
147 else if (i == IDs::linkID)
148 {
149 linkedClipsChanged();
150 }
151 else if (i == IDs::speed || i == IDs::source || i == IDs::mpeMode
152 || i == IDs::fadeInType || i == IDs::fadeOutType
153 || i == IDs::fadeInBehaviour || i == IDs::fadeOutBehaviour
154 || i == IDs::fadeIn || i == IDs::fadeOut
155 || i == IDs::loopStart || i == IDs::loopLength
156 || i == IDs::loopStartBeats || i == IDs::loopLengthBeats
157 || i == IDs::transpose || i == IDs::pitchChange
158 || i == IDs::elastiqueMode || i == IDs::elastiqueOptions
159 || i == IDs::autoPitch || i == IDs::autoTempo
160 || i == IDs::channels || i == IDs::isReversed
161 || i == IDs::currentTake || i == IDs::sequence || i == IDs::repeatSequence
162 || i == IDs::loopedSequenceType || i == IDs::grooveStrength
163 || i == IDs::proxyAllowed || i == IDs::resamplingQuality || i == IDs::warpTime
164 || i == IDs::disabled || i == IDs::followActionBeats || i == IDs::followActionNumLoops
165 || i == IDs::followActionDurationType)
166 {
167 restart();
168 }
169 }
170 else if (v.hasType (IDs::COMPSECTION))
171 {
172 restart();
173 }
174 else if (v.hasType (IDs::WARPMARKER))
175 {
176 if (i == IDs::sourceTime || i == IDs::warpTime)
177 restart();
178 }
179 else if (v.hasType (IDs::QUANTISATION))
180 {
181 if (i == IDs::type || i == IDs::amount)
182 restart();
183 }
184 else if (v.hasType (IDs::NOTE) || v.hasType (IDs::CONTROL) || v.hasType (IDs::SYSEX))
185 {
186 if (i != IDs::c)
187 restart();
188 }
189 else if (MidiExpression::isExpression (v.getType()))
190 {
191 if (i == IDs::b || i == IDs::v)
192 restart();
193 }
194 else if (v.hasType (IDs::CHANNEL))
195 {
196 if (i == IDs::pattern || i == IDs::channel
197 || i == IDs::velocities || i == IDs::gates || i == IDs::probabilities
198 || i == IDs::note || i == IDs::velocity || i == IDs::groove
199 || i == IDs::grooveStrength)
200 restart();
201 }
202 else if (v.hasType (IDs::PATTERN))
203 {
204 if (i == IDs::noteLength || i == IDs::numNotes)
205 restart();
206 }
207 else if (v.hasType (IDs::SEQUENCE))
208 {
209 if (i == IDs::channelNumber)
210 restart();
211 }
212 else if (v.hasType (IDs::GROOVE))
213 {
214 if (i == IDs::current)
215 restart();
216 }
217 else if (v.hasType (IDs::TAKES))
218 {
219 if (i == IDs::currentTake)
220 restart();
221 }
222 else if (v.hasType (IDs::SIDECHAINCONNECTION))
223 {
224 restart();
225 }
226 else if (v.hasType (IDs::CLICKTRACK))
227 {
228 if (i == IDs::active)
229 {
230 auto& ecm = edit.engine.getExternalControllerManager();
231
232 if (ecm.isAttachedToEdit (edit))
233 ecm.clickChanged (edit.clickTrackEnabled);
234 }
235 }
236 else if (v.hasType (IDs::TEMPO) || v.hasType (IDs::TIMESIG))
237 {
238 restart();
240 }
241 else if (v.hasType (IDs::PLUGIN))
242 {
243 if (i == IDs::enabled
245 {
246 restart();
247 }
248 else
249 {
250 const auto type = v[IDs::type].toString();
251
252 if (type == AuxSendPlugin::xmlTypeName || type == AuxReturnPlugin::xmlTypeName)
253 {
254 if (i == IDs::enabled || i == IDs::busNum)
255 restart();
256 }
257 else if (type == RackInstance::xmlTypeName)
258 {
259 if (i == IDs::enabled
260 || i == IDs::leftTo || i == IDs::rightTo || i == IDs::leftFrom || i == IDs::rightFrom)
261 restart();
262 }
263 else if (i == IDs::outputDevice || i == IDs::inputDevice
264 || i == IDs::manualAdjustMs || i == IDs::sidechainSourceID || i == IDs::ignoreVca || i == IDs::busNum)
265 {
266 restart();
267 }
268 }
269 }
270 else if (v.hasType (IDs::MASTERVOLUME))
271 {
272 if (i == IDs::fadeIn || i == IDs::fadeOut
273 || i == IDs::fadeInType || i == IDs::fadeOutType)
274 restart();
275 }
276 else if (v.hasType (IDs::LOOPINFO))
277 {
278 if (i == IDs::numBeats || i == IDs::rootNote
279 || i == IDs::numerator || i == IDs::denominator)
280 {
281 restart();
282 }
283 }
284 else if (v.hasType (IDs::ACTION) && v.getParent().hasType (IDs::FOLLOWACTIONS))
285 {
286 if (i == IDs::type || i == IDs::weight)
287 restart();
288 }
289 }
290
291 void valueTreeChildAdded (juce::ValueTree& p, juce::ValueTree& c) override
292 {
293 childAddedOrRemoved (p, c, true);
294 }
295
296 void valueTreeChildRemoved (juce::ValueTree& p, juce::ValueTree& c, int) override
297 {
298 childAddedOrRemoved (p, c, false);
299 }
300
301 void childAddedOrRemoved (juce::ValueTree& p, juce::ValueTree& c, bool wasAdded)
302 {
303 if (c.hasType (IDs::NOTE)
304 || c.hasType (IDs::CONTROL)
305 || c.hasType (IDs::SYSEX)
306 || c.hasType (IDs::PLUGININSTANCE)
307 || c.hasType (IDs::CONNECTION)
308 || p.hasType (IDs::CHANNELS)
309 || c.hasType (IDs::CHANNELS)
310 || c.hasType (IDs::SIDECHAINCONNECTION)
311 || c.hasType (IDs::MACROPARAMETERS)
312 || c.hasType (IDs::MACROPARAMETER)
313 || p.hasType (IDs::MAPPEDPARAMETER)
314 || c.hasType (IDs::LFO)
315 || c.hasType (IDs::BREAKPOINTOSCILLATOR)
316 || c.hasType (IDs::STEP)
317 || c.hasType (IDs::ENVELOPEFOLLOWER)
318 || c.hasType (IDs::RANDOM)
319 || c.hasType (IDs::MIDITRACKER)
320 || p.hasType (IDs::LOOPINFO)
321 || p.hasType (IDs::PATTERN))
322 {
323 restart();
324 }
325 else if (c.hasType (IDs::PLUGIN)
326 || c.hasType (IDs::RACK))
327 {
328 if (wasAdded)
329 EditItemID::readOrCreateNewID (edit, c);
330
331 restart();
332 }
333 else if (Clip::isClipState (c))
334 {
335 if (wasAdded)
336 EditItemID::readOrCreateNewID (edit, c);
337
338 clipMovedOrAdded (c);
339 linkedClipsChanged();
340 }
341 else if (TrackList::isTrack (c))
342 {
343 if (wasAdded)
344 EditItemID::readOrCreateNewID (edit, c);
345
346 restart();
348 }
349 else if (c.hasType (IDs::WARPMARKER))
350 {
351 restart();
352 }
353 else if (p.hasType (IDs::TRACKCOMP))
354 {
355 restart();
356 }
357 else if (p.hasType (IDs::TEMPOSEQUENCE))
358 {
359 restart();
361 }
362 else if (p.hasType (IDs::NOTE))
363 {
364 restart();
365 }
366 else if (p.hasType (IDs::FOLLOWACTIONS) && c.hasType (IDs::ACTION))
367 {
368 restart();
369 }
370 }
371
372 void valueTreeChildOrderChanged (juce::ValueTree&, int, int) override {}
373 void valueTreeParentChanged (juce::ValueTree&) override {}
374
375 void clipMovedOrAdded (const juce::ValueTree& v)
376 {
378
379 if (v.hasType (IDs::AUDIOCLIP)
380 || v.hasType (IDs::MIDICLIP)
381 || v.hasType (IDs::STEPCLIP)
382 || v.hasType (IDs::EDITCLIP)
383 || v.hasType (IDs::CHORDCLIP)
384 || v.hasType (IDs::CONTAINERCLIP))
385 {
386 if (! v[IDs::disabled])
387 restart();
388 }
389 }
390
391 void restart()
392 {
393 edit.restartPlayback();
394 }
395
396 void updateTrackStatusesAsync()
397 {
398 if (trackStatusUpdater == nullptr)
399 trackStatusUpdater = std::make_unique<TrackStatusUpdater> (*this);
400 }
401
403 {
404 TrackStatusUpdater (TreeWatcher& o) : owner (o) { triggerAsyncUpdate(); }
405
406 void handleAsyncUpdate() override
407 {
408 if (! owner.edit.isLoading())
409 owner.edit.updateTrackStatuses();
410
411 owner.trackStatusUpdater = nullptr;
412 }
413
414 TreeWatcher& owner;
415 };
416
417 std::unique_ptr<TrackStatusUpdater> trackStatusUpdater;
418
419 //==============================================================================
420 void linkedClipsChanged()
421 {
422 linkedClipsMapDirty = true;
423 }
424
425 juce::Array<Clip*> getClipsInLinkGroup (const juce::String& linkGroupid)
426 {
427 jassert (! edit.isLoading());
428
429 if (linkedClipsMapDirty)
430 {
431 linkedClipsMap.clear();
432
433 edit.visitAllTracksRecursive ([&] (Track& t)
434 {
435 if (auto ct = dynamic_cast<ClipTrack*> (&t))
436 for (auto c : ct->getClips())
437 if (c->getLinkGroupID().isNotEmpty())
438 linkedClipsMap[c->getLinkGroupID()].add (c);
439
440 return true;
441 });
442
443 linkedClipsMapDirty = false;
444 }
445
446 return linkedClipsMap[linkGroupid];
447 }
448
449 void messageThreadAssertIfLoaded() const
450 {
451 if (! edit.isLoading())
452 {
453 TRACKTION_ASSERT_MESSAGE_THREAD
454 }
455 }
456
457 bool linkedClipsMapDirty = true;
459
461};
462
463//==============================================================================
465{
466 ChangedPluginsList() = default;
467
470 {
471 changedPlugins.addIfNotAlreadyThere (&p);
472 }
473
474 void clear()
475 {
476 changedPlugins.clear();
477 }
478
481 {
482 TRACKTION_ASSERT_MESSAGE_THREAD
483
484 if (auto index = changedPlugins.indexOf (&p); index >= 0)
485 {
486 p.flushPluginStateToValueTree();
487 changedPlugins.remove (index);
488 }
489 }
490
492 void flushAll()
493 {
494 for (auto& plugin : changedPlugins)
495 if (auto p = plugin.get())
496 p->flushPluginStateToValueTree();
497
498 clear();
499 }
500
501private:
503};
504
505//==============================================================================
507{
508 FrozenTrackCallback (Edit& ed) : edit (ed) {}
509 void handleAsyncUpdate() override { edit.updateFrozenTracks(); }
510 Edit& edit;
511};
512
513//==============================================================================
515{
516 PluginChangeTimer (Edit& ed) : edit (ed) {}
517
518 void pluginChanged()
519 {
520 startTimer (500);
521 }
522
523 void timerCallback() override
524 {
525 edit.markAsChanged();
526 stopTimer();
527 }
528
529 Edit& edit;
530};
531
532//==============================================================================
534{
535 MirroredPluginUpdateTimer (Edit& ed) : edit (ed) {}
536
537 void pluginChanged (Plugin& p)
538 {
539 changedPlugins.addIfNotAlreadyThere (&p);
540 startTimer (500);
541 }
542
543 void timerCallback() override
544 {
545 if (! edit.isLoading())
546 {
547 stopTimer();
548
549 for (auto& changed : changedPlugins)
550 if (auto p = changed.get())
552
553 changedPlugins.clear();
554 }
555 }
556
557 Edit& edit;
559};
560
561//==============================================================================
562static int getNextInstanceId() noexcept
563{
564 static juce::Atomic<int> nextId;
565 return ++nextId;
566}
567
568//==============================================================================
569Edit::Edit (Options options)
570 : engine (options.engine),
571 tempoSequence (*this),
572 state (options.editState),
573 instanceId (getNextInstanceId()),
574 editProjectItemID (options.editProjectItemID),
575 loadContext (options.loadContext),
576 editRole (options.role)
577{
579
581 jassert (state.hasType (IDs::EDIT));
582 jassert (editProjectItemID.load().isValid()); // This must be valid or it won't be able to create temp files etc.
583
584 if (options.editFileRetriever)
585 editFileRetriever = std::move (options.editFileRetriever);
586 else
587 editFileRetriever = [this] () -> juce::File
588 {
589 if (auto item = getProjectItemForEdit (*this))
590 return item->getSourceFile();
591
592 return {};
593 };
594
595 if (options.filePathResolver)
596 filePathResolver = std::move (options.filePathResolver);
597 else
598 filePathResolver = [this] (const juce::String& path) -> juce::File
599 {
600 jassert (path.isNotEmpty());
601
603 return path;
604
606 return editFileRetriever().getSiblingFile (path);
607
608 return {};
609 };
610
611 pluginCache = std::make_unique<PluginCache> (*this);
612 mirroredPluginUpdateTimer = std::make_unique<MirroredPluginUpdateTimer> (*this);
613 transportControl = std::make_unique<TransportControl> (*this, state.getOrCreateChildWithName (IDs::TRANSPORT, nullptr));
614 automationRecordManager = std::make_unique<AutomationRecordManager> (*this);
615 markerManager = std::make_unique<MarkerManager> (*this, state.getOrCreateChildWithName (IDs::MARKERTRACK, nullptr));
616 pluginChangeTimer = std::make_unique<PluginChangeTimer> (*this);
617 frozenTrackCallback = std::make_unique<FrozenTrackCallback> (*this);
618 masterPluginList = std::make_unique<PluginList> (*this);
619 parameterChangeHandler = std::make_unique<ParameterChangeHandler> (*this);
620 parameterControlMappings = std::make_unique<ParameterControlMappings> (*this);
621 rackTypes = std::make_unique<RackTypeList> (*this);
622 trackCompManager = std::make_unique<TrackCompManager> (*this);
623 changedPluginsList = std::make_unique<ChangedPluginsList>();
624
625 undoManager.setMaxNumberOfStoredUnits (1000 * options.numUndoLevelsToStore, options.numUndoLevelsToStore);
626
627 initialise (options);
628
629 undoTransactionTimer = std::make_unique<UndoTransactionTimer> (*this);
630
631 if (loadContext != nullptr && ! loadContext->shouldExit)
632 {
633 loadContext->completed = true;
634 loadContext = nullptr;
635 }
636
637 // Don't spam logs with preview Edits
638 if (getProjectItemID().getProjectID() != 0)
639 TRACKTION_LOG ("Loaded edit: " + getName());
640
641 jassert (! engine.getActiveEdits().edits.contains (this));
642 engine.getActiveEdits().edits.add (this);
643
644 isFullyConstructed.store (true, std::memory_order_relaxed);
645}
646
647static Edit::Options getOptionsFor (Engine& engine, Edit::EditRole role)
648{
649 auto editState = createEmptyEdit (engine);
650
651 return Edit::Options
652 {
653 engine,
654 editState,
655 ProjectItemID::fromProperty (editState, IDs::projectID),
656 role,
657 nullptr,
659 {},
660 {},
661 1 // min num audio tracks
662 };
663}
664
665Edit::Edit (Engine& e, EditRole roleToUse) : Edit (getOptionsFor (e, roleToUse))
666{
667 if (auto track = getFirstAudioTrack (*this))
668 track->getOutput().setOutputToDefaultDevice (false);
669}
670
672{
674
675 for (auto af : pluginCache->getPlugins())
676 af->hideWindowForShutdown();
677
678 for (auto at : getTracksOfType<AudioTrack> (*this, true))
679 for (auto c : at->getClips())
680 if (auto acb = dynamic_cast<AudioClipBase*> (c))
681 acb->hideMelodyneWindow();
682
683 engine.getActiveEdits().edits.removeFirstMatchingValue (this);
684 masterReference.clear();
685 changeResetterTimer.reset();
686
687 if (transportControl != nullptr)
688 transportControl->freePlaybackContext();
689
690 // must only delete an edit with the message thread locked - many things on the message thread may be in the
691 // middle of using it at this point..
692 jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
693 jassert (! getTransport().isPlayContextActive());
694 jassert (numUndoTransactionInhibitors == 0);
695
697 changedPluginsList.reset();
698 editInputDevices.reset();
699 treeWatcher.reset();
700
701 notifyListenersOfDeletion();
702
703 engine.getExternalControllerManager().detachFromEdit (this);
704
705 pluginChangeTimer.reset();
706
707 undoManager.clearUndoHistory();
708
709 for (auto rt : rackTypes->getTypes())
710 rt->hideWindowForShutdown();
711
712 pluginCache.reset();
713
714 globalMacros.reset();
715 masterVolumePlugin.reset();
716 masterPluginList->releaseObjects();
717 masterPluginList.reset();
718 trackList.reset();
719 mirroredPluginUpdateTimer.reset();
720 rackTypes.reset();
721 undoTransactionTimer.reset();
722 markerManager.reset();
723 araDocumentHolder.reset();
724 frozenTrackCallback.reset();
725
726 pitchSequence.freeResources();
727 tempoSequence.freeResources();
728
729 pluginCache.reset();
730}
731
732//==============================================================================
734{
735 if (auto item = getProjectItemForEdit (*this))
736 return item->getName();
737
738 return {};
739}
740
742{
743 editProjectItemID = newID;
744 state.setProperty (IDs::projectID, editProjectItemID.load().toString(), nullptr);
745}
746
747Edit::ScopedRenderStatus::ScopedRenderStatus (Edit& ed, bool shouldReallocateOnDestruction)
748 : edit (ed), reallocateOnDestruction (shouldReallocateOnDestruction)
749{
750 TRACKTION_ASSERT_MESSAGE_THREAD
751 jassert (edit.performingRenderCount >= 0);
752 ++edit.performingRenderCount;
754}
755
757{
758 TRACKTION_ASSERT_MESSAGE_THREAD
759 jassert (edit.performingRenderCount > 0);
760 --edit.performingRenderCount;
761
762 if (edit.performingRenderCount == 0 && reallocateOnDestruction && edit.shouldPlay())
764}
765
766
767//==============================================================================
768void Edit::initialise (const Options& options)
769{
771 const StopwatchTimer loadTimer;
772
773 if (loadContext != nullptr)
774 loadContext->progress = 0.0f;
775
776 treeWatcher = std::make_unique<TreeWatcher> (*this, state);
777
778 isLoadInProgress = true;
779 tempDirectory = juce::File();
780
781 if (! state.hasProperty (IDs::creationTime))
782 addValueTreeProperties (state,
783 IDs::appVersion, engine.getPropertyStorage().getApplicationVersion(),
784 IDs::creationTime, juce::Time::getCurrentTime().toMilliseconds());
785
786 lastSignificantChange.referTo (state, IDs::lastSignificantChange, nullptr,
788
789 globalMacros = std::make_unique<GlobalMacros> (*this);
790 initialiseTempoAndPitch();
791 initialiseTransport();
792 initialiseVideo();
793 initialiseClickTrack();
794 initialiseMasterVolume();
795 initialiseRacks();
796 initialiseMasterPlugins();
797 initialiseAudioDevices();
798 loadTracks();
799
800 if (loadContext != nullptr)
801 loadContext->progress = 1.0f;
802
803 initialiseTracks (options);
804 initialiseARA();
806 readFrozenTracksFiles();
807
808 getLength(); // forcibly update the length before the isLoadInProgress is disabled.
809
810 for (auto t : getAllTracks (*this))
812
813 initialiseControllerMappings();
814
815 callBlocking ([this]
816 {
817 TemporaryFileManager::purgeOrphanFreezeAndProxyFiles (*this);
818
819 // Must be set to false before curve updates
820 // but set inside here to give the message loop some time to dispatch async updates
821 isLoadInProgress = false;
822
823 for (auto mpl : getAllMacroParameterLists (*this))
824 for (auto mp : mpl->getMacroParameters())
825 mp->initialise();
826
827 for (auto ap : getAllAutomatableParams (true))
828 ap->updateStream();
829
830 for (auto effect : getAllClipEffects (*this))
831 effect->initialise();
832 });
833
835
836 // reset the change status asynchronously to take into account deferred updates
837 changeResetterTimer = std::make_unique<EditChangeResetterTimer> (*this);
838
839 #if TRACKTION_ENABLE_AUTOMAP && TRACKTION_ENABLE_CONTROL_SURFACES
840 if (shouldPlay())
841 if (auto na = engine.getExternalControllerManager().getAutomap())
842 na->load (*this);
843 #endif
844
845 auxBusses = state.getChildWithName ("AUXBUSNAMES");
846
848
849 DBG ("Edit loaded in: " << loadTimer.getDescription());
850}
851
852void Edit::initialiseTempoAndPitch()
853{
854 // Initiliase PitchSequence first as the TempoSequence depends on it
855 pitchSequence.initialise (*this, state.getOrCreateChildWithName (IDs::PITCHSEQUENCE, nullptr));
856
857 const bool needToLoadOldTempoData = ! state.getChildWithName (IDs::TEMPOSEQUENCE).isValid();
858 tempoSequence.setState (state.getOrCreateChildWithName (IDs::TEMPOSEQUENCE, nullptr), false);
859
860 if (needToLoadOldTempoData)
861 loadOldTimeSigInfo();
862}
863
864void Edit::initialiseTimecode (juce::ValueTree& transportState)
865{
866 timecodeFormat.referTo (state, IDs::timecodeFormat, nullptr);
867
868 recordingPunchInOut.referTo (transportState, IDs::recordPunchInOut, nullptr);
869 playInStopEnabled.referTo (transportState, IDs::endToEnd, nullptr, true);
870
871 timecodeOffset.referTo (transportState, IDs::midiTimecodeOffset, nullptr);
872 midiTimecodeSourceDeviceEnabled.referTo (transportState, IDs::midiTimecodeEnabled, nullptr);
873 midiTimecodeIgnoringHours.referTo (transportState, IDs::midiTimecodeIgnoringHours, nullptr);
874 midiTimecodeSourceDevice.referTo (transportState, IDs::midiTimecodeSourceDevice, nullptr);
875 midiMachineControlSourceDevice.referTo (transportState, IDs::midiMachineControlSourceDevice, nullptr);
876 midiMachineControlDestDevice.referTo (transportState, IDs::midiMachineControlDestDevice, nullptr);
877}
878
879void Edit::initialiseTransport()
880{
881 auto transportState = state.getOrCreateChildWithName (IDs::TRANSPORT, nullptr);
882 initialiseTimecode (transportState);
883}
884
885void Edit::initialiseMasterVolume()
886{
887 auto* um = &undoManager;
888
889 auto mvTree = state.getOrCreateChildWithName (IDs::MASTERVOLUME, nullptr);
890 auto masterVolState = mvTree.getChildWithName (IDs::PLUGIN);
891 auto oldMasterVolState = state.getChildWithName (IDs::PLUGIN);
892
893 if (oldMasterVolState.isValid())
894 {
895 // To fix a bug with the T6 launch version we need to copy the incorrect master state tree
896 copyValueTree (masterVolState, oldMasterVolState, nullptr);
897 state.removeChild (oldMasterVolState, nullptr);
898 mvTree.addChild (masterVolState, -1, nullptr);
899 }
900
901 if (! masterVolState.isValid())
902 {
903 masterVolState = VolumeAndPanPlugin::create();
904 masterVolState.setProperty (IDs::volume, decibelsToVolumeFaderPosition (-3.0f), nullptr);
905 mvTree.addChild (masterVolState, -1, nullptr);
906 }
907
908 masterVolumePlugin = new VolumeAndPanPlugin (*this, masterVolState, true);
909
910 masterFadeIn.referTo (mvTree, IDs::fadeIn, um);
911 masterFadeOut.referTo (mvTree, IDs::fadeOut, um);
912 masterFadeInType.referTo (mvTree, IDs::fadeInType, um, AudioFadeCurve::concave);
913 masterFadeOutType.referTo (mvTree, IDs::fadeOutType, um, AudioFadeCurve::concave);
914
915 if (mvTree.hasProperty (IDs::left))
916 {
917 // support for loading legacy edits
918 const float masterVolL = (float) mvTree.getProperty (IDs::left, decibelsToVolumeFaderPosition (0.0f));
919 const float masterVolR = (float) mvTree.getProperty (IDs::right, decibelsToVolumeFaderPosition (0.0f));
920
921 const float leftGain = volumeFaderPositionToGain (masterVolL);
922 const float rightGain = volumeFaderPositionToGain (masterVolR);
923 const float volGain = (leftGain + rightGain) * 0.5f;
924
925 const float masterVolumeFaderPos = gainToVolumeFaderPosition (volGain);
926 const float masterPan = (rightGain - volGain) / volGain;
927
928 getMasterSliderPosParameter()->getCurve().clear();
929 getMasterSliderPosParameter()->setParameter (masterVolumeFaderPos, juce::dontSendNotification);
930
931 getMasterPanParameter()->getCurve().clear();
932 getMasterPanParameter()->setParameter (masterPan, juce::dontSendNotification);
933 }
934}
935
936void Edit::initialiseClickTrack()
937{
938 auto click = state.getOrCreateChildWithName (IDs::CLICKTRACK, nullptr);
939
940 clickTrackGain.referTo (click, IDs::level, nullptr);
941
943 clickTrackGain = engine.getPropertyStorage().getProperty (SettingID::lastClickTrackLevel, 0.6);
944
945 clickTrackEnabled.referTo (click, IDs::active, nullptr);
946 clickTrackEmphasiseBars.referTo (click, IDs::emphasiseBars, nullptr);
947 clickTrackRecordingOnly.referTo (click, IDs::onlyRecording, nullptr);
948
949 clickTrackDevice.referTo (click, IDs::outputDevice, nullptr);
950}
951
952void Edit::loadTracks()
953{
954 trackCompManager->initialise (state.getOrCreateChildWithName (IDs::TRACKCOMPS, nullptr));
955
956 // Make sure tempo + marker tracks are first (their order in the XML may be wrong so sort them now)
958
959 trackList = std::make_unique<TrackList> (*this, state);
960 treeWatcher->linkedClipsChanged();
961 updateTrackStatuses();
962}
963
964void Edit::removeZeroLengthClips()
965{
966 Clip::Array clipsToRemove;
967
968 for (auto t : getClipTracks (*this))
969 for (auto& c : t->getClips())
970 if (c->getPosition().getLength().inSeconds() <= 0.0)
971 clipsToRemove.add (c);
972
973 for (auto& c : clipsToRemove)
974 c->removeFromParent();
975}
976
977void Edit::initialiseTracks (const Options& options)
978{
979 // If the tempo track hasn't been created yet this is a new Edit
980 if (getTempoTrack() == nullptr)
981 {
982 ensureNumberOfAudioTracks (static_cast<int> (options.numAudioTracks));
983 updateTrackStatuses();
984 }
985
991 removeZeroLengthClips();
993
994 // some tracks may have referred to others that were created after them, so give
995 // their outputs a chance to catch up now..
996 for (auto t : getAudioTracks (*this))
997 {
998 t->initialise();
999 t->getOutput().updateOutput();
1000 }
1001
1002 for (auto t : getAllTracks (*this))
1003 t->refreshCurrentAutoParam();
1004}
1005
1006void Edit::initialiseAudioDevices()
1007{
1008 inputDeviceState = state.getOrCreateChildWithName (IDs::INPUTDEVICES, nullptr);
1009 editInputDevices = std::make_unique<EditInputDevices> (*this, inputDeviceState);
1010}
1011
1012void Edit::initialiseRacks()
1013{
1014 rackTypes->initialise (state.getOrCreateChildWithName (IDs::RACKS, nullptr));
1015}
1016
1017void Edit::initialiseMasterPlugins()
1018{
1019 masterPluginList->initialise (state.getOrCreateChildWithName (IDs::MASTERPLUGINS, nullptr));
1020}
1021
1022void Edit::initialiseVideo()
1023{
1024 auto* um = &undoManager;
1025
1026 auto videoState = state.getOrCreateChildWithName (IDs::VIDEO, nullptr);
1027 videoOffset.referTo (videoState, IDs::videoOffset, um);
1028 videoMuted.referTo (videoState, IDs::videoMuted, nullptr);
1029 videoSource.referTo (videoState, IDs::videoSource, nullptr);
1030
1031 if (videoSource.isUsingDefault()) // fallback for old tracktion edits that just stored filename
1032 loadOldVideoInfo (videoState);
1033}
1034
1035void Edit::initialiseControllerMappings()
1036{
1037 parameterControlMappings->loadFromEdit();
1038}
1039
1041{
1042 if (! automapState.isValid())
1043 automapState = state.getOrCreateChildWithName (IDs::AUTOMAPXML, &getUndoManager());
1044
1045 return automapState;
1046}
1047
1049{
1050 if (araDocumentHolder == nullptr)
1051 araDocumentHolder = std::make_unique<ARADocumentHolder> (*this, state.getOrCreateChildWithName (IDs::ARADOCUMENT, &getUndoManager()));
1052
1053 return *araDocumentHolder;
1054}
1055
1056void Edit::initialiseARA()
1057{
1058 auto areAnyClipsUsingMelodyne = [this]()
1059 {
1060 for (auto at : getTracksOfType<AudioTrack> (*this, true))
1061 for (auto c : at->getClips())
1062 if (auto acb = dynamic_cast<AudioClipBase*> (c))
1063 if (acb->isUsingMelodyne())
1064 return true;
1065
1066 return false;
1067 };
1068
1069 if (areAnyClipsUsingMelodyne())
1070 getARADocument().getPimpl();
1071}
1072
1074{
1075 #if TRACKTION_ENABLE_AUTOMAP && TRACKTION_ENABLE_CONTROL_SURFACES
1076 if (shouldPlay())
1077 if (auto na = engine.getExternalControllerManager().getAutomap())
1078 na->save (*this);
1079 #endif
1080
1081 jassert (state.getProperty (IDs::appVersion).toString().isNotEmpty());
1082
1083 addValueTreeProperties (state,
1084 IDs::appVersion, engine.getPropertyStorage().getApplicationVersion(),
1085 IDs::modifiedBy, engine.getPropertyStorage().getUserName(),
1086 IDs::projectID, editProjectItemID.load().toString());
1087
1088 for (auto p : getAllPlugins (*this, true))
1089 p->flushPluginStateToValueTree();
1090
1091 for (auto m : getAllModifiers (*this))
1092 m->flushPluginStateToValueTree();
1093
1094 for (auto at : getAudioTracks (*this))
1095 {
1096 at->flushStateToValueTree();
1097 at->getOutput().flushStateToValueTree();
1098 }
1099
1100 changedPluginsList->clear();
1101
1102 if (araDocumentHolder != nullptr)
1103 araDocumentHolder->flushStateToValueTree();
1104}
1105
1107{
1108 changedPluginsList->flushPluginStateIfNeeded (p);
1109}
1110
1112{
1113 return juce::Time (lastSignificantChange.get().getHexValue64());
1114}
1115
1117{
1118 // Dispatch pending changes syncronously or hasChanged will always get set to true
1120 hasChanged = false;
1121}
1122
1124{
1126 hasChanged = true;
1127}
1128
1130{
1131 return hasChanged;
1132}
1133
1135{
1136 shouldRestartPlayback = true;
1137
1138 if (! isTimerRunning())
1139 startTimer (1);
1140}
1141
1143{
1144 return transportControl->getCurrentPlaybackContext();
1145}
1146
1148{
1149 if (! abletonLink)
1150 abletonLink = std::make_unique<AbletonLink> (*transportControl);
1151
1152 return *abletonLink;
1153}
1154
1155//==============================================================================
1157{
1158 wastedMidiMessagesListeners.add (l);
1159}
1160
1162{
1163 wastedMidiMessagesListeners.remove (l);
1164}
1165
1167{
1168 wastedMidiMessagesListeners.call (&WastedMidiMessagesListener::warnOfWastedMidiMessages, d, t);
1169}
1170
1171//==============================================================================
1172static juce::Array<SelectionManager*> getSelectionManagers (const Edit& ed)
1173{
1175
1176 for (SelectionManager::Iterator sm; sm.next();)
1177 if (sm->getEdit() == &ed)
1178 sms.add (sm.get());
1179
1180 return sms;
1181}
1182
1183//==============================================================================
1184void Edit::undoOrRedo (bool isUndo)
1185{
1186 if (getTransport().isRecording())
1187 getTransport().stop (false, false, true);
1188
1189 if (isUndo)
1190 getUndoManager().undo();
1191 else
1192 getUndoManager().redo();
1193
1194 for (auto sm : getSelectionManagers (*this))
1195 {
1196 sm->keepSelectedObjectsOnScreen();
1197 sm->refreshPropertyPanel();
1198 }
1199}
1200
1201void Edit::undo() { undoOrRedo (true); }
1202void Edit::redo() { undoOrRedo (false); }
1203
1204Edit::UndoTransactionInhibitor::UndoTransactionInhibitor (Edit& e) : edit (&e) { ++e.numUndoTransactionInhibitors; }
1205Edit::UndoTransactionInhibitor::UndoTransactionInhibitor (const UndoTransactionInhibitor& o) : edit (o.edit) { if (auto e = edit.get()) ++(e->numUndoTransactionInhibitors); }
1206Edit::UndoTransactionInhibitor::~UndoTransactionInhibitor() { if (auto e = edit.get()) --(e->numUndoTransactionInhibitors); }
1207
1208//==============================================================================
1210{
1211 if (nextID == 0)
1212 {
1213 auto existingIDs = EditItemID::findAllIDs (state);
1214
1215 existingIDs.insert (existingIDs.end(), idsToAvoid.begin(), idsToAvoid.end());
1216
1217 trackCache.visitItems ([&] (auto i) { existingIDs.push_back (i->itemID); });
1218 clipCache.visitItems ([&] (auto i) { existingIDs.push_back (i->itemID); });
1219
1220 std::sort (existingIDs.begin(), existingIDs.end());
1221 nextID = existingIDs.empty() ? 1001 : (existingIDs.back().getRawID() + 1);
1222
1223 #if JUCE_DEBUG
1224 usedIDs.insert (existingIDs.begin(), existingIDs.end());
1225 #endif
1226 }
1227
1228 auto newID = EditItemID::fromRawID (nextID++);
1229
1230 #if JUCE_DEBUG
1231 jassert (usedIDs.find (newID) == usedIDs.end());
1232 usedIDs.insert (newID);
1233 #endif
1234
1235 return newID;
1236}
1237
1239{
1240 return createNewItemID ({});
1241}
1242
1244{
1245 if (trackList != nullptr)
1246 trackList->visitAllRecursive (f);
1247}
1248
1250{
1251 if (trackList != nullptr)
1252 trackList->visitAllTopLevel (f);
1253}
1254
1255void Edit::visitAllTracks (std::function<bool(Track&)> f, bool recursive) const
1256{
1257 if (trackList != nullptr)
1258 trackList->visitAllTracks (f, recursive);
1259}
1260
1261template <typename Predicate>
1262static Track* findTrackForPredicate (const Edit& edit, Predicate&& f)
1263{
1264 Track* result = nullptr;
1265
1266 edit.visitAllTracksRecursive ([&] (Track& t)
1267 {
1268 if (f (t))
1269 {
1270 result = &t;
1271 return false;
1272 }
1273
1274 return true;
1275 });
1276
1277 return result;
1278}
1279
1281{
1282 if (t < TimePosition())
1283 return {};
1284
1285 auto first = toPosition (getLength());
1286
1287 for (auto ct : getClipTracks (*this))
1288 {
1289 auto d = ct->getNextTimeOfInterest (t);
1290
1291 if (d < first && d > t)
1292 first = d;
1293 }
1294
1295 return first;
1296}
1297
1299{
1300 if (t < TimePosition())
1301 return {};
1302
1303 TimePosition last;
1304
1305 for (auto ct : getClipTracks (*this))
1306 {
1307 auto d = ct->getPreviousTimeOfInterest (t);
1308
1309 if (d > last)
1310 last = d;
1311 }
1312
1313 return last;
1314}
1315
1316//==============================================================================
1317Track::Ptr Edit::loadTrackFrom (juce::ValueTree& v)
1318{
1319 bool needsSanityCheck = false;
1320
1321 if (findTrackForID (*this, EditItemID::fromID (v)) != nullptr)
1322 {
1323 createNewItemID().writeID (v, nullptr);
1324 needsSanityCheck = true;
1325 }
1326
1327 if (auto tr = createTrack (v))
1328 {
1329 if (needsSanityCheck)
1330 tr->sanityCheckName();
1331
1332 return tr;
1333 }
1334
1336 return {};
1337}
1338
1339template <typename Type>
1340static Track::Ptr createAndInitialiseTrack (Edit& ed, const juce::ValueTree& v)
1341{
1342 Track::Ptr t = new Type (ed, v);
1343 t->initialise();
1344 return t;
1345}
1346
1347Track::Ptr Edit::createTrack (const juce::ValueTree& v)
1348{
1350
1351 if (v.hasType (IDs::TRACK)) return createAndInitialiseTrack<AudioTrack> (*this, v);
1352 if (v.hasType (IDs::MARKERTRACK)) return createAndInitialiseTrack<MarkerTrack> (*this, v);
1353 if (v.hasType (IDs::FOLDERTRACK)) return createAndInitialiseTrack<FolderTrack> (*this, v);
1354 if (v.hasType (IDs::AUTOMATIONTRACK)) return createAndInitialiseTrack<AutomationTrack> (*this, v);
1355 if (v.hasType (IDs::TEMPOTRACK)) return createAndInitialiseTrack<TempoTrack> (*this, v);
1356 if (v.hasType (IDs::CHORDTRACK)) return createAndInitialiseTrack<ChordTrack> (*this, v);
1357 if (v.hasType (IDs::ARRANGERTRACK)) return createAndInitialiseTrack<ArrangerTrack> (*this, v);
1358 if (v.hasType (IDs::MASTERTRACK)) return createAndInitialiseTrack<MasterTrack> (*this, v);
1359
1361 return {};
1362}
1363
1364//==============================================================================
1366{
1367 return findTrackForPredicate (*this, [] (Track& t) { return t.isMuted (true); }) != nullptr;
1368}
1369
1371{
1372 return findTrackForPredicate (*this, [] (Track& t) { return t.isSolo (true); }) != nullptr;
1373}
1374
1376{
1377 return findTrackForPredicate (*this, [] (Track& t) { return t.isSoloIsolate (true); }) != nullptr;
1378}
1379
1381{
1382 const bool anySolo = areAnyTracksSolo();
1383
1384 visitAllTracksRecursive ([anySolo] (Track& t)
1385 {
1386 t.updateAudibility (anySolo);
1387 return true;
1388 });
1389
1391
1392 if (ecm.isAttachedToEdit (this))
1393 ecm.updateMuteSoloLights (false);
1394}
1395
1396//==============================================================================
1398{
1399 if (! sceneList)
1400 sceneList = std::make_unique<SceneList> (state.getOrCreateChildWithName (IDs::SCENES, &undoManager), *this);
1401
1402 return *sceneList;
1403}
1404
1406{
1407 if (! launchQuantisation)
1408 launchQuantisation = std::make_unique<LaunchQuantisation> (state, *this);
1409
1410 return *launchQuantisation;
1411}
1412
1413//==============================================================================
1415{
1416 auto& dm = engine.getDeviceManager();
1417
1418 if (auto min = dm.findMidiInputDeviceForID (midiTimecodeSourceDevice))
1419 return min;
1420
1421 return dm.getMidiInDevice (0);
1422}
1423
1425{
1426 if (newDevice == nullptr)
1427 midiTimecodeSourceDevice.resetToDefault();
1428 else
1429 midiTimecodeSourceDevice = newDevice->getDeviceID();
1430
1433}
1434
1436{
1438 {
1440
1441 if (b)
1442 {
1443 if (auto mi = getCurrentMidiTimecodeSource())
1444 {
1446 return; // no need to restart, as setCurrentMidiTimecodeSource does this
1447 }
1448
1449 engine.getUIBehaviour().showWarningMessage (TRANS("No MIDI input was selected to be the timecode source"));
1450 }
1451
1454 }
1455}
1456
1458{
1459 if (timecodeOffset != newOffset)
1460 {
1461 timecodeOffset = newOffset;
1462
1465 }
1466}
1467
1469{
1470 return engine.getDeviceManager().findMidiInputDeviceForID (midiMachineControlSourceDevice);
1471}
1472
1474{
1475 if (newDevice == nullptr)
1476 midiMachineControlSourceDevice.resetToDefault();
1477 else
1478 midiMachineControlSourceDevice = newDevice->getDeviceID();
1479
1482}
1483
1485{
1486 auto& dm = engine.getDeviceManager();
1487
1488 for (int i = dm.getNumMidiOutDevices(); --i >= 0;)
1489 if (auto mout = dm.getMidiOutDevice (i))
1490 if (mout->getName() == midiMachineControlDestDevice)
1491 return mout;
1492
1493 return {};
1494}
1495
1497{
1498 if (newDevice == nullptr)
1499 midiMachineControlDestDevice.resetToDefault();
1500 else
1501 midiMachineControlDestDevice = newDevice->getName();
1502
1505}
1506
1513
1515{
1516 auto& dm = engine.getDeviceManager();
1517
1518 for (auto d : dm.getMidiInDevices())
1519 {
1520 if (auto min = dynamic_cast<PhysicalMidiInputDevice*> (d.get()))
1521 {
1522 min->setReadingMidiTimecode (midiTimecodeSourceDeviceEnabled
1523 && min->getName() == midiTimecodeSourceDevice);
1524 min->setAcceptingMMC (min->getName() == midiMachineControlSourceDevice);
1525 min->setIgnoresHours (midiTimecodeIgnoringHours);
1526 }
1527 }
1528
1529 for (int i = dm.getNumMidiOutDevices(); --i >= 0;)
1530 {
1531 if (auto mo = dm.getMidiOutDevice (i))
1532 {
1533 mo->setSendingMMC (mo->getName() == midiMachineControlDestDevice);
1534
1535 if (mo->isEnabled())
1536 mo->updateMidiTC (this);
1537 }
1538 }
1539}
1540
1541//==============================================================================
1542void Edit::loadOldTimeSigInfo()
1543{
1545
1546 auto oldInfoState = state.getChildWithName ("TIMECODETYPE");
1547
1548 if (! oldInfoState.isValid())
1549 return;
1550
1551 std::unique_ptr<juce::XmlElement> oldInfoXml (oldInfoState.createXml());
1552
1553 if (auto oldInfo = oldInfoXml.get())
1554 {
1555 if (auto sequenceNode = oldInfo->getFirstChildElement())
1556 if (sequenceNode->hasTagName ("SECTION"))
1557 oldInfo = sequenceNode;
1558
1559 if (auto tempo = tempoSequence.getTempo (0))
1560 tempo->set (BeatPosition(), oldInfo->getDoubleAttribute ("bpm", 120.0), 0, false);
1561
1562 if (auto timeSig = tempoSequence.getTimeSig (0))
1563 {
1564 timeSig->setStringTimeSig (oldInfo->getStringAttribute ("timeSignature", "4/4"));
1565 timeSig->triplets = oldInfo->getBoolAttribute ("triplets", false);
1566 }
1567
1568 state.removeChild (state.getChildWithName ("TIMECODETYPE"), nullptr);
1569 }
1570}
1571
1572void Edit::loadOldVideoInfo (const juce::ValueTree& videoState)
1573{
1575
1576 const juce::File videoFile (videoState["videoFile"].toString());
1577
1578 if (videoFile.existsAsFile())
1579 if (auto proj = getProjectForEdit (*this))
1580 if (auto newItem = proj->createNewItem (videoFile, ProjectItem::videoItemType(),
1581 videoFile.getFileNameWithoutExtension(),
1582 {}, ProjectItem::Category::video, false))
1583 videoSource = newItem->getID();
1584}
1585
1587{
1588 return timecodeFormat;
1589}
1590
1592{
1593 timecodeFormat = newFormat;
1594
1595 for (auto sm : getSelectionManagers (*this))
1596 if (! sm->containsType<ExternalPlugin>())
1597 sm->refreshPropertyPanel();
1598
1600
1601 if (ecm.isAttachedToEdit (*this))
1602 ecm.editPositionChanged (this, getTransport().position);
1603}
1604
1606{
1607 auto f = getTimecodeFormat();
1608
1609 switch (f.type)
1610 {
1611 case TimecodeType::millisecs: f = TimecodeDisplayFormat (TimecodeType::barsBeats); break;
1612 case TimecodeType::barsBeats: f = TimecodeDisplayFormat (TimecodeType::fps24); break;
1613 case TimecodeType::fps24: f = TimecodeDisplayFormat (TimecodeType::fps25); break;
1614 case TimecodeType::fps25: f = TimecodeDisplayFormat (TimecodeType::fps30); break;
1615 case TimecodeType::fps30: f = TimecodeDisplayFormat (TimecodeType::millisecs); break;
1616 default: jassertfalse; break;
1617 }
1618
1620}
1621
1622//==============================================================================
1624{
1625 TRACKTION_ASSERT_MESSAGE_THREAD
1626
1627 // notify clips of tempo changes
1628 for (auto t : getClipTracks (*this))
1629 for (auto& c : t->getClips())
1630 c->pitchTempoTrackChanged();
1631}
1632
1634{
1636
1637 for (auto t : getAudioTracks (*this))
1638 {
1639 for (auto& c : t->getClips())
1640 c->sourceMediaChanged();
1641
1642 for (auto cs : t->getClipSlotList().getClipSlots())
1643 if (auto c = cs->getClip())
1644 c->sourceMediaChanged();
1645 }
1646
1647 for (auto p : getAllPlugins (*this, true))
1648 p->sourceMediaChanged();
1649}
1650
1651//==============================================================================
1653{
1655}
1656
1658{
1659 getMasterPanParameter()->setParameter (p, juce::sendNotification);
1660}
1661
1662//==============================================================================
1664{
1665 if (auxBusses.isValid())
1666 return auxBusses.getChildWithProperty (IDs::bus, bus).getProperty (IDs::name);
1667
1668 return {};
1669}
1670
1671void Edit::setAuxBusName (int bus, const juce::String& nm)
1672{
1673 auto name = nm.substring (0, 20);
1674
1675 if (getAuxBusName (bus) != name)
1676 {
1677 if (! auxBusses.isValid())
1678 auxBusses = state.getOrCreateChildWithName ("AUXBUSNAMES", &getUndoManager());
1679
1680 auto b = auxBusses.getChildWithProperty (IDs::bus, bus);
1681
1682 if (name.isEmpty())
1683 {
1684 if (b.isValid())
1685 auxBusses.removeChild (b, nullptr);
1686 }
1687 else
1688 {
1689 if (! b.isValid())
1690 {
1691 b = createValueTree (IDs::NAME,
1692 IDs::bus, bus);
1693 auxBusses.addChild (b, -1, nullptr);
1694 }
1695
1696 b.setProperty (IDs::name, name, nullptr);
1697 }
1698 }
1699}
1700
1701//==============================================================================
1703{
1704 if (isTimerRunning())
1705 timerCallback();
1706}
1707
1708void Edit::timerCallback()
1709{
1710 if (! isFullyConstructed.load (std::memory_order_relaxed))
1711 return;
1712
1713 if (shouldRestartPlayback && shouldPlay())
1714 {
1715 shouldRestartPlayback = false;
1716 parameterControlMappings->checkForDeletedParams();
1717
1719 }
1720
1721 stopTimer();
1722}
1723
1724//==============================================================================
1726{
1727 if (! totalEditLength)
1728 {
1729 totalEditLength = TimeDuration();
1730
1731 for (auto t : getClipTracks (*this))
1732 totalEditLength = juce::jmax (*totalEditLength, t->getLength());
1733 }
1734
1735 return *totalEditLength;
1736}
1737
1739{
1740 auto t = TimePosition::fromSeconds (getLength().inSeconds());
1741 bool gotOne = false;
1742
1743 for (auto track : getClipTracks (*this))
1744 {
1745 if (auto first = track->getClips().getFirst())
1746 {
1747 gotOne = true;
1748 t = std::min (t, first->getPosition().getStart());
1749 }
1750 }
1751
1752 return gotOne ? t : TimePosition();
1753}
1754
1756{
1757 if (treeWatcher != nullptr)
1758 return treeWatcher->getClipsInLinkGroup (linkGroupID);
1759
1760 return {};
1761}
1762
1764{
1766
1768 return {};
1769
1770 auto parent = state;
1771
1772 if (insertPoint.parentTrackID.isValid())
1773 if (auto t = findTrackForID (*this, insertPoint.parentTrackID))
1774 parent = t->state;
1775
1776 auto preceeding = parent.getChildWithProperty (IDs::id, insertPoint.precedingTrackID);
1777
1778 return insertTrack (v, parent, preceeding, sm);
1779}
1780
1782 juce::ValueTree preceeding, SelectionManager* sm)
1783{
1784 if (! parent.isValid())
1785 parent = state;
1786
1787 parent.addChild (v, parent.indexOf (preceeding) + 1, &undoManager);
1788 auto newTrack = findTrackForState (*this, v);
1789
1790 if (newTrack != nullptr)
1791 {
1793
1794 if (sm != nullptr)
1795 {
1796 sm->addToSelection (newTrack);
1798 }
1799 }
1800
1801 return newTrack;
1802}
1803
1805{
1806 if (auto newTrack = insertNewTrack (insertPoint, IDs::TRACK, sm))
1807 {
1808 newTrack->pluginList.addDefaultTrackPlugins (false);
1809 return dynamic_cast<AudioTrack*> (newTrack.get());
1810 }
1811
1812 return {};
1813}
1814
1816{
1817 if (auto newTrack = insertNewTrack (insertPoint, IDs::FOLDERTRACK, sm))
1818 {
1819 newTrack->pluginList.addDefaultTrackPlugins (! asSubmix);
1820 return dynamic_cast<FolderTrack*> (newTrack.get());
1821 }
1822
1823 return {};
1824}
1825
1827{
1828 if (auto t = insertNewTrack (insertPoint, IDs::AUTOMATIONTRACK, sm))
1829 if (auto at = dynamic_cast<AutomationTrack*> (t.get()))
1830 return *at;
1831
1832 return {};
1833}
1834
1836{
1837 return insertTrack (insertPoint, juce::ValueTree (xmlType), sm);
1838}
1839
1841{
1843
1844 if (t == nullptr)
1845 return;
1846
1847 juce::ValueTree newParent (state), preceedingTrack;
1848
1849 if (destination.parentTrackID.isValid())
1850 if (auto parentTrack = findTrackForID (*this, destination.parentTrackID))
1851 newParent = parentTrack->state;
1852
1853 if (destination.precedingTrackID.isValid())
1854 if (auto p = findTrackForID (*this, destination.precedingTrackID))
1855 preceedingTrack = p->state;
1856
1857 auto oldParent = t->state.getParent();
1858 auto currentIndex = oldParent.indexOf (t->state);
1859 auto precedingIndex = preceedingTrack.isValid() ? newParent.indexOf (preceedingTrack) : 0;
1860
1861 if (newParent.isAChildOf (t->state) || newParent == t->state)
1862 return;
1863
1864 if (oldParent != newParent)
1865 {
1866 oldParent.removeChild (currentIndex, &undoManager);
1867 newParent.addChild (t->state, precedingIndex + 1, &undoManager);
1868 }
1869 else
1870 {
1871 auto newIndex = precedingIndex < currentIndex ? precedingIndex + 1 : precedingIndex;
1872
1873 if (currentIndex < 0 || newIndex < 0)
1874 return;
1875
1876 newParent.moveChild (currentIndex, newIndex, &undoManager);
1877 }
1878
1879 for (auto sm : getSelectionManagers (*this))
1880 sm->keepSelectedObjectsOnScreen();
1881}
1882
1884{
1886
1887 if (t == nullptr)
1888 return;
1889
1890 juce::ValueTree newParent (state), preceedingTrack;
1891
1892 if (destination.parentTrackID.isValid())
1893 if (auto parentTrack = findTrackForID (*this, destination.parentTrackID))
1894 newParent = parentTrack->state;
1895
1896 if (destination.precedingTrackID.isValid())
1897 if (auto p = findTrackForID (*this, destination.precedingTrackID))
1898 preceedingTrack = p->state;
1899
1900 auto oldParent = t->state.getParent();
1901 auto currentIndex = oldParent.indexOf (t->state);
1902 auto precedingIndex = preceedingTrack.isValid() ? newParent.indexOf (preceedingTrack) : 0;
1903
1904 if (newParent.isAChildOf (t->state) || newParent == t->state)
1905 return;
1906
1907 auto newTrack = t->state.createCopy();
1908
1910 EditItemID::remapIDs (newTrack, nullptr, t->edit, &remappedIDs);
1911
1912 if (oldParent != newParent)
1913 {
1914 newParent.addChild (newTrack, precedingIndex + 1, &undoManager);
1915 }
1916 else
1917 {
1918 auto newIndex = precedingIndex + 1;
1919
1920 if (currentIndex < 0 || newIndex < 0)
1921 return;
1922
1923 newParent.addChild (newTrack, newIndex, &undoManager);
1924 }
1925
1926 for (auto sm : getSelectionManagers (*this))
1927 {
1928 if (auto track = findTrackForState (t->edit, newTrack))
1929 sm->select (track, true);
1930
1931 sm->keepSelectedObjectsOnScreen();
1932 }
1933
1934 // Find any parameters on the Track that have modifier assignments
1935 // Check to see if they're assigned to the old modifier IDs
1936 // If they are, find the new modifier ID equivalents and update them
1937 // If they can't be found leave them if they're global or a parent of the new track.
1938 if (auto track = findTrackForState (t->edit, newTrack))
1939 {
1940 for (auto param : track->getAllAutomatableParams())
1941 {
1942 auto assignments = param->getAssignments();
1943
1944 for (int i = assignments.size(); --i >= 0;)
1945 {
1946 auto ass = assignments.getUnchecked (i);
1947
1948 // Macro reassignment is done during Plugin::giveNewIDsToPlugins so
1949 // we need to make sure we don't remove these
1950 if (dynamic_cast<MacroParameter::Assignment*> (ass.get()) != nullptr)
1951 continue;
1952
1953 const auto oldID = EditItemID::fromProperty (ass->state, IDs::source);
1954 const auto newID = remappedIDs[oldID];
1955
1956 if (newID.isValid())
1957 {
1958 ass->state.setProperty (IDs::source, newID, nullptr);
1959 }
1960 else
1961 {
1962 // If the modifier is on this track, keep it
1963 // If oldID is found in a global track, keep it
1964 // If oldID is found in a parent track, keep it
1965 if (auto tt = getTrackContainingModifier (t->edit, findModifierForID (t->edit, oldID)))
1966 if (tt == track || TrackList::isFixedTrack (tt->state) || track->isAChildOf (*tt))
1967 continue;
1968
1969 // Otherwise remove the assignment
1970 param->removeModifier (*ass);
1971 }
1972 }
1973 }
1974 }
1975}
1976
1977void Edit::updateTrackStatuses()
1978{
1980
1981 sanityCheckTrackNames();
1983
1984 // Only refresh if this Edit is being shown or it can cancel things like file previews
1985 for (auto sm : getSelectionManagers (*this))
1986 sm->refreshPropertyPanel();
1987}
1988
1989void Edit::updateTrackStatusesAsync()
1990{
1991 if (treeWatcher != nullptr && ! isLoading())
1992 treeWatcher->updateTrackStatusesAsync();
1993}
1994
1996{
1997 if (t != nullptr && containsTrack (*this, *t))
1998 {
2000 // You can't delete global tracks, just hide them instead
2001 jassert (! (t->isMasterTrack() || t->isMarkerTrack() || t->isTempoTrack() || t->isChordTrack()));
2002
2003 t->deselect();
2004 t->setFrozen (false, Track::groupFreeze);
2005
2006 // Remove any modifier assignments
2007 if (auto modifierList = t->getModifierList())
2008 for (auto mod : modifierList->getModifiers())
2009 mod->remove();
2010
2011 // this is needed in case we're undoing, and things in the track, like midi notes might be selected
2012 for (auto sm : getSelectionManagers (*this))
2013 sm->deselectAll();
2014
2015 auto audioTracks = getAudioTracks (*this);
2016
2017 auto clearEnabledTrackDevices = [this, &audioTracks] (AudioTrack* trackBeingDeleted)
2018 {
2019 // If the track we're deleting is the target track for a device, disable that device
2020 for (auto* idi : editInputDevices->getDevicesForTargetTrack (*trackBeingDeleted))
2021 if (idi->owner.isTrackDevice())
2022 idi->owner.setEnabled (false);
2023
2024 auto& wid = trackBeingDeleted->getWaveInputDevice();
2025 auto& mid = trackBeingDeleted->getMidiInputDevice();
2026
2027 const bool clearWave = wid.isEnabled();
2028 const bool clearMidi = mid.isEnabled();
2029
2030 if (clearWave || clearMidi)
2031 {
2033
2034 for (auto at : audioTracks)
2035 {
2036 if (clearWave) editInputDevices->clearInputsOfDevice (*at, wid, &getUndoManager());
2037 if (clearMidi) editInputDevices->clearInputsOfDevice (*at, mid, &getUndoManager());
2038 }
2039 }
2040 };
2041
2042 // Remove track from all tracks that might be ghosting it
2043 // (There's got to be a better way of doing this?)
2044 if (auto trackAsAudio = dynamic_cast<AudioTrack*> (t))
2045 {
2046 for (auto at : audioTracks)
2047 at->setTrackToGhost (trackAsAudio, false);
2048
2049 clearEnabledTrackDevices (trackAsAudio);
2050 }
2051
2052 if (auto ft = dynamic_cast<FolderTrack*> (t))
2053 {
2054 for (auto at : ft->getAllAudioSubTracks (true))
2055 {
2056 for (auto f : at->pluginList)
2057 if (auto ep = dynamic_cast<ExternalPlugin*> (f))
2058 ep->hideWindowForShutdown();
2059
2060 clearEnabledTrackDevices (at);
2061 editInputDevices->clearAllInputs (*at, &getUndoManager());
2062 }
2063 }
2064 else
2065 {
2066 for (auto f : t->pluginList)
2067 if (auto ep = dynamic_cast<ExternalPlugin*> (f))
2068 ep->hideWindowForShutdown();
2069
2070 if (auto at = dynamic_cast<AudioTrack*> (t))
2071 {
2072 clearEnabledTrackDevices (at);
2073 editInputDevices->clearAllInputs (*at, &getUndoManager());
2074 }
2075 }
2076
2077 t->state.getParent().removeChild (t->state, &undoManager);
2078 }
2079}
2080
2081void Edit::sanityCheckTrackNames()
2082{
2084 {
2085 t.sanityCheckName();
2086 return true;
2087 });
2088}
2089
2091{
2092 if (getArrangerTrack() == nullptr)
2093 {
2094 auto v = createValueTree (IDs::ARRANGERTRACK,
2095 IDs::name, TRANS("Arranger"));
2096
2097 state.addChild (v, 0, &getUndoManager());
2098 }
2099}
2100
2102{
2103 if (getTempoTrack() == nullptr)
2104 {
2105 auto v = createValueTree (IDs::TEMPOTRACK,
2106 IDs::name, TRANS("Global"));
2107
2108 state.addChild (v, 0, &getUndoManager());
2109 }
2110}
2111
2113{
2114 if (auto t = getMarkerTrack())
2115 {
2116 t->setName (TRANS("Marker"));
2117 }
2118 else
2119 {
2120 auto v = createValueTree (IDs::MARKERTRACK,
2121 IDs::name, TRANS("Marker"));
2122
2123 state.addChild (v, 0, &getUndoManager());
2124 }
2125}
2126
2128{
2129 if (auto t = getChordTrack())
2130 {
2131 t->setName (TRANS("Chord"));
2132 }
2133 else
2134 {
2135 auto v = createValueTree (IDs::CHORDTRACK,
2136 IDs::name, TRANS("Chord"));
2137
2138 state.addChild (v, 0, &getUndoManager());
2139 }
2140}
2141
2143{
2144 if (auto t = getMasterTrack())
2145 {
2146 t->setName (TRANS("Master"));
2147 }
2148 else
2149 {
2150 auto v = createValueTree (IDs::MASTERTRACK,
2151 IDs::name, TRANS("Master"));
2152
2153 state.addChild (v, 0, &getUndoManager());
2154 }
2155}
2156
2157void Edit::ensureNumberOfAudioTracks (int minimumNumTracks)
2158{
2159 while (getAudioTracks (*this).size() < minimumNumTracks)
2160 {
2162
2163 insertNewAudioTrack (TrackInsertPoint (nullptr, getTopLevelTracks (*this).getLast()), nullptr);
2164 }
2165}
2166
2167//==============================================================================
2169{
2170 return *pluginCache;
2171}
2172
2173//==============================================================================
2175{
2177
2178 for (auto t : getAllTracks (*this))
2179 t->sendMirrorUpdateToAllPlugins (plugin);
2180
2181 for (auto r : getRackList().getTypes())
2182 for (auto p : r->getPlugins())
2183 p->updateFromMirroredPluginIfNeeded (plugin);
2184
2185 masterPluginList->sendMirrorUpdateToAllPlugins (plugin);
2186}
2187
2189{
2190 if (mirroredPluginUpdateTimer != nullptr)
2191 mirroredPluginUpdateTimer->pluginChanged (p);
2192}
2193
2195{
2197
2198 for (auto p : getAllPlugins (*this, true))
2199 p->playStartedOrStopped();
2200}
2201
2203{
2204 jassert (! modifierTimers.contains (&mt));
2205 modifierTimers.add (&mt);
2206}
2207
2209{
2210 modifierTimers.removeFirstMatchingValue (&mt);
2211}
2212
2213void Edit::updateModifierTimers (TimePosition editTime, int numSamples) const
2214{
2215 const juce::ScopedLock sl (modifierTimers.getLock());
2216
2217 for (auto mt : modifierTimers)
2218 mt->updateStreamTime (editTime, numSamples);
2219}
2220
2221//==============================================================================
2223{
2224 lowLatencyMonitoring = enabled;
2226
2227 auto& deviceManager = engine.getDeviceManager().deviceManager;
2228 deviceManager.getAudioDeviceSetup (setup);
2229
2230 if (lowLatencyMonitoring)
2231 {
2232 if (deviceManager.getCurrentAudioDevice() != nullptr)
2233 {
2234 normalLatencyBufferSizeSeconds = setup.bufferSize / setup.sampleRate;
2235 setup.bufferSize = juce::roundToInt ((static_cast<double> (engine.getPropertyStorage().getProperty (SettingID::lowLatencyBuffer, 5.8)) / 1000.0) * setup.sampleRate);
2236 }
2237 else
2238 {
2239 normalLatencyBufferSizeSeconds = 0.0f;
2240 }
2241
2243 }
2244 else
2245 {
2247 setup.bufferSize = juce::roundToInt (normalLatencyBufferSizeSeconds * setup.sampleRate);
2248 }
2249
2250 if (auto dev = deviceManager.getCurrentAudioDevice())
2251 {
2252 if (! enabled || (enabled && setup.bufferSize < dev->getCurrentBufferSizeSamples()))
2253 {
2254 if (enabled)
2255 {
2256 for (auto bufferSize : dev->getAvailableBufferSizes())
2257 {
2258 if (bufferSize >= setup.bufferSize)
2259 {
2260 setup.bufferSize = bufferSize;
2261 break;
2262 }
2263 }
2264 }
2265
2266 const bool isPlaying = getTransport().isPlaying();
2268 deviceManager.setAudioDeviceSetup (setup, false);
2270
2271 if (isPlaying)
2272 getTransport().play (true);
2273 }
2274 }
2275}
2276
2278{
2279 for (auto p : getAllPlugins (*this, false))
2280 {
2281 if (newPlugins.contains (p->itemID))
2282 p->setEnabled (false); // disable new ones
2283 else if (lowLatencyDisabledPlugins.contains (p->itemID))
2284 p->setEnabled (true); // re-enable any previosuly disabled ones
2285 }
2286
2287 lowLatencyDisabledPlugins = newPlugins;
2288}
2289
2290//==============================================================================
2292{
2294
2295 for (auto p : getAllPlugins (*this, true))
2296 p->initialiseFully();
2297}
2298
2299//==============================================================================
2301{
2302 if (auto context = getCurrentPlaybackContext())
2303 return context->getInputFor (d);
2304
2305 return {};
2306}
2307
2309{
2310 if (auto context = getCurrentPlaybackContext())
2311 return context->getAllInputs();
2312
2313 return {};
2314}
2315
2317{
2318 return *editInputDevices;
2319}
2320
2321//==============================================================================
2322void Edit::pluginChanged (Plugin& p) noexcept
2323{
2324 if (isLoadInProgress)
2325 return;
2326
2327 changedPluginsList->pluginChanged (p);
2328 pluginChangeTimer->pluginChanged();
2329}
2330
2331//==============================================================================
2332void Edit::setClickTrackRange (TimeRange newTimes) noexcept
2333{
2334 clickMark1Time = newTimes.getStart();
2335 clickMark2Time = newTimes.getEnd();
2336}
2337
2338TimeRange Edit::getClickTrackRange() const noexcept
2339{
2340 return TimeRange::between (clickMark1Time, clickMark2Time);
2341}
2342
2344{
2345 if (clickTrackDevice == DeviceManager::getDefaultAudioOutDeviceName (false)
2346 || clickTrackDevice == DeviceManager::getDefaultMidiOutDeviceName (false))
2347 return clickTrackDevice;
2348
2349 if (auto out = engine.getDeviceManager().findOutputDeviceWithName (clickTrackDevice))
2350 return out->getName();
2351
2352 return DeviceManager::getDefaultAudioOutDeviceName (false);
2353}
2354
2356{
2357 auto& dm = engine.getDeviceManager();
2358
2359 if (auto out = dm.findOutputDeviceWithName (clickTrackDevice))
2360 return out == &dev;
2361
2362 if (clickTrackDevice == DeviceManager::getDefaultMidiOutDeviceName (false))
2363 return dm.getDefaultMidiOutDeviceID() == dev.getDeviceID();
2364
2365 return dm.getDefaultWaveOutDeviceID() == dev.getDeviceID();
2366}
2367
2369{
2370 clickTrackDevice = deviceName;
2372}
2373
2375{
2376 clickTrackGain = juce::jlimit (0.2f, 1.0f, gain);
2377 engine.getPropertyStorage().setProperty (SettingID::lastClickTrackLevel, gain);
2378}
2379
2380
2381//==============================================================================
2383{
2384 engine.getPropertyStorage().setProperty (SettingID::countInMode, static_cast<int> (mode));
2385}
2386
2388{
2389 return static_cast<CountIn> (static_cast<int> (engine.getPropertyStorage().getProperty (SettingID::countInMode)));
2390}
2391
2393{
2394 switch (getCountInMode())
2395 {
2396 case CountIn::none: return 0;
2397 case CountIn::oneBar: return tempoSequence.getTimeSig(0)->numerator;
2398 case CountIn::twoBar: return tempoSequence.getTimeSig(0)->numerator * 2;
2399 case CountIn::oneBeat: return 1;
2400 case CountIn::twoBeat: return 2;
2401 default: jassertfalse; break;
2402 }
2403
2404 return 0;
2405}
2406
2407//==============================================================================
2408void Edit::readFrozenTracksFiles()
2409{
2411
2412 auto freezeFiles = TemporaryFileManager::getFrozenTrackFiles (*this);
2413 bool resetFrozenness = freezeFiles.isEmpty();
2414
2415 for (auto& freezeFile : freezeFiles)
2416 {
2417 const auto outId = TemporaryFileManager::getDeviceIDFromFreezeFile (*this, freezeFile);
2418
2419 if (engine.getDeviceManager().findOutputDeviceForID (outId) == nullptr)
2420 {
2421 resetFrozenness = true;
2422 break;
2423 }
2424 }
2425
2426 // also reset frozenness if no tracks are frozen, to clear out dodgy old freeze files
2427 bool anyFrozen = false;
2428
2429 for (auto t : getAllTracks (*this))
2430 anyFrozen = anyFrozen || t->isFrozen (Track::groupFreeze);
2431
2432 if (resetFrozenness || ! anyFrozen)
2433 {
2434 AudioFile::deleteFiles (engine, freezeFiles);
2435
2436 for (auto t : getAllTracks (*this))
2437 {
2438 if (t->isFrozen (Track::groupFreeze))
2439 {
2440 if (auto at = dynamic_cast<AudioTrack*> (t))
2441 {
2442 at->frozen = false;
2443 at->changed();
2444 }
2445 }
2446 }
2447
2448 if (! isTimerRunning())
2449 startTimer (1);
2450 }
2451}
2452
2453void Edit::updateFrozenTracks()
2454{
2455 if (isRendering())
2456 return;
2457
2460 const ScopedRenderStatus srs (*this, true);
2461
2462 AudioFile::deleteFiles (engine, TemporaryFileManager::getFrozenTrackFiles (*this));
2463
2464 auto& dm = engine.getDeviceManager();
2465
2466 for (int j = dm.getNumOutputDevices(); --j >= 0;)
2467 {
2468 if (auto outputDevice = dynamic_cast<WaveOutputDevice*> (dm.getOutputDeviceAt (j)))
2469 {
2470 juce::BigInteger frozen;
2471 TimeDuration length;
2472 int i = 0;
2473
2474 for (auto t : getAllTracks (*this))
2475 {
2476 if (auto at = dynamic_cast<AudioTrack*> (t))
2477 {
2478 if (at->isFrozen (Track::groupFreeze)
2479 && at->getOutput().outputsToDevice (outputDevice->getName(), true)
2480 && ! at->isMuted (true))
2481 {
2482 frozen.setBit (i, true);
2483 length = juce::jmax (length, at->getLengthIncludingInputTracks());
2484 }
2485 }
2486
2487 ++i;
2488 }
2489
2490 if (frozen.countNumberOfSetBits() > 0 && length > TimeDuration())
2491 {
2492 length = length + TimeDuration::fromSeconds (5.0);
2493
2494 for (auto sm : getSelectionManagers (*this))
2495 sm->deselectAll();
2496
2497 juce::StringPairArray metadataFound;
2498
2499 Renderer::Parameters r (*this);
2500 r.tracksToDo = frozen;
2501 r.destFile = TemporaryFileManager::getFreezeFileForDevice (*this, *outputDevice);
2502 r.audioFormat = engine.getAudioFileFormatManager().getFrozenFileFormat();
2503 r.blockSizeForAudio = dm.getBlockSize();
2504 r.sampleRateForAudio = dm.getSampleRate();
2505 r.time = { {}, length };
2506 r.canRenderInMono = true;
2507 r.mustRenderInMono = false;
2508 r.usePlugins = true;
2509 r.useMasterPlugins = false;
2510 r.category = ProjectItem::Category::frozen;
2511 r.addAntiDenormalisationNoise = EditPlaybackContext::shouldAddAntiDenormalisationNoise (engine);
2512
2513 Renderer::renderToProjectItem (TRANS("Updating frozen tracks for output device \"XDVX\"")
2514 .replace ("XDVX", outputDevice->getName()) + "...", r);
2515
2516 if (! r.destFile.exists())
2517 {
2518 // failed to render properly - calling this should unfreeze all the tracks
2519 readFrozenTracksFiles();
2520 }
2521
2522 engine.getAudioFileManager().checkFilesForChanges();
2523 }
2524
2525 // once we've frozen the tracks we need to un-solo them or they will block other playing tracks
2526 i = 0;
2527
2528 for (auto t : getAllTracks (*this))
2529 {
2530 if (frozen[i++])
2531 {
2532 if (auto at = dynamic_cast<AudioTrack*> (t))
2533 {
2534 at->setSolo (false);
2535 at->state.sendPropertyChangeMessage (IDs::height);
2536 }
2537 }
2538 }
2539 }
2540 }
2541
2542 SelectionManager::refreshAllPropertyPanels();
2543}
2544
2545void Edit::needToUpdateFrozenTracks()
2546{
2547 // ignore this until fully loaded
2548 if (frozenTrackCallback != nullptr && ! isLoadInProgress)
2549 frozenTrackCallback->triggerAsyncUpdate();
2550}
2551
2553{
2554 for (auto t : getClipTracks (*this))
2555 if (t->areAnyClipsUsingFile (af))
2556 return true;
2557
2558 return false;
2559}
2560
2562{
2563 auto& apg = engine.getAudioFileManager().proxyGenerator;
2564
2565 for (auto t : getClipTracks (*this))
2566 {
2567 for (auto& c : t->getClips())
2568 {
2569 if (auto acb = dynamic_cast<AudioClipBase*> (c))
2570 {
2571 auto af = acb->getPlaybackFile();
2572
2573 if (apg.isProxyBeingGenerated (af))
2574 apg.deleteProxy (af);
2575 }
2576 }
2577 }
2578}
2579
2580juce::File Edit::getTempDirectory (bool createIfNonExistent) const
2581{
2582 juce::File result (tempDirectory);
2583
2584 if (result == juce::File())
2585 result = tempDirectory = engine.getTemporaryFileManager().getTempDirectory()
2586 .getChildFile ("edit_" + getProjectItemID().toStringSuitableForFilename());
2587
2588 if (createIfNonExistent && ! result.createDirectory())
2589 TRACKTION_LOG_ERROR ("Failed to create edit temp folder: " + result.getFullPathName());
2590
2591 return result;
2592}
2593
2595{
2596 tempDirectory = dir;
2597}
2598
2599//==============================================================================
2600void Edit::setVideoFile (const juce::File& f, juce::String importDesc)
2601{
2603 juce::File currentFile;
2604
2606 currentFile = item->getSourceFile();
2607
2608 if (f != currentFile)
2609 {
2610 videoSource.resetToDefault();
2611
2612 if (auto proj = getProjectForEdit (*this))
2613 {
2614 auto item = proj->getProjectItemForFile (f);
2615
2616 if (item == nullptr)
2617 item = proj->createNewItem (f, ProjectItem::videoItemType(), f.getFileNameWithoutExtension(),
2618 importDesc, ProjectItem::Category::video, false);
2619
2620 if (item != nullptr)
2621 videoSource = item->getID();
2622 }
2623 }
2624}
2625
2627{
2628 if (videoSource.get().isValid())
2630 return item->getSourceFile();
2631
2632 return {};
2633}
2634
2635//==============================================================================
2637{
2639
2640 list.add (getMasterSliderPosParameter().get());
2641 list.add (getMasterPanParameter().get());
2642
2643 list.addArray (globalMacros->getMacroParameters());
2644
2645 for (auto f : *masterPluginList)
2646 {
2647 list.addArray (f->getMacroParameters());
2648
2649 for (int j = 0; j < f->getNumAutomatableParameters(); ++j)
2650 list.add (f->getAutomatableParameter (j).get());
2651 }
2652
2653 for (auto rft : getRackList().getTypes())
2654 {
2655 for (auto mod : rft->getModifierList().getModifiers())
2656 list.addArray (mod->getAutomatableParameters());
2657
2658 list.addArray (rft->getMacroParameters());
2659
2660 for (auto p : rft->getPlugins())
2661 {
2662 if (auto mpl = p->getMacroParameterList())
2663 list.addArray (mpl->getMacroParameters());
2664
2665 list.addArray (p->getAutomatableParameters());
2666 }
2667 }
2668
2669 if (includeTrackParams)
2670 {
2671 // Skip the MasterTrack as that is covered by the masterPluginList above
2672 auto masterTrack = getMasterTrack();
2673
2675 {
2676 if (masterTrack != &t)
2677 {
2678 if (auto m = dynamic_cast<MacroParameterElement*> (&t))
2679 list.addArray (m->getMacroParameters());
2680
2681 list.addArray (t.getAllAutomatableParams());
2682 }
2683
2684 return true;
2685 });
2686 }
2687
2688 return list;
2689}
2690
2691void Edit::visitAllAutomatableParams (bool includeTrackParams, const std::function<void(AutomatableParameter&)>& visit) const
2692{
2693 visit (*masterVolumePlugin->volParam);
2694 visit (*masterVolumePlugin->panParam);
2695
2696 if (auto mpl = globalMacros->getMacroParameterList())
2697 mpl->visitMacroParameters (visit);
2698
2699 for (auto p : *masterPluginList)
2700 {
2701 if (auto mpl = p->getMacroParameterList())
2702 mpl->visitMacroParameters (visit);
2703
2704 p->visitAllAutomatableParams (visit);
2705 }
2706
2707 for (auto rft : getRackList().getTypes())
2708 {
2709 for (auto mod : rft->getModifierList().getModifiers())
2710 mod->visitAllAutomatableParams (visit);
2711
2712 if (auto mpl = rft->getMacroParameterList())
2713 mpl->visitMacroParameters (visit);
2714
2715 for (auto p : rft->getPlugins())
2716 {
2717 if (auto mpl = p->getMacroParameterList())
2718 mpl->visitMacroParameters (visit);
2719
2720 p->visitAllAutomatableParams (visit);
2721 }
2722 }
2723
2724 if (includeTrackParams)
2725 {
2726 // Skip the MasterTrack as that is covered by the masterPluginList above
2727 auto masterTrack = getMasterTrack();
2728
2730 {
2731 if (masterTrack != &t)
2732 {
2733 if (auto m = dynamic_cast<MacroParameterElement*> (&t))
2734 if (auto mpl = m->getMacroParameterList())
2735 mpl->visitMacroParameters (visit);
2736
2737 t.visitAllAutomatableParams (visit);
2738 }
2739
2740 return true;
2741 });
2742 }
2743}
2744
2745//==============================================================================
2747{
2748 return dynamic_cast<ArrangerTrack*> (findTrackForPredicate (*this, [] (Track& t) { return t.isArrangerTrack(); }));
2749}
2750
2752{
2753 return dynamic_cast<MarkerTrack*> (findTrackForPredicate (*this, [] (Track& t) { return t.isMarkerTrack(); }));
2754}
2755
2757{
2758 return dynamic_cast<TempoTrack*> (findTrackForPredicate (*this, [] (Track& t) { return t.isTempoTrack(); }));
2759}
2760
2762{
2763 return dynamic_cast<ChordTrack*> (findTrackForPredicate (*this, [] (Track& t) { return t.isChordTrack(); }));
2764}
2765
2767{
2768 return dynamic_cast<MasterTrack*> (findTrackForPredicate (*this, [] (Track& t) { return t.isMasterTrack(); }));
2769}
2770
2772{
2773 Metadata metadata;
2774 metadata.date = juce::String (juce::Time::getCurrentTime().getYear());
2775
2776 auto meta = state.getChildWithName (IDs::ID3VORBISMETADATA);
2777
2778 if (meta.isValid())
2779 {
2780 metadata.album = meta[IDs::album];
2781 metadata.artist = meta[IDs::artist];
2782 metadata.comment = meta[IDs::comment];
2783 metadata.date = meta.getProperty (IDs::date, metadata.date);
2784 metadata.genre = meta[IDs::genre];
2785 metadata.title = meta[IDs::title];
2786 metadata.trackNumber = meta.getProperty (IDs::trackNumber, 1);
2787 }
2788
2789 return metadata;
2790}
2791
2793{
2794 auto meta = state.getOrCreateChildWithName (IDs::ID3VORBISMETADATA, &getUndoManager());
2795
2796 addValueTreeProperties (meta,
2797 IDs::album, metadata.album,
2798 IDs::artist, metadata.artist,
2799 IDs::comment, metadata.comment,
2800 IDs::date, metadata.date,
2801 IDs::genre, metadata.genre,
2802 IDs::title, metadata.title,
2803 IDs::trackNumber, metadata.trackNumber);
2804}
2805
2806//==============================================================================
2808{
2809 return std::make_unique<Edit> (std::move (options));
2810}
2811
2813 bool tryToMatchTempo, bool* couldMatchTempo, juce::ValueTree midiPreviewPlugin,
2814 juce::ValueTree midiDrumPreviewPlugin, bool forceMidiToDrums, std::unique_ptr<Edit> edit)
2815{
2817
2818 if (edit == nullptr)
2820
2821 edit->ensureNumberOfAudioTracks (3);
2822 edit->isPreviewEdit = true;
2823 edit->getTransport().setPosition (0s);
2824
2825 if (couldMatchTempo != nullptr)
2826 *couldMatchTempo = false;
2827
2828 auto tracks = getAudioTracks (*edit);
2829 if (tracks.size() < 3)
2830 return {};
2831
2832 for (auto t : tracks)
2833 {
2834 auto& clips = t->getClips();
2835 for (int i = clips.size(); --i >= 0;)
2836 clips[i]->removeFromParent();
2837
2838 jassert (t->getClips().size() == 0);
2839 }
2840
2841 auto& master = edit->getMasterPluginList();
2842
2843 while (master.size() > 0)
2844 master[0]->removeFromParent();
2845
2846 bool isDrums = false;
2847
2848 auto midiTrack = tracks[1];
2849 auto drumTrack = tracks[2];
2850
2851 bool resizeClip = false;
2852 double clipTempo = 120.0;
2853
2854 if (Clip::isClipState (v))
2855 {
2857 mb.fromBase64Encoding (v[IDs::state].toString());
2858
2859 auto state = updateLegacyEdit (juce::ValueTree::readFromData (mb.getData(), mb.getSize()));
2860
2861 if (state.isValid())
2862 {
2863 EditItemID::remapIDs (state, nullptr, *edit);
2864 midiTrack->insertClipWithState (state);
2865 }
2866
2867 clipTempo = state[IDs::bpm];
2868
2869 if (clipTempo < 20.0)
2870 clipTempo = 120.0;
2871
2872 resizeClip = true;
2873 }
2874
2875 if (auto firstClip = midiTrack->getClips().getFirst())
2876 {
2877 if (dynamic_cast<StepClip*> (firstClip) != nullptr)
2878 isDrums = true;
2879 else if (auto mc = dynamic_cast<MidiClip*> (firstClip))
2880 isDrums = forceMidiToDrums || mc->isRhythm();
2881 }
2882
2883 auto track = midiTrack;
2884
2885 if (isDrums)
2886 {
2887 track = drumTrack;
2888
2889 if (auto firstClip = midiTrack->getClips().getFirst())
2890 firstClip->moveTo (*track);
2891 }
2892
2893 if (track->pluginList.size() < 3)
2894 {
2895 if (isDrums && midiDrumPreviewPlugin.isValid())
2896 track->pluginList.insertPlugin (midiDrumPreviewPlugin, 0);
2897 if (! isDrums && midiPreviewPlugin.isValid())
2898 track->pluginList.insertPlugin (midiPreviewPlugin, 0);
2899 }
2900
2901 double songTempo = 120.0;
2902 auto length = TimeDuration::fromSeconds (1.0);
2903
2904 // Get original clip length
2905 if (auto firstClip = track->getClips().getFirst())
2906 length = firstClip->getPosition().getLength();
2907
2908 // change tempo to match main edit
2909 if (tryToMatchTempo && editToMatch != nullptr)
2910 {
2911 auto& targetTempo = editToMatch->tempoSequence.getTempoAt (TimePosition::fromSeconds (0.01));
2912 auto firstTempo = edit->tempoSequence.getTempo (0);
2913 firstTempo->setBpm (targetTempo.getBpm());
2914
2915 songTempo = targetTempo.getBpm();
2916
2917 if (couldMatchTempo != nullptr)
2918 *couldMatchTempo = true;
2919
2920 auto& targetPitch = editToMatch->pitchSequence.getPitchAt (TimePosition::fromSeconds (0.01));
2921
2922 if (auto firstPitch = edit->pitchSequence.getPitch (0))
2923 {
2924 firstPitch->setPitch (targetPitch.getPitch());
2925 firstPitch->setScaleID (targetPitch.getScale());
2926 }
2927 else
2928 {
2930 }
2931 }
2932
2933 if (resizeClip)
2934 {
2935 if (auto firstClip = track->getClips().getFirst())
2936 {
2937 length = TimeDuration::fromSeconds (length.inSeconds() * clipTempo / songTempo);
2938 firstClip->setStart ({}, false, true);
2939 firstClip->setLength (length, true);
2940
2941 edit->getTransport().setLoopRange ({ TimePosition(), length });
2942 }
2943 }
2944
2945 if (v.hasType (IDs::PROGRESSION))
2946 {
2947 auto clipLength = edit->tempoSequence.toTime (BeatPosition::fromBeats (32 * 4));
2948 edit->getTransport().setLoopRange ({ TimePosition(), clipLength });
2949
2950 if (auto mc = track->insertMIDIClip ({ TimePosition(), clipLength }, nullptr))
2951 {
2952 if (auto pg = mc->getPatternGenerator())
2953 {
2954 pg->mode = PatternGenerator::Mode::chords;
2955 pg->octave = 6;
2956 pg->setChordProgression (v.createCopy());
2957 pg->generatePattern();
2958 }
2959 }
2960 }
2961 else if (v.hasType (IDs::BASSPATTERN))
2962 {
2963 auto clipLength = edit->tempoSequence.toTime (BeatPosition::fromBeats (32 * 4));
2964 edit->getTransport().setLoopRange ({ TimePosition(), clipLength });
2965
2966 if (auto mc = track->insertMIDIClip ({ TimePosition(), clipLength }, nullptr))
2967 {
2968 if (auto pg = mc->getPatternGenerator())
2969 {
2970 pg->mode = PatternGenerator::Mode::bass;
2971 pg->octave = 6;
2972 pg->setChordProgressionFromChordNames ({"i"});
2973 pg->setBassPattern (v.createCopy());
2974 pg->generatePattern();
2975 }
2976 }
2977 }
2978 else if (v.hasType (IDs::CHORDPATTERN))
2979 {
2980 auto clipLength = edit->tempoSequence.toTime (BeatPosition::fromBeats (32 * 4));
2981 edit->getTransport().setLoopRange ({ TimePosition(), clipLength });
2982
2983 if (auto mc = track->insertMIDIClip ({ TimePosition(), clipLength }, nullptr))
2984 {
2985 if (auto pg = mc->getPatternGenerator())
2986 {
2987 pg->mode = PatternGenerator::Mode::chords;
2988 pg->octave = 6;
2989 pg->setChordProgressionFromChordNames ({"i"});
2990 pg->setChordPattern (v.createCopy());
2991 pg->generatePattern();
2992 }
2993 }
2994 }
2995
2996 return edit;
2997}
2998
3000 bool tryToMatchTempo, bool tryToMatchPitch, bool* couldMatchTempo,
3001 juce::ValueTree midiPreviewPlugin,
3002 juce::ValueTree midiDrumPreviewPlugin,
3003 bool forceMidiToDrums,
3005{
3007
3008 if (edit == nullptr)
3010
3011 edit->ensureNumberOfAudioTracks (3);
3012 edit->isPreviewEdit = true;
3013 edit->getTransport().setPosition (0s);
3014
3015 if (couldMatchTempo != nullptr)
3016 *couldMatchTempo = false;
3017
3018 auto tracks = getAudioTracks (*edit);
3019 if (tracks.size() < 3)
3020 return {};
3021
3022 for (auto t : tracks)
3023 {
3024 auto& clips = t->getClips();
3025 for (int i = clips.size(); --i >= 0;)
3026 clips[i]->removeFromParent();
3027
3028 jassert (t->getClips().size() == 0);
3029 }
3030
3031 auto& master = edit->getMasterPluginList();
3032 while (master.size() > 0)
3033 master[0]->removeFromParent();
3034
3035 edit->setMasterVolumeSliderPos (decibelsToVolumeFaderPosition (0.0));
3036
3037 auto audioTrack = tracks[0];
3038 auto midiTrack = tracks[1];
3039 auto drumTrack = tracks[2];
3040
3041 bool isMidi = file.hasFileExtension (juce::String (midiFileWildCard).removeCharacters ("*"));
3042
3043 if (isMidi)
3044 {
3045 if (auto fin = file.createInputStream())
3046 {
3047 juce::MidiFile mf;
3048 mf.readFrom (*fin);
3050
3052
3053 for (int i = 0; i < mf.getNumTracks(); ++i)
3054 sequence.addSequence (*mf.getTrack (i), 0.0, 0.0, 1.0e6);
3055
3056 sequence.updateMatchedPairs();
3057
3058 int ch10Count = 0, allCount = 0;
3059
3060 for (auto m : sequence)
3061 {
3062 auto msg = m->message;
3063
3064 if (msg.isNoteOn())
3065 {
3066 allCount++;
3067
3068 if (msg.getChannel() == 10)
3069 ch10Count++;
3070 }
3071 }
3072
3073 bool isDrums = forceMidiToDrums || (float (ch10Count) / allCount > 90.0f);
3074 auto track = isDrums ? drumTrack : midiTrack;
3075
3076 if (auto mc = track->insertMIDIClip ({ TimePosition(), TimePosition::fromSeconds (1.0) }, nullptr))
3077 {
3078 if (track->pluginList.size() < 3)
3079 {
3080 if (isDrums && midiDrumPreviewPlugin.isValid())
3081 track->pluginList.insertPlugin (midiDrumPreviewPlugin, 0);
3082 if (! isDrums && midiPreviewPlugin.isValid())
3083 track->pluginList.insertPlugin (midiPreviewPlugin, 0);
3084 }
3085
3086 // Find bpm of source midi file
3087 double srcBpm = 120.0;
3089 mf.findAllTempoEvents (tempos);
3090
3091 if (auto* evt = tempos.getEventPointer (0))
3092 if (evt->message.isTempoMetaEvent())
3093 srcBpm = 4.0 * 60.0 / (4 * evt->message.getTempoSecondsPerQuarterNote());
3094
3095 // set tempo to that of midi file
3096 edit->tempoSequence.getTempo (0)->setBpm (srcBpm);
3097
3098 // add midi notes
3099 mc->mergeInMidiSequence (sequence, MidiList::NoteAutomationType::none);
3100 auto length = sequence.getEndTime();
3101
3102 // change tempo to match main edit
3103 if (tryToMatchTempo && editToMatch != nullptr)
3104 {
3105 auto& targetTempo = editToMatch->tempoSequence.getTempoAt (TimePosition::fromSeconds (0.01));
3106 auto firstTempo = edit->tempoSequence.getTempo (0);
3107 firstTempo->setBpm (targetTempo.getBpm());
3108
3109 if (couldMatchTempo != nullptr)
3110 *couldMatchTempo = true;
3111 }
3112
3113 auto dstBpm = edit->tempoSequence.getTempo (0)->getBpm();
3114
3115 length *= srcBpm / dstBpm;
3116
3117 if (length < 0.001)
3118 length = 2;
3119
3120 const TimeRange timeRange (TimePosition(), TimeDuration::fromSeconds (length));
3121 mc->setPosition ({ timeRange, TimeDuration() });
3122 edit->getTransport().setLoopRange (timeRange);
3123 }
3124 }
3125 }
3126 else
3127 {
3128 const AudioFile af (engine, file);
3129 auto length = af.getLength();
3130
3131 if (auto wc = dynamic_cast<WaveAudioClip*> (audioTrack->insertNewClip (TrackItem::Type::wave, { 0_tp, 1_tp }, nullptr)))
3132 {
3133 wc->setUsesProxy (false);
3134 wc->getSourceFileReference().setToDirectFileReference (file, false);
3135
3136 if (editToMatch != nullptr)
3137 {
3138 wc->setTimeStretchMode (TimeStretcher::disabled);
3139 if (tryToMatchTempo || tryToMatchPitch)
3140 {
3141 wc->setLoopInfo (af.getInfo().loopInfo);
3142
3143 engine.getEngineBehaviour().newClipAdded (*wc, false);
3144 }
3145
3146 auto& targetTempo = editToMatch->tempoSequence.getTempoAt (TimePosition::fromSeconds (0.01));
3147 auto targetPitch = editToMatch->pitchSequence.getPitch (0);
3148
3149 if (tryToMatchTempo)
3150 {
3151 auto firstTempo = edit->tempoSequence.getTempo (0);
3152 firstTempo->setBpm (targetTempo.getBpm());
3153 engine.getEngineBehaviour().newClipAdded (*wc, false);
3154
3155 edit->setTimecodeFormat (editToMatch->getTimecodeFormat());
3156 AudioFileInfo wi = wc->getWaveInfo();
3157
3158 if (wi.sampleRate > 0.0)
3159 {
3160 if (wc->getLoopInfo().getNumBeats() > 0)
3161 {
3162 length *= wc->getLoopInfo().getBpm (wi) / targetTempo.getBpm();
3163 wc->setTimeStretchMode (TimeStretcher::defaultMode);
3164 wc->setAutoTempo (true);
3165
3166 if (couldMatchTempo != nullptr)
3167 *couldMatchTempo = true;
3168 }
3169 }
3170 }
3171
3172 if (tryToMatchPitch)
3173 {
3174 auto firstPitch = edit->pitchSequence.getPitch (0);
3175 firstPitch->setPitch (targetPitch->getPitch());
3176 engine.getEngineBehaviour().newClipAdded (*wc, false);
3177
3178 edit->pitchSequence.copyFrom (editToMatch->pitchSequence);
3179 if (wc->getLoopInfo().getRootNote() != -1)
3180 {
3181 wc->setTimeStretchMode (TimeStretcher::defaultMode);
3182 wc->setAutoPitch (true);
3183 }
3184 else
3185 {
3186 wc->setAutoPitch (false);
3187 }
3188 }
3189 }
3190
3191 const TimeRange timeRange (0_tp, TimeDuration::fromSeconds (length));
3192 wc->setPosition ({ timeRange, 0_td });
3193 edit->getTransport().setLoopRange (timeRange);
3194 }
3195 }
3196
3197 return edit;
3198}
3199
3201{
3203 auto edit = createSingleTrackEdit (clip.edit.engine);
3204 edit->setTempDirectory (clip.edit.getTempDirectory (false));
3205 edit->tempoSequence.copyFrom (clip.edit.tempoSequence);
3206 edit->pitchSequence.copyFrom (clip.edit.pitchSequence);
3207 edit->setTimecodeFormat (clip.edit.getTimecodeFormat());
3208 edit->isPreviewEdit = true;
3209
3210 if (auto track = getFirstAudioTrack (*edit))
3211 {
3212 if (auto c = track->insertClipWithState (clip.state.createCopy()))
3213 {
3215 jassert (c->getPosition() == clip.getPosition());
3216 jassert (c->getLoopStart() == clip.getLoopStart());
3217 jassert (c->getLoopLength() == clip.getLoopLength());
3218 jassert (c->itemID == clip.itemID);
3219
3220 // The idea here is that the new clip should be bodged to share the same proxy
3221 // file as the old one, to avoid having to render a new one.. If this assertion
3222 // fails, then something has gone wrong with that plan
3223 jassert (dynamic_cast<AudioClipBase*> (c) == nullptr
3224 || ((AudioClipBase*) c)->getPlaybackFile() == ((AudioClipBase&) clip).getPlaybackFile()
3225 || ((AudioClipBase*) c)->isUsingMelodyne());
3226
3227 if (c->isMidi())
3228 track->getOutput().setOutputToDefaultDevice (true);
3229 }
3230 else
3231 {
3233 }
3234 }
3235
3236 return edit;
3237}
3238
3240{
3241 return std::make_unique<Edit> (e, roleToUse);
3242}
3243
3245{
3246 return createEdit (Options
3247 {
3248 e,
3249 editState,
3250 ProjectItemID::fromProperty (editState, IDs::projectID),
3251 roleToUse,
3252 nullptr,
3253 1, // undo levels
3254 {},
3255 {},
3256 0 // min num audio tracks
3257 });
3258}
3259
3260//==============================================================================
3261EditDeleter::EditDeleter() = default;
3262EditDeleter::~EditDeleter() { TRACKTION_ASSERT_MESSAGE_THREAD }
3263
3264void EditDeleter::deleteEdit (std::unique_ptr<Edit> ed)
3265{
3266 const juce::ScopedLock sl (listLock);
3267 editsToDelete.add (ed.release());
3268 triggerAsyncUpdate();
3269}
3270
3271void EditDeleter::handleAsyncUpdate()
3272{
3273 const juce::ScopedLock sl (listLock);
3274 editsToDelete.clear();
3275}
3276
3277//==============================================================================
3278ActiveEdits::ActiveEdits() = default;
3279
3280juce::Array<Edit*> ActiveEdits::getEdits() const
3281{
3283 eds.addArray (edits);
3284 return eds;
3285}
3286
3287}} // namespace tracktion { inline namespace engine
T begin(T... args)
void addArray(const Type *elementsToAdd, int numElementsToAdd)
int size() const noexcept
void add(const ElementType &newElement)
bool contains(ParameterType elementToLookFor) const
ElementType getLast() const noexcept
AudioDeviceSetup getAudioDeviceSetup() const
bool isUsingDefault() const
void referTo(ValueTree &tree, const Identifier &property, UndoManager *um)
Type get() const noexcept
const String & getFullPathName() const noexcept
File getChildFile(StringRef relativeOrAbsolutePath) const
static bool isAbsolutePath(StringRef path)
String getFileNameWithoutExtension() const
Result createDirectory() const
bool isValid() const noexcept
const String & toString() const noexcept
bool fromBase64Encoding(StringRef encodedString)
void * getData() noexcept
size_t getSize() const noexcept
void convertTimestampTicksToSeconds()
int getNumTracks() const noexcept
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true, int *midiFileType=nullptr)
void findAllTempoEvents(MidiMessageSequence &tempoChangeEvents) const
const MidiMessageSequence * getTrack(int index) const noexcept
MidiEventHolder * getEventPointer(int index) const noexcept
static String toHexString(IntegerType number)
String substring(int startIndex, int endIndex) const
static Time JUCE_CALLTYPE getCurrentTime() noexcept
int64 toMilliseconds() const noexcept
void stopTimer() noexcept
bool isTimerRunning() const noexcept
void startTimer(int intervalInMilliseconds) noexcept
void setMaxNumberOfStoredUnits(int maxNumberOfUnitsToKeep, int minimumTransactionsToKeep)
bool hasType(const Identifier &typeName) const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
bool isValid() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
int indexOf(const ValueTree &child) const noexcept
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
const var & getProperty(const Identifier &name) const noexcept
ValueTree getChildWithName(const Identifier &type) const
ValueTree getOrCreateChildWithName(const Identifier &type, UndoManager *undoManager)
ValueTree getChildWithProperty(const Identifier &propertyName, const var &propertyValue) const
bool hasProperty(const Identifier &name) const noexcept
A track similar to the MarkerTrack but can be used to move sections of an Edit around.
Base class for Clips that produce some kind of audio e.g.
A clip in an edit.
static bool isClipState(const juce::ValueTree &)
Checks whether a ValueTree is some kind of clip state.
The Tracktion Edit class!
juce::ValueTree inputDeviceState
The ValueTree of the input device states.
ChordTrack * getChordTrack() const
Returns the global ChordTrack.
static std::unique_ptr< Edit > createEditForPreviewingClip(Clip &)
Creates an Edit for previewing a Clip.
InputDeviceInstance * getCurrentInstanceForInputDevice(InputDevice *) const
Returns an InputDeviceInstance for a global InputDevice.
juce::CachedValue< float > clickTrackGain
The gain of the click track.
static std::unique_ptr< Edit > createEdit(Options)
Creates an Edit for the given options.
void restartPlayback()
Use this to tell the play engine to rebuild the audio graph if the toplogy has changed.
juce::ValueTree state
The ValueTree of the Edit state.
juce::ValueTree getAutomapState()
Returns the ValueTree used as the Auotmap state.
void ensureMasterTrack()
Creates a MasterTrack if there isn't currently one.
juce::String getClickTrackDevice() const
Returns the name of the device being used as the click track output.
void setTimecodeOffset(TimeDuration newOffset)
Sets the offset to apply to MIDI timecode.
EditPlaybackContext * getCurrentPlaybackContext() const
Returns the active EditPlaybackContext which is what is attached to the DeviceManager.
ARADocumentHolder & getARADocument()
Returns the ARA document handler.
ArrangerTrack * getArrangerTrack() const
Returns the global ArrangerTrack.
void sendMirrorUpdateToAllPlugins(Plugin &) const
Syncronously updates all Plugins[s] mirroring this one.
juce::CachedValue< TimeDuration > videoOffset
The duration in seconds of the video offset.
TimeRange getClickTrackRange() const noexcept
Returns the range the click track will be audible within.
PitchSequence pitchSequence
The global PitchSequence of this Edit.
std::function< juce::File()> editFileRetriever
This callback can be set to return the file for this Edit.
static std::unique_ptr< Edit > createEditForPreviewingFile(Engine &, const juce::File &, const Edit *editToMatch, bool tryToMatchTempo, bool tryToMatchPitch, bool *couldMatchTempo, juce::ValueTree midiPreviewPlugin, juce::ValueTree midiDrumPreviewPlugin={}, bool forceMidiToDrums=false, std::unique_ptr< Edit > editToUpdate={})
Creates an Edit for previewing a file.
void enableTimecodeSync(bool)
Toggles syncing to MIDI timecode.
juce::CachedValue< TimeDuration > masterFadeIn
The duration in seconds of the fade in.
void setLowLatencyDisabledPlugins(const juce::Array< EditItemID > &plugins)
First enables all currently disabled latency plugins and then disables the new set.
Track::Ptr insertTrack(TrackInsertPoint, juce::ValueTree, SelectionManager *)
Inserts a new Track with the given state in the Edit.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
SceneList & getSceneList()
Returns a list of Scenes in the Edit.
void warnOfWastedMidiMessages(InputDevice *, Track *)
Triggers a callback to any registered WastedMidiMessagesListener[s].
EditInputDevices & getEditInputDevices() noexcept
Returns the EditInputDevices for the Edit.
juce::CachedValue< juce::String > lastSignificantChange
The last time a change was made to the Edit.
void sendStartStopMessageToPlugins()
Calls Plugin::playStartedOrStopped to handle automation reacording.
void sendSourceFileUpdate()
Sends a 'sourceMediaChanged' call to all the clips.
CountIn getCountInMode() const
Returns the duration of the count in.
std::atomic< float > progress
Progress will be updated as the Edit loads.
void setEditMetadata(Metadata)
Sets the Metadata for the Edit.
juce::File getVideoFile() const
Returns the currently set video file.
EditRole
Enum used to determine what an Edit is being used for.
@ forEditing
Creates an Edit for normal use.
void visitAllAutomatableParams(bool includeTrackParams, const std::function< void(AutomatableParameter &)> &) const
Returns all automatable parameters in an Edit.
void ensureTempoTrack()
Creates a TempoTrack if there isn't currently one.
void setMidiTimecodeIgnoringHours(bool shouldIgnore)
Sets whether hours are ignored when syncing to MIDI timecode.
void deleteTrack(Track *)
Deletes a Track.
LaunchQuantisation & getLaunchQuantisation()
Returns the global launch quantisation.
Edit(Options)
Creates an Edit from a set of Options.
Metadata getEditMetadata()
Returns the current Metadata for the Edit.
void setTimecodeFormat(TimecodeDisplayFormat)
Sets the TimecodeDisplayFormat to use.
juce::CachedValue< bool > clickTrackRecordingOnly
Whether the click track should be audible only when recording.
void setClickTrackOutput(const juce::String &deviceName)
Sets the device to use as the click track output.
TimecodeDisplayFormat getTimecodeFormat() const
Returns the current TimecodeDisplayFormat.
void removeWastedMidiMessagesListener(WastedMidiMessagesListener *)
Removes a previously added WastedMidiMessagesListener.
void setClickTrackRange(TimeRange) noexcept
Sets a range for the click track to be audible within.
void sendTempoOrPitchSequenceChangedUpdates()
Sends a message to all the clips to let them know the tempo or pitch sequence has changed.
TimeDuration getLength() const
Returns the end time of last clip.
bool areAnyClipsUsingFile(const AudioFile &)
Returns true if any clips are using this file.
CountIn
An enum to determine the duration of the count in.
@ twoBeat
Two beats count in.
@ none
No count in, play starts immidiately.
void ensureMarkerTrack()
Creates a MarkerTrack if there isn't currently one.
void setCurrentMidiMachineControlSource(std::shared_ptr< MidiInputDevice >)
Sets the MidiInputDevice to be used as an MMC source.
void moveTrack(Track::Ptr, TrackInsertPoint)
Moves a track to a new position.
bool areAnyTracksSolo() const
Returns true if any tracks are soloed.
juce::CachedValue< bool > playInStopEnabled
Whether the audio engine should run when playback is stopped.
juce::Time getTimeOfLastChange() const
Returns the time the last change occurred.
juce::Array< AutomatableParameter * > getAllAutomatableParams(bool includeTrackParams) const
Returns all automatable parameters in an Edit.
juce::Array< Clip * > findClipsInLinkGroup(juce::String linkGroupID) const
Returns and Clip[s] with the given linkGroupID.
bool shouldPlay() const noexcept
Returns true if this Edit should be played back (or false if it was just opened for inspection).
void removeModifierTimer(ModifierTimer &)
Removes a ModifierTimer previously added.
void setLowLatencyMonitoring(bool enabled, const juce::Array< EditItemID > &plugins)
Toggles low latency monitoring for a set of plugins.
PluginCache & getPluginCache() noexcept
Returns the PluginCache which manages all active Plugin[s] for this Edit.
void ensureNumberOfAudioTracks(int minimumNumTracks)
Creates new tracks to ensure the minimum number.
AbletonLink & getAbletonLink() const noexcept
Returns the AbletonLink object.
void setMasterVolumeSliderPos(float)
Sets the master volume level.
juce::String getName()
Returns the name of the Edit if a ProjectItem can be found for it.
juce::ReferenceCountedObjectPtr< FolderTrack > insertNewFolderTrack(TrackInsertPoint, SelectionManager *, bool asSubmix)
Inserts a new FolderTrack in the Edit, optionally as a submix.
void redo()
Redoes the changes undone by the last undo.
juce::CachedValue< ProjectItemID > videoSource
The ProjectItemID of the video source.
juce::CachedValue< bool > videoMuted
Whether the video source is muted.
void visitAllTopLevelTracks(std::function< bool(Track &)>) const
Visits all top-level tracks (i.e.
std::atomic< bool > shouldExit
Can be set to true to cancel loading the Edit.
juce::ReferenceCountedObjectPtr< AutomationTrack > insertNewAutomationTrack(TrackInsertPoint, SelectionManager *)
Inserts a new AutomationTrack in the Edit.
bool areAnyTracksSoloIsolate() const
Returns true if any tracks are solo isolated.
juce::CachedValue< bool > clickTrackEmphasiseBars
Whether the click track should emphasise bars.
void updateMuteSoloStatuses()
Updates all the tracks and external controller mute/solo statuses.
std::shared_ptr< MidiInputDevice > getCurrentMidiMachineControlSource() const
Returns the MidiInputDevice being used as an MMC source.
void cancelAllProxyGeneratorJobs() const
Stops all proxy generator jobs clips may be performing.
juce::CachedValue< TimeDuration > masterFadeOut
The duration in seconds of the fade out.
void setCurrentMidiMachineControlDest(MidiOutputDevice *)
Sets the MidiInputDevice to be used as an MMC destination.
MasterTrack * getMasterTrack() const
Returns the global MasterTrack.
MarkerTrack * getMarkerTrack() const
Returns the global MarkerTrack.
void dispatchPendingUpdatesSynchronously()
If there's a change to send out to the listeners, do it now rather than waiting for the next timer me...
void setProjectItemID(ProjectItemID)
Sets the ProjectItemID of the Edit, this is also stored in the state.
static std::unique_ptr< Edit > createSingleTrackEdit(Engine &, EditRole role=EditRole::forEditing)
Creates an Edit with a single AudioTrack.
juce::CachedValue< bool > midiTimecodeIgnoringHours
Whether the MIDI timecode source ignores hours.
void flushState()
Saves the plugin, automap and ARA states to the state ValueTree.
std::shared_ptr< MidiInputDevice > getCurrentMidiTimecodeSource() const
Returns the MidiInputDevice being used as the MIDI timecode source.
juce::CachedValue< bool > clickTrackEnabled
Whether the click track is enabled.
void setCountInMode(CountIn)
Sets the duration of the count in.
void resetChangedStatus()
Resets the changed status so hasChangedSinceSaved returns false.
int numUndoLevelsToStore
The number of undo levels to use.
void setMasterPanPos(float)
Returns the master pan position.
void pluginChanged(Plugin &) noexcept
Plugins should call this when one of their parameters or state changes to mark the edit as unsaved.
void addWastedMidiMessagesListener(WastedMidiMessagesListener *)
Add a WastedMidiMessagesListener to be notified of wasted MIDI messages.
juce::Array< InputDeviceInstance * > getAllInputDevices() const
Returns all the active InputDeviceInstance[s] in the Edit.
bool isRendering() const noexcept
Returns true if the Edit is currently being rendered.
std::atomic< bool > completed
Set to true once the Edit has loaded.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
juce::ReferenceCountedObjectPtr< AudioTrack > insertNewAudioTrack(TrackInsertPoint, SelectionManager *)
Inserts a new AudioTrack in the Edit.
void setVideoFile(const juce::File &, juce::String importDesc)
Sets a video file to display.
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
juce::CachedValue< TimeDuration > timecodeOffset
The duration in seconds of the timecode offset.
void updateMirroredPlugin(Plugin &)
Adds this plugin to a list so mirrored Plugin[s] are updated asyncronously.
void setCurrentMidiTimecodeSource(std::shared_ptr< MidiInputDevice >)
Sets the MidiInputDevice being to be used as the MIDI timecode source.
void setClickTrackVolume(float gain)
Sets the volume of the click track.
std::function< juce::File(const juce::String &)> filePathResolver
An optional filePathResolver to use.
void ensureArrangerTrack()
Creates an ArrangerTrack if there isn't currently one.
TempoTrack * getTempoTrack() const
Returns the global TempoTrack.
void ensureChordTrack()
Creates a ChordTrack if there isn't currently one.
juce::CachedValue< AudioFadeCurve::Type > masterFadeInType
The curve type of the fade in.
void toggleTimecodeMode()
Toggles the TimecodeDisplayFormat through the available TimecodeType[s].
void invalidateStoredLength() noexcept
Invalidates the stored length so the next call to getLength will update form the Edit contents.
AutomatableParameter::Ptr getMasterSliderPosParameter() const
Returns the master volume AutomatableParameter.
static std::unique_ptr< Edit > createEditForExamining(Engine &, juce::ValueTree, EditRole role=EditRole::forExamining)
Creates an Edit that loads a state, using the role Edit::forExamining.
TimePosition getPreviousTimeOfInterest(TimePosition beforeThisTime)
Finds the previous marker or start/end of a clip after a certain time.
void copyTrack(Track::Ptr, TrackInsertPoint)
Copies a track to a new position.
int getNumCountInBeats() const
Returns the number of beats of the count in.
void visitAllTracksRecursive(std::function< bool(Track &)>) const
Visits all tracks in the Edit with the given function.
void flushPluginStateIfNeeded(Plugin &)
Saves the specified plugin state to the state ValueTree.
void updateMidiTimecodeDevices()
Updates the MIDI timecode/MMC devices.
juce::CachedValue< bool > recordingPunchInOut
Whether recoridng only happens within the in/out markers.
~Edit() override
Destructor.
AutomatableParameter::Ptr getMasterPanParameter() const
Returns the master pan AutomatableParameter.
TimePosition getFirstClipTime() const
Returns the time of first clip.
EditItemCache< Track > trackCache
Quick way to find and iterate all Track[s] in the Edit.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for this Edit.
bool hasChangedSinceSaved() const
Returns true if the Edit has changed since it was last saved.
void setTempDirectory(const juce::File &)
Sets the temp directory for the Edit to use.
static std::unique_ptr< Edit > createEditForPreviewingPreset(Engine &engine, juce::ValueTree, const Edit *editToMatch, bool tryToMatchTempo, bool *couldMatchTempo, juce::ValueTree midiPreviewPlugin, juce::ValueTree midiDrumPreviewPlugin={}, bool forceMidiToDrums=false, std::unique_ptr< Edit > editToUpdate={})
Creates an Edit for previewing a preset.
MidiOutputDevice * getCurrentMidiMachineControlDest() const
Returns the MidiInputDevice being used as an MMC destination.
juce::CachedValue< bool > midiTimecodeSourceDeviceEnabled
Whether a MIDI timecode source is enabled.
EditItemID createNewItemID() const
Returns a new EditItemID to use for a new EditItem.
void undo()
Undoes the most recent changes made.
ProjectItemID getProjectItemID() const noexcept
Returns the ProjectItemID of the Edit.
static int getDefaultNumUndoLevels() noexcept
Returns the default number of undo levels that should be used.
bool areAnyTracksMuted() const
Returns true if any tracks are muted.
std::function< juce::File(const juce::String &)> filePathResolver
This callback can be set to resolve file paths.
TimePosition getNextTimeOfInterest(TimePosition afterThisTime)
Finds the next marker or start/end of a clip after a certain time.
void addModifierTimer(ModifierTimer &)
Adds a ModifierTimer to be updated each block.
juce::File getTempDirectory(bool createIfNonExistent) const
Returns the temp directory the Edit it using.
void visitAllTracks(std::function< bool(Track &)>, bool recursive) const
Visits all tracks in the Edit with the given function.
EditItemCache< Clip > clipCache
Quick way to find and iterate all Clip[s] in the Edit.
void initialiseAllPlugins()
Initialises all the plugins.
bool isClickTrackDevice(OutputDevice &) const
Returns true if the given OutputDevice is being used as the click track output.
juce::CachedValue< AudioFadeCurve::Type > masterFadeOutType
The curve type of the fade out.
RackTypeList & getRackList() const noexcept
Returns the RackTypeList which contains all the RackTypes for the Edit.
juce::String getAuxBusName(int bus) const
Returns the name of an aux bus.
void setAuxBusName(int bus, const juce::String &name)
Sets the name of an aux bus.
Track::Ptr insertNewTrack(TrackInsertPoint, const juce::Identifier &xmlType, SelectionManager *)
Inserts a new Track with the given type in the Edit.
void updateModifierTimers(TimePosition editTime, int numSamples) const
Updates all the ModifierTimers with a given edit time and number of samples.
Engine & engine
A reference to the Engine.
std::function< juce::File()> editFileRetriever
An optional editFileRetriever to use.
void markAsChanged()
Marks the edit as being significantly changed and should therefore be saved.
Determines how the Edit will be created.
virtual EditLimits getEditLimits()
Should return the maximum number of elements that can be added to an Edit.
virtual void newClipAdded(Clip &, bool fromRecording)
Returns the defaults to be applied to new clips.
virtual bool shouldBypassedPluginsBeRemovedFromPlaybackGraph()
Should return if plugins which have been bypassed should be included in the playback graph.
The Engine is the central class for all tracktion sessions.
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.
ExternalControllerManager & getExternalControllerManager() const
Returns the ExternalControllerManager instance.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
ProjectManager & getProjectManager() const
Returns the ProjectManager instance.
ActiveEdits & getActiveEdits() const noexcept
Returns the ActiveEdits instance.
TemporaryFileManager & getTemporaryFileManager() const
Returns the TemporaryFileManager allowing to handle the default app and user temporary folders.
An instance of an InputDevice that's available to an Edit.
Represents an input device.
Base class for elements which can contain macro parameters.
A track to represent the master plugins.
@ none
No automation, add the sequence as plain MIDI with the channel of the clip.
Base class for audio or midi output devices, to which a track's output can be sent.
An ID representing one of the items in a Project.
ProjectItem::Ptr getProjectItem(ProjectItemID)
tries to find the project that contains an id, and open it as a ProjectItem.
static ProjectItem::Ptr renderToProjectItem(const juce::String &taskDescription, const Parameters &params)
Renders an Edit to a file and creates a new ProjectItem for it.
Represents the Scenes in an Edit.
void cancelAnyPendingUpdates()
If changed() has been called, this will cancel any pending async change notificaions.
Manages a list of items that are currently selected.
void keepSelectedObjectsOnScreen()
Scrolls whatever is necessary to keep the selected stuff visible.
TimeSigSetting * getTimeSig(int index) const
Returns the TimeSigSetting at a given index.
TempoSetting * getTempo(int index) const
Returns the TempoSetting at the given index.
TempoSetting & getTempoAt(TimePosition) const
Returns the TempoSetting at the given position.
void setState(const juce::ValueTree &, bool remapEdit)
Sets the state this TempoSequence should refer to.
A track to represent the "global" items such as tempo, key changes etc.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
@ groupFreeze
Freezes multiple tracks together in to a single file.
void ensureContextAllocated(bool alwaysReallocate=false)
Ensures an active EditPlaybackContext has been created so this Edit can be played back.
void freePlaybackContext()
Detaches the current EditPlaybackContext, removing it from the DeviceManager.
void play(bool justSendMMCIfEnabled)
Starts playback of an Edit.
static void stopAllTransports(Engine &, bool discardRecordings, bool clearDevices)
Stops all TransportControl[s] in the Engine playing.
void editHasChanged()
Triggers a playback graph rebuild.
void stop(bool discardRecordings, bool clearDevices, bool canSendMMCStop=true)
Stops recording, creating clips of newyly recorded files/MIDI data.
bool isPlaying() const
Returns true if the transport is playing.
void stopIfRecording()
Stops playback only if recording is currently in progress.
virtual void newTrackCreated(Track &)
Called when a new track is created from some kind of user action i.e.
virtual void showWarningMessage(const juce::String &message)
Should display a temporary warning message.
An audio clip that uses an audio file as its source.
T end(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define DBG(textToWrite)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
#define JUCE_DECLARE_WEAK_REFERENCEABLE(Class)
T load(T... args)
typedef float
T min(T... args)
constexpr Type jmax(Type a, Type b)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
sendNotification
dontSendNotification
int roundToInt(const FloatType value) noexcept
AudioTrack * getFirstAudioTrack(const Edit &edit)
Returns the first audio track in an Edit.
juce::Array< TrackType * > getTracksOfType(const Edit &, bool recursive)
Returns the tracks of a given type in an Edit.
Track * findTrackForState(const Edit &edit, const juce::ValueTree &v)
Returns the Track with a given state if contained in the Edit.
juce::Array< Track * > getTopLevelTracks(const Edit &edit)
Returns all of the non-foldered tracks in an Edit.
juce::Array< MacroParameterList * > getAllMacroParameterLists(const Edit &edit)
Returns all the MacroParameterLists in an Edit.
juce::Array< Track * > getAllTracks(const Edit &edit)
Returns all the tracks in an Edit.
bool containsTrack(const Edit &edit, const Track &track)
Returns true if the Edit contains this Track.
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
ProjectItem::Ptr getProjectItemForEdit(const Edit &e)
Tries to find the project item that refers to this edit (but may return nullptr!)
juce::Array< ClipTrack * > getClipTracks(const Edit &edit)
Returns all the ClipTracks in an Edit.
juce::ReferenceCountedArray< Modifier > getAllModifiers(const Edit &edit)
Returns all the Modifiers in an Edit.
Modifier::Ptr findModifierForID(ModifierList &ml, EditItemID modifierID)
Returns a Modifier if it can be found in the list.
Track * getTrackContainingModifier(const Edit &edit, const Modifier::Ptr &m)
Returns the Track containing a Modifier.
Track * findTrackForID(const Edit &edit, EditItemID id)
Returns the Track with a given ID if contained in the Edit.
Project::Ptr getProjectForEdit(const Edit &e)
Tries to find the project that contains this edit (but may return nullptr!)
juce::ValueTree createEmptyEdit(Engine &e)
Legacy, will be deprecated soon.
juce::ValueTree updateLegacyEdit(const juce::ValueTree &)
Converts old edit formats to the latest structure.
bool isRecording(EditPlaybackContext &epc)
Returns true if any inputs are currently recording.
int maxNumTracks
The maximum number of Track[s] an Edit can contain.
Plugin::Array getAllPlugins(const Edit &edit, bool includeMasterVolume)
Returns all the plugins in a given Edit.
juce::Array< ClipEffect * > getAllClipEffects(Edit &edit)
Returns all clip effects.
T release(T... args)
T sort(T... args)
T store(T... args)
Represents a position in beats.
Represents a duration in real-life time.
Represents a position in real-life time.
ID for objects of type EditElement - e.g.
void flushPluginStateIfNeeded(Plugin &p)
Flushes the plugin state to the Edit if it has changed.
void pluginChanged(Plugin &p)
Call to indicate a plugin's state has changed and needs saving.
void flushAll()
Flushes all changed plugins to the Edit.
ScopedRenderStatus(Edit &, bool shouldReallocateOnDestruction)
Constructs a ScopedRenderStatus, removing an Edit from the device manager.
Disables the creation of a new transaction.
UndoTransactionInhibitor(Edit &)
Creates an UndoTransactionInhibitor for an Edit.
Interface for classes that need to know about unused MIDI messages.
virtual void warnOfWastedMidiMessages(InputDevice *, Track *)=0
Callback to be notified when a MIDI message isn't used by a track because it doesn't have a plugin wh...
An Assignment between a MacroParameter and an AutomatableParameter.
Base class for objects which need to know about the global Edit time every block.
Defines the place to insert Track[s].
static void sortTracksByType(juce::ValueTree &editState, juce::UndoManager *)
Sorts a list of tracks by their type, placing global tracks at the top.
static bool isFixedTrack(const juce::ValueTree &) noexcept
Returns true if the track is fixed.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.