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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_FolderTrack.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
14FolderTrack::FolderTrack (Edit& ed, const juce::ValueTree& v)
15 : Track (ed, v, true)
16{
17 soloed.referTo (state, IDs::solo, nullptr);
18 muted.referTo (state, IDs::mute, nullptr);
19 soloIsolated.referTo (state, IDs::soloIsolate, nullptr);
20
21 pluginUpdater.setFunction ([this] { updatePlugins(); });
22}
23
24FolderTrack::~FolderTrack()
25{
26 notifyListenersOfDeletion();
27}
28
29void FolderTrack::initialise()
30{
31 Track::initialise();
32 updatePlugins();
33}
34
35juce::String FolderTrack::getSelectableDescription()
36{
37 return (isSubmixFolder() ? TRANS("Submix") : TRANS("Folder")) + " - \"" + getName() + "\"";
38}
39
40bool FolderTrack::isFolderTrack() const
41{
42 return true;
43}
44
45void FolderTrack::sanityCheckName()
46{
47 auto n = Track::getName();
48
49 auto checkName = [&n] (const juce::String& type)
50 {
51 return ((n.startsWithIgnoreCase ("Folder ")
52 || n.startsWithIgnoreCase (TRANS(type) + " "))
53 && n.substring (6).trim().containsOnly ("0123456789"));
54 };
55
56 if (checkName ("Folder") || checkName ("Submix"))
57 {
58 // For "Track 1" type names, leave the actual name empty.
59 resetName();
60 }
61}
62
63juce::String FolderTrack::getName() const
64{
65 auto n = Track::getName();
66
67 if (n.isEmpty())
68 n << (isSubmixFolder() ? TRANS("Submix")
69 : TRANS("Folder")) << ' ' << getFolderTrackNumber();
70
71 return n;
72}
73
74int FolderTrack::getFolderTrackNumber() const noexcept
75{
76 int result = 1;
77
78 edit.visitAllTracksRecursive ([&] (Track& t)
79 {
80 if (this == &t)
81 return false;
82
83 if (t.isFolderTrack())
84 ++result;
85
86 return true;
87 });
88
89 return result;
90}
91
92bool FolderTrack::isSubmixFolder() const
93{
94 for (auto p : pluginList)
95 if (dynamic_cast<VCAPlugin*> (p) == nullptr && dynamic_cast<TextPlugin*> (p) == nullptr)
96 return true;
97
98 return false;
99}
100
101TrackOutput* FolderTrack::getOutput() const noexcept
102{
103 if (! isSubmixFolder())
104 return nullptr;
105
106 for (auto t : getAllAudioSubTracks (true))
107 if (auto at = dynamic_cast<AudioTrack*> (t))
108 return &at->getOutput();
109
110 return nullptr;
111}
112
113juce::Array<Track*> FolderTrack::getInputTracks() const
114{
115 juce::Array<Track*> tracks;
116
117 for (auto track : getAllSubTracks (false))
118 {
119 if (dynamic_cast<AudioTrack*> (track) != nullptr)
120 tracks.add (track);
121
122 if (auto ft = dynamic_cast<FolderTrack*> (track))
123 if (ft->isSubmixFolder())
124 tracks.add (track);
125 }
126
127 return tracks;
128}
129
130bool FolderTrack::isMuted (bool includeMutingByDestination) const
131{
132 if (muted)
133 return true;
134
135 if (includeMutingByDestination)
136 if (auto p = getParentFolderTrack())
137 return p->isMuted (true);
138
139 return false;
140}
141
142bool FolderTrack::isSolo (bool includeIndirectSolo) const
143{
144 if (soloed)
145 return true;
146
147 if (includeIndirectSolo)
148 {
149 // If any of the parent tracks are soloed, this needs to be indirectly soloed
150 for (auto p = getParentFolderTrack(); p != nullptr; p = p->getParentFolderTrack())
151 if (p->isSolo (false))
152 return true;
153
154 if (! isPartOfSubmix())
155 if (auto output = getOutput())
156 if (auto dest = output->getDestinationTrack())
157 return dest->isSolo (true);
158
159 // If any sub-tracks are soloed, this needs to be indirectly soloed
160 bool anySubTracksSolo = false;
161
162 if (auto tl = getSubTrackList())
163 tl->visitAllRecursive ([&anySubTracksSolo] (Track& t)
164 {
165 if (t.isSolo (false))
166 {
167 anySubTracksSolo = true;
168 return false;
169 }
170
171 return true;
172 });
173
174 return anySubTracksSolo;
175 }
176
177 return false;
178}
179
180bool FolderTrack::isSoloIsolate (bool includeIndirectSolo) const
181{
182 if (soloIsolated)
183 return true;
184
185 if (includeIndirectSolo)
186 {
187 // If any of the parent tracks are solo isolate, this needs to be indirectly solo isolate
188 for (auto p = getParentFolderTrack(); p != nullptr; p = p->getParentFolderTrack())
189 if (p->isSoloIsolate (false))
190 return true;
191
192 if (! isPartOfSubmix())
193 if (auto output = getOutput())
194 if (auto dest = output->getDestinationTrack())
195 return dest->isSoloIsolate (true);
196
197 // If any sub-tracks are solo isoloate, this needs to be indirectly solo isolate
198 bool anySubTracksSolo = false;
199
200 if (auto tl = getSubTrackList())
201 tl->visitAllRecursive ([&anySubTracksSolo] (Track& t)
202 {
203 if (t.isSoloIsolate (false))
204 {
205 anySubTracksSolo = true;
206 return false;
207 }
208
209 return true;
210 });
211
212 return anySubTracksSolo;
213 }
214
215 return false;
216}
217
218float FolderTrack::getVcaDb (TimePosition time)
219{
220 const std::scoped_lock sl (pluginMutex);
221
222 if (auto ptr = vcaPlugin)
223 if (ptr->isEnabled())
224 return ptr->updateAutomationStreamAndGetVolumeDb (time);
225
226 return 0.0f;
227}
228
229TimeRange FolderTrack::getClipExtendedBounds (Clip& c)
230{
231 if (c.isGrouped())
232 if (auto cc = dynamic_cast<CollectionClip*> (c.getGroupParent()))
233 return cc->getEditTimeRange();
234
235 return c.getEditTimeRange();
236}
237
238void FolderTrack::generateCollectionClips (SelectionManager& sm)
239{
241
242 if (dirtyClips)
243 {
244 dirtyClips = false;
245 juce::Array<Clip*> selectedClips;
246
247 for (int i = collectionClips.size(); --i >= 0;)
248 {
249 if (auto cc = collectionClips[i])
250 {
251 // find all the clips that are indirectly selected
252 if (sm.isSelected (*cc) && ! cc->isDragging())
253 {
254 sm.deselect (cc.get());
255 selectedClips.addArray (cc->getClips());
256 }
257
258 // don't recreate an collection clips that are being dragged
259 if (! cc->isDragging())
260 collectionClips.remove (i);
261 }
262 }
263
264 // any collection clips that remain, adjust their edges
265 for (int i = 0; i < collectionClips.size(); ++i)
266 {
267 if (auto cc = collectionClips[i])
268 {
269 if (cc->getNumClips() > 0)
270 {
271 TimeRange totalRange;
272 bool first = true;
273
274 for (int j = cc->getNumClips(); --j >= 0;)
275 {
276 auto c = cc->getClip(j);
277
278 if (c == nullptr || c->getTrack() == nullptr)
279 {
280 cc->removeClip (c.get());
281 }
282 else
283 {
284 auto pos = c->getPosition();
285
286 if (pos.getLength() > TimeDuration::fromSeconds (0.000001))
287 {
288 if (first)
289 totalRange = pos.time;
290 else
291 totalRange = totalRange.getUnionWith (pos.time);
292
293 first = false;
294 }
295 }
296 }
297
298 cc->range = totalRange;
299 }
300 }
301 }
302
303 juce::Array<Clip*> clips;
304
305 // find all the clips on sub tracks
306 for (auto at : getAllAudioSubTracks (true))
307 clips.addArray (at->getClips());
308
309 TrackItem::sortByTime (clips);
310
311 // remove any that are already in a collection clip
312 for (int i = clips.size(); --i >= 0;)
313 {
314 auto c = clips.getUnchecked (i);
315
316 for (auto cc : collectionClips)
317 {
318 if (cc->containsClip (c))
319 {
320 clips.remove (i);
321 break;
322 }
323 }
324 }
325
326 // start recreating collection clips
327 CollectionClip* colClip = nullptr;
328
329 for (auto clip : clips)
330 {
331 const auto tolerance = 0.000001s;
332 auto bounds = getClipExtendedBounds (*clip);
333
334 if (bounds.getLength() > tolerance)
335 {
336 if (colClip == nullptr || bounds.getStart() + tolerance >= colClip->getPosition().getEnd())
337 {
338 colClip = new CollectionClip (*this);
339 colClip->range = bounds;
340 colClip->addClip (clip);
341
342 collectionClips.add (colClip);
343 }
344 else
345 {
346 if (bounds.getEnd() > colClip->getPosition().getEnd())
347 colClip->range = TimeRange (colClip->range.getStart(), bounds.getEnd());
348
349 colClip->addClip (clip);
350 }
351 }
352 }
353
354 // select collection clips that contain at least one clip that was indirectly selected before
355 for (auto cc : collectionClips)
356 for (auto c : selectedClips)
357 if (cc->containsClip (c))
358 sm.addToSelection (cc);
359 }
360 else
361 {
362 for (auto cc : collectionClips)
363 {
364 jassert (Selectable::isSelectableValid (cc));
365
366 TimeRange totalRange;
367 bool first = true;
368
369 for (auto c : cc->getClips())
370 {
371 auto bounds = getClipExtendedBounds (*c);
372
373 if (bounds.getLength() > 0.000001s)
374 {
375 if (first)
376 totalRange = bounds;
377 else
378 totalRange = totalRange.getUnionWith (bounds);
379
380 first = false;
381 }
382 }
383
384 cc->range = totalRange;
385 }
386 }
387}
388
389CollectionClip* FolderTrack::getCollectionClip (int index) const noexcept
390{
391 return collectionClips[index].get();
392}
393
394int FolderTrack::getNumCollectionClips() const noexcept
395{
396 return collectionClips.size();
397}
398
399int FolderTrack::indexOfCollectionClip (CollectionClip* c) const
400{
401 return collectionClips.indexOf (c);
402}
403
404int FolderTrack::getIndexOfNextCollectionClipAt (TimePosition time)
405{
406 return findIndexOfNextItemAt (collectionClips, time);
407}
408
409CollectionClip* FolderTrack::getNextCollectionClipAt (TimePosition time)
410{
411 return collectionClips[getIndexOfNextCollectionClipAt (time)].get();
412}
413
414bool FolderTrack::contains (CollectionClip* clip) const
415{
416 return collectionClips.contains (clip);
417}
418
419int FolderTrack::indexOfTrackItem (TrackItem* ti) const
420{
421 return indexOfCollectionClip (dynamic_cast<CollectionClip*> (ti));
422}
423
424int FolderTrack::getNumTrackItems() const
425{
426 return getNumCollectionClips();
427}
428
429int FolderTrack::getIndexOfNextTrackItemAt (TimePosition time)
430{
431 return getIndexOfNextCollectionClipAt (time);
432}
433
434TrackItem* FolderTrack::getTrackItem (int idx) const
435{
436 return getCollectionClip (idx);
437}
438
439TrackItem* FolderTrack::getNextTrackItemAt (TimePosition time)
440{
441 return getNextCollectionClipAt (time);
442}
443
444VCAPlugin* FolderTrack::getVCAPlugin() { return pluginList.findFirstPluginOfType<VCAPlugin>(); }
445VolumeAndPanPlugin* FolderTrack::getVolumePlugin() { return pluginList.findFirstPluginOfType<VolumeAndPanPlugin>(); }
446
447void FolderTrack::setDirtyClips()
448{
449 dirtyClips = true;
450
451 if (auto p = getParentFolderTrack())
452 p->setDirtyClips();
453}
454
455bool FolderTrack::isFrozen (FreezeType t) const
456{
457 // Submix tracks can't be frozen as they can't contain clips
458 if (isSubmixFolder())
459 return false;
460
461 for (auto at : getAllAudioSubTracks (true))
462 if (at->isFrozen (t))
463 return true;
464
465 return false;
466}
467
468bool FolderTrack::canContainPlugin (Plugin* p) const
469{
470 return dynamic_cast<FreezePointPlugin*> (p) == nullptr;
471}
472
473bool FolderTrack::willAcceptPlugin (Plugin& p)
474{
475 if (! canContainPlugin (&p))
476 return false;
477
478 if (dynamic_cast<TextPlugin*> (&p) != nullptr)
479 return true;
480
481 if (dynamic_cast<VCAPlugin*> (&p) != nullptr)
482 return getVCAPlugin() == nullptr;
483
484 if (! isSubmixFolder())
485 edit.engine.getUIBehaviour().showWarningMessage (TRANS("Converted to submix track"));
486
487 return true;
488}
489
490void FolderTrack::setMute (bool b) { muted = b; }
491void FolderTrack::setSolo (bool b) { soloed = b; }
492void FolderTrack::setSoloIsolate (bool b) { soloIsolated = b; }
493
494void FolderTrack::updatePlugins()
495{
496 const std::scoped_lock sl (pluginMutex);
497 vcaPlugin = pluginList.findFirstPluginOfType<VCAPlugin>();
498 volumePlugin = pluginList.findFirstPluginOfType<VolumeAndPanPlugin>();
499}
500
501void FolderTrack::valueTreeChildAdded (juce::ValueTree& p, juce::ValueTree& c)
502{
503 if (c.hasType (IDs::PLUGIN))
504 pluginUpdater.triggerAsyncUpdate();
505
506 Track::valueTreeChildAdded (p, c);
507}
508
509void FolderTrack::valueTreeChildRemoved (juce::ValueTree& p, juce::ValueTree& c, int index)
510{
511 if (c.hasType (IDs::PLUGIN))
512 pluginUpdater.triggerAsyncUpdate();
513
514 Track::valueTreeChildRemoved (p, c, index);
515}
516
517void FolderTrack::valueTreeChildOrderChanged (juce::ValueTree& p, int oldIndex, int newIndex)
518{
519 if (p.getChild (oldIndex).hasType (IDs::PLUGIN) || p.getChild (newIndex).hasType (IDs::PLUGIN))
520 pluginUpdater.triggerAsyncUpdate();
521
522 Track::valueTreeChildOrderChanged (p, oldIndex, newIndex);
523}
524
525}} // namespace tracktion { inline namespace engine
ElementType getUnchecked(int index) const
void addArray(const Type *elementsToAdd, int numElementsToAdd)
int size() const noexcept
void remove(int indexToRemove)
void add(const ElementType &newElement)
A clip in an edit.
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
Base class for EditItems that live in a Track, e.g.
Represents the destination output device(s) for a track.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
FreezeType
Determines the type of freeze.
virtual void showWarningMessage(const juce::String &message)
Should display a temporary warning message.
The VCA plugin sits on a folder track to control the overall level of all the volume/pan plugins in i...
The built-in Tracktion volume/pan plugin.
#define TRANS(stringLiteral)
#define jassert(expression)
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
int findIndexOfNextItemAt(const ArrayType &items, TimePosition time)
Returns the index of the next item after the given time.
bool containsClip(const Edit &edit, Clip *clip)
Returns true if an Edit contains a given clip.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
Represents a position in real-life time.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.