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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ClipTrack.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
14//==============================================================================
16{
17 CollectionClipList (ClipTrack& t, juce::ValueTree& v) : ct (t), state (v)
18 {
19 state.addListener (this);
20 }
21
22 ~CollectionClipList() override
23 {
24 state.removeListener (this);
25 }
26
27 void valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& id) override
28 {
29 if (id == IDs::groupID)
30 {
31 if (auto c = ct.findClipForID (EditItemID::fromID (v)))
32 {
33 for (auto cc : collectionClips)
34 {
35 if (cc->containsClip (c))
36 {
37 cc->removeClip (c);
38
39 if (cc->getNumClips() == 0)
40 {
41 collectionClips.removeObject (cc);
42 ct.trackItemsDirty = true;
43 break;
44 }
45
46 cc->updateStartAndEnd();
47 ct.trackItemsDirty = true;
48 }
49 }
50
51 if (c->isGrouped())
52 {
53 auto cc = findOrCreateCollectionClip (c->getGroupID());
54 cc->addClip (c);
55 cc->updateStartAndEnd();
56 ct.trackItemsDirty = true;
57
58 c->deselect();
59 }
60 }
61 }
62 else if (id == IDs::start || id == IDs::length)
63 {
64 if (auto c = ct.findClipForID (EditItemID::fromID (v)))
65 {
66 if (c->isGrouped())
67 {
68 if (auto cc = findCollectionClip (c->getGroupID()))
69 {
70 cc->updateStartAndEnd();
71 ct.trackItemsDirty = true;
72 }
73 }
74 }
75 }
76 }
77
78 void valueTreeChildAdded (juce::ValueTree&, juce::ValueTree& child) override
79 {
80 if (Clip::isClipState (child))
81 {
82 if (auto c = ct.findClipForID (EditItemID::fromID (child)))
83 {
84 if (c->isGrouped())
85 {
86 auto cc = findOrCreateCollectionClip (c->getGroupID());
87 cc->addClip (c);
88 cc->updateStartAndEnd();
89 ct.trackItemsDirty = true;
90 }
91 }
92 }
93 }
94
95 void valueTreeChildRemoved (juce::ValueTree&, juce::ValueTree& child, int) override
96 {
97 if (Clip::isClipState (child))
98 {
99 for (auto cc : collectionClips)
100 {
101 if (cc->removeClip (EditItemID::fromID (child)))
102 {
103 if (cc->getNumClips() == 0)
104 {
105 collectionClips.removeObject (cc);
106 ct.trackItemsDirty = true;
107 break;
108 }
109
110 cc->updateStartAndEnd();
111 ct.trackItemsDirty = true;
112 }
113 }
114 }
115 }
116
117 CollectionClip* findOrCreateCollectionClip (EditItemID groupID)
118 {
119 for (auto cc : collectionClips)
120 if (cc->getGroupID() == groupID)
121 return cc;
122
123 auto cc = new CollectionClip (ct);
124 cc->setGroupID (groupID);
125 collectionClips.add (cc);
126 ct.trackItemsDirty = true;
127
128 return cc;
129 }
130
131 CollectionClip* findCollectionClip (EditItemID groupID)
132 {
133 for (auto cc : collectionClips)
134 if (cc->getGroupID() == groupID)
135 return cc;
136
137 return {};
138 }
139
140 void clipCreated (Clip& c)
141 {
142 auto cc = findOrCreateCollectionClip (c.getGroupID());
143 cc->addClip (&c);
144 cc->updateStartAndEnd();
145 ct.trackItemsDirty = true;
146 }
147
148 void valueTreeChildOrderChanged (juce::ValueTree&, int, int) override {}
149 void valueTreeParentChanged (juce::ValueTree&) override {}
150
151 ClipTrack& ct;
152 juce::ValueTree& state;
153
155
157};
158
159//==============================================================================
160ClipTrack::ClipTrack (Edit& ed, const juce::ValueTree& v, bool hasModifierList)
161 : Track (ed, v, hasModifierList)
162{
163 collectionClipList = std::make_unique<CollectionClipList> (*this, state);
164}
165
166ClipTrack::~ClipTrack()
167{
168}
169
175
177{
179
180 for (auto c : getClips())
181 c->flushStateToValueTree();
182}
183
184//==============================================================================
185void ClipTrack::refreshTrackItems() const
186{
187 TRACKTION_ASSERT_MESSAGE_THREAD
188
189 if (trackItemsDirty)
190 {
191 trackItemsDirty = false;
192
193 trackItems.clear();
194 trackItems.ensureStorageAllocated (getClips().size());
195
196 for (auto clip : getClips())
197 trackItems.add (clip);
198
199 for (auto cc : collectionClipList->collectionClips)
200 trackItems.add (cc);
201
202 TrackItem::sortByTime (trackItems);
203 }
204}
205
207{
208 refreshTrackItems();
209 return trackItems.size();
210}
211
213{
214 refreshTrackItems();
215 return trackItems[idx];
216}
217
219{
220 refreshTrackItems();
221 return trackItems.indexOf (ti);
222}
223
225{
226 refreshTrackItems();
227 return findIndexOfNextItemAt (trackItems, time);
228}
229
231{
232 refreshTrackItems();
233 return trackItems[getIndexOfNextTrackItemAt (time)];
234}
235
236//==============================================================================
237CollectionClip* ClipTrack::getCollectionClip (int index) const noexcept
238{
239 return collectionClipList->collectionClips[index].get();
240}
241
242CollectionClip* ClipTrack::getCollectionClip (Clip* clip) const
243{
244 if (clip->isGrouped())
245 for (auto cc : collectionClipList->collectionClips)
246 if (cc->containsClip (clip))
247 return cc;
248
249 return {};
250}
251
252int ClipTrack::getNumCollectionClips() const noexcept
253{
254 return collectionClipList->collectionClips.size();
255}
256
257int ClipTrack::indexOfCollectionClip (CollectionClip* cc) const
258{
259 return collectionClipList->collectionClips.indexOf (cc);
260}
261
262int ClipTrack::getIndexOfNextCollectionClipAt (TimePosition time)
263{
264 return findIndexOfNextItemAt (collectionClipList->collectionClips, time);
265}
266
267CollectionClip* ClipTrack::getNextCollectionClipAt (TimePosition time)
268{
269 return collectionClipList->collectionClips [getIndexOfNextCollectionClipAt (time)].get();
270}
271
272bool ClipTrack::contains (CollectionClip* cc) const
273{
274 return collectionClipList->collectionClips.contains (cc);
275}
276
277//==============================================================================
279{
280 for (auto c : getClips())
281 if (c->itemID == id)
282 return c;
283
284 if (auto at = dynamic_cast<const AudioTrack*> (this))
285 for (auto slot : const_cast<AudioTrack*> (at)->getClipSlotList().getClipSlots())
286 if (auto c = slot->getClip())
287 if (c->itemID == id)
288 return c;
289
290 return {};
291}
292
293TimeDuration ClipTrack::getLength() const
294{
295 return toDuration (getTotalRange().getEnd());
296}
297
298TimeDuration ClipTrack::getLengthIncludingInputTracks() const
299{
300 auto l = getLength();
301
302 for (auto t : getAudioTracks (edit))
303 if (t != this && t->getOutput().getDestinationTrack() == this)
304 l = std::max (l, t->getLengthIncludingInputTracks());
305
306 return l;
307}
308
309TimeRange ClipTrack::getTotalRange() const
310{
312}
313
314bool ClipTrack::addClip (const Clip::Ptr& clip)
315{
317
318 if (clip != nullptr)
319 {
321 {
322 jassert (findClipForID (clip->itemID) == nullptr);
323
324 auto um = clip->getUndoManager();
325 clip->state.getParent().removeChild (clip->state, um);
326 state.addChild (clip->state, -1, um);
327
328 changed();
329 return true;
330 }
331 else
332 {
333 clip->edit.engine.getUIBehaviour().showWarningMessage (TRANS("Can't add any more clips to this track!"));
334 }
335 }
336 return false;
337}
338
339void ClipTrack::addCollectionClip (CollectionClip* cc)
340{
341 CollectionClip::Ptr refHolder (cc);
342
343 // if this collection clip has already been automatically created, remove it
344 for (int i = collectionClipList->collectionClips.size(); --i >= 0;)
345 if (collectionClipList->collectionClips[i]->containsClip (cc->getClip (0).get()))
346 collectionClipList->collectionClips.remove (i);
347
348 collectionClipList->collectionClips.add (cc);
349}
350
351void ClipTrack::removeCollectionClip (CollectionClip* cc)
352{
353 collectionClipList->collectionClips.removeObject (cc);
354}
355
356//==============================================================================
357Clip* ClipTrack::insertClipWithState (juce::ValueTree clipState)
358{
359 return engine::insertClipWithState (*this, clipState);
360}
361
362Clip* ClipTrack::insertClipWithState (const juce::ValueTree& stateToUse, const juce::String& name, TrackItem::Type type,
363 ClipPosition position, bool deleteExistingClips, bool allowSpottingAdjustment)
364{
365 return engine::insertClipWithState (*this, stateToUse, name, type,
366 position, deleteExistingClips ? DeleteExistingClips::yes : DeleteExistingClips::no, allowSpottingAdjustment);
367}
368
369WaveAudioClip::Ptr ClipTrack::insertWaveClip (const juce::String& name, const juce::File& sourceFile,
370 ClipPosition position, bool deleteExistingClips)
371{
372 return engine::insertWaveClip (*this, name, sourceFile, position, deleteExistingClips ? DeleteExistingClips::yes : DeleteExistingClips::no);
373}
374
375WaveAudioClip::Ptr ClipTrack::insertWaveClip (const juce::String& name, ProjectItemID sourceID,
376 ClipPosition position, bool deleteExistingClips)
377{
378 return engine::insertWaveClip (*this, name, sourceID, position, deleteExistingClips ? DeleteExistingClips::yes : DeleteExistingClips::no);
379}
380
381MidiClip::Ptr ClipTrack::insertMIDIClip (const juce::String& name, TimeRange position, SelectionManager* sm)
382{
383 if (auto newClip = engine::insertMIDIClip (*this, name, position))
384 {
385 if (sm != nullptr)
386 {
387 sm->selectOnly (newClip.get());
388 sm->keepSelectedObjectsOnScreen();
389 }
390
391 return newClip;
392 }
393
394 return {};
395}
396
397MidiClip::Ptr ClipTrack::insertMIDIClip (TimeRange position, SelectionManager* sm)
398{
399 return insertMIDIClip (TrackItem::getSuggestedNameForNewItem (TrackItem::Type::midi), position, sm);
400}
401
402EditClip::Ptr ClipTrack::insertEditClip (TimeRange position, ProjectItemID sourceID)
403{
404 return engine::insertEditClip (*this, position, sourceID);
405}
406
407void ClipTrack::deleteRegion (TimeRange range, SelectionManager* sm)
408{
409 auto newClips = engine::deleteRegion (*this, range);
410
411 if (sm != nullptr)
412 for (auto newClip : newClips)
413 sm->addToSelection (newClip);
414}
415
416void ClipTrack::deleteRegionOfClip (Clip::Ptr c, TimeRange range, SelectionManager* sm)
417{
418 jassert (c != nullptr);
419 auto newClips = engine::deleteRegion (*c, range);
420
421 if (sm != nullptr)
422 for (auto newClip : newClips)
423 sm->addToSelection (newClip);
424}
425
426Clip* ClipTrack::insertNewClip (TrackItem::Type type, const juce::String& name, TimeRange pos, SelectionManager* sm)
427{
428 return insertNewClip (type, name, { pos, 0_td }, sm);
429}
430
431Clip* ClipTrack::insertNewClip (TrackItem::Type type, const juce::String& name, ClipPosition position, SelectionManager* sm)
432{
434
435 if (auto newClip = insertClipWithState ({}, name, type, position, false, false))
436 {
437 if (sm != nullptr)
438 {
439 sm->selectOnly (newClip);
440 sm->keepSelectedObjectsOnScreen();
441 }
442
443 return newClip;
444 }
445
446 return {};
447}
448
449Clip* ClipTrack::insertNewClip (TrackItem::Type type, TimeRange pos, SelectionManager* sm)
450{
451 return insertNewClip (type, TrackItem::getSuggestedNameForNewItem (type), pos, sm);
452}
453
454bool ClipTrack::containsAnyMIDIClips() const
455{
456 return engine::containsAnyMIDIClips (*this);
457}
458
463
468
470{
471 return this;
472}
473
475{
476 return edit;
477}
478
480{
481 if (c.isGrouped())
482 collectionClipList->clipCreated (c);
483
484 trackItemsDirty = true;
485}
486
488{
489 changed();
491 trackItemsDirty = true;
492}
493
495{
496 changed();
498 trackItemsDirty = true;
499}
500
502{
503 trackItemsDirty = true;
504}
505
506inline juce::String incrementLastDigit (const juce::String& in)
507{
508 int digitCount = 0;
509
510 for (int i = in.length(); --i >= 0;)
511 {
513 digitCount++;
514 else
515 break;
516 }
517
518 if (digitCount == 0)
519 return in + " 2";
520
521 return in.dropLastCharacters (digitCount)
523}
524
526{
527 return split (clip, time);
528}
529
531{
532 engine::split (*this, time);
533}
534
536{
538 Track::insertSpaceIntoTrack (time, amountOfSpace);
539
540 // make a copied list first, as they'll get moved out-of-order..
541 Clip::Array clipsToDo;
542 const auto& clips = getClips();
543
544 for (int i = clips.size(); --i >= 0;)
545 {
546 auto c = clips.getUnchecked (i);
547
548 if (c->getPosition().time.getCentre() >= time)
549 clipsToDo.add (c);
550 else
551 break;
552 }
553
554 for (int i = clipsToDo.size(); --i >= 0;)
555 if (auto c = clipsToDo.getUnchecked (i).get())
556 c->setStart (c->getPosition().getStart() + amountOfSpace, false, true);
557}
558
559juce::Array<TimePosition> ClipTrack::findAllTimesOfInterest()
560{
562
563 for (auto& o : getClips())
564 cuts.addArray (o->getInterestingTimes());
565
566 cuts.sort();
567 return cuts;
568}
569
571{
572 if (t < TimePosition())
573 return TimePosition();
574
575 for (auto c : findAllTimesOfInterest())
576 if (c > t + TimeDuration::fromSeconds (0.0001))
577 return c;
578
579 return toPosition (getLength());
580}
581
582TimePosition ClipTrack::getPreviousTimeOfInterest (TimePosition t)
583{
584 if (t < TimePosition())
585 return {};
586
587 auto cuts = findAllTimesOfInterest();
588
589 for (int i = cuts.size(); --i >= 0;)
590 if (cuts.getUnchecked (i) < t - TimeDuration::fromSeconds (0.0001))
591 return cuts.getUnchecked (i);
592
593 return {};
594}
595
596bool ClipTrack::containsPlugin (const Plugin* plugin) const
597{
598 if (pluginList.contains (plugin))
599 return true;
600
601 for (auto c : getClips())
602 if (auto plugins = c->getPluginList())
603 if (plugins->contains (plugin))
604 return true;
605
606 return false;
607}
608
610{
611 auto destArray = Track::getAllPlugins();
612
613 for (auto c : getClips())
614 {
615 destArray.addArray (c->getAllPlugins());
616
617 if (auto containerClip = dynamic_cast<ContainerClip*> (c))
618 for (auto childClip : containerClip->getClips())
619 destArray.addArray (childClip->getAllPlugins());
620 }
621
622 return destArray;
623}
624
626{
627 pluginList.sendMirrorUpdateToAllPlugins (p);
628
629 for (auto c : getClips())
630 c->sendMirrorUpdateToAllPlugins (p);
631}
632
633bool ClipTrack::areAnyClipsUsingFile (const AudioFile& af)
634{
635 for (auto c : getClips())
636 if (auto acb = dynamic_cast<AudioClipBase*> (c))
637 if (acb->isUsingFile (af))
638 return true;
639
640 return false;
641}
642
643}} // namespace tracktion { inline namespace engine
ElementType getUnchecked(int index) const
int size() const noexcept
static bool isDigit(char character) noexcept
int size() const noexcept
ObjectClassPtr getUnchecked(int index) const noexcept
ObjectClass * add(ObjectClass *newObject)
int length() const noexcept
String dropLastCharacters(int numberToDrop) const
int getTrailingIntValue() const noexcept
void addListener(Listener *listener)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
void removeListener(Listener *listener)
Base class for Clips that produce some kind of audio e.g.
const juce::Array< Clip * > & getClips() const
Returns the clips this owner contains.
void initialiseClipOwner(Edit &, juce::ValueTree clipParentState)
Must be called once from the subclass constructor to init the clip owner.
void flushStateToValueTree() override
Flushes all plugin states on the track to the state object.
Clip * findClipForID(EditItemID) const override
Returns a clip one with a matching ID can be found on this Track.
TimePosition getNextTimeOfInterest(TimePosition afterThisTime)
finds the next cut point
TrackItem * getTrackItem(int idx) const override
Should return the TrackItem at the given index.
EditItemID getClipOwnerID() override
Must return the ID of this ClipOwner.
void clipAddedOrRemoved() override
Called when a clip is added or removed.
Edit & getClipOwnerEdit() override
Must return the Edit this ClipOwner belongs to.
void sendMirrorUpdateToAllPlugins(Plugin &) const override
Sends a message to all plugins that the given plugin has changed.
void clipPositionChanged() override
Called when a clip start or end position has changed.
void insertSpaceIntoTrack(TimePosition, TimeDuration) override
inserts space and moves everything up
void initialise() override
Initialises the Track.
int getNumTrackItems() const override
Should return the number of TrackItem[s] on this Track.
Plugin::Array getAllPlugins() const override
Returns all pugins on this Track.
Clip * splitClip(Clip &, TimePosition)
breaks a clip into 2 bits
juce::ValueTree & getClipOwnerState() override
Must return the state of this ClipOwner.
TrackItem * getNextTrackItemAt(TimePosition) override
Should return the TrackItem after this time.
void splitAt(TimePosition)
split all clips at this time
void clipOrderChanged() override
Called when clips have moved times so that their order has changed.
bool containsPlugin(const Plugin *) const override
Tests whether this Track or a clip on it contains the given plugin.
int indexOfTrackItem(TrackItem *) const override
Should return the index of the given TrackItem.
void clipCreated(Clip &) override
Called when a clip is created which could be during Edit load.
int getIndexOfNextTrackItemAt(TimePosition) override
Should return the index of the TrackItem after this time.
Selectable * getClipOwnerSelectable() override
Must return the selectable if this ClipOwner is one.
A clip in an edit.
static bool isClipState(const juce::ValueTree &)
Checks whether a ValueTree is some kind of clip state.
A clip that can contain multiple other clips and mix their output together.
const EditItemID itemID
Every EditItem has an ID which is unique within the edit.
The Tracktion Edit class!
Engine & engine
A reference to the Engine.
virtual EditLimits getEditLimits()
Should return the maximum number of elements that can be added to an Edit.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
Base class for things that can be selected, and whose properties can appear in the properties panel.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
Base class for EditItems that live in a Track, e.g.
Type
Defines the types of item that can live on Track[s].
static void sortByTime(ArrayType &items)
Helper function to sort an array of TrackItem[s] by their start time.
static juce::String getSuggestedNameForNewItem(Type)
Returns a text string for a new clip of the given type.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
virtual void setFrozen(bool, FreezeType)
Attempts to freeze or unfreeze the track using a given FreezeType.
PluginList pluginList
The Track's PluginList.
@ groupFreeze
Freezes multiple tracks together in to a single file.
virtual void flushStateToValueTree()
Flushes all plugin states on the track to the state object.
juce::ValueTree state
The state of this Track.
virtual Plugin::Array getAllPlugins() const
Returns all pugins on this Track.
virtual void initialise()
Initialises the Track.
virtual void insertSpaceIntoTrack(TimePosition, TimeDuration)
Should insert empty space in to the track, shuffling down any items after the time.
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
bool containsAnyMIDIClips(const ClipOwner &co)
Returns true if the clip owner contains any MIDI clips.
int findIndexOfNextItemAt(const ArrayType &items, TimePosition time)
Returns the index of the next item after the given time.
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
juce::Array< Clip * > deleteRegion(ClipOwner &parent, TimeRange range)
Removes a region of a ClipOwner and returns any newly created clips.
MidiClip::Ptr insertMIDIClip(ClipOwner &parent, const juce::String &name, TimeRange position)
Inserts a new MidiClip into the ClipOwner's clip list.
juce::ReferenceCountedObjectPtr< WaveAudioClip > insertWaveClip(ClipOwner &parent, const juce::String &name, const juce::File &sourceFile, ClipPosition position, DeleteExistingClips deleteExistingClips)
Inserts a new WaveAudioClip into the ClipOwner's clip list.
TimeRange findUnionOfEditTimeRanges(const ArrayType &items)
Returns the the time range that covers all the given TrackItems.
EditClip::Ptr insertEditClip(ClipOwner &parent, TimeRange position, ProjectItemID sourceID)
Inserts a new EditClip into the ClipOwner's clip list.
DeleteExistingClips
Determines behaviour for overwriting clips.
int maxClipsInTrack
The maximum number of Clip[s] a Track can contain.
bool containsClip(const Edit &edit, Clip *clip)
Returns true if an Edit contains a given clip.
Clip * insertClipWithState(ClipOwner &clipOwner, juce::ValueTree clipState)
Inserts a clip with the given state in to the ClipOwner's clip list.
juce::Array< Clip * > split(ClipOwner &parent, TimePosition time)
Splits the given clp owner at the time and returns any newly created clips.
T size(T... args)
Represents a duration in real-life time.
Represents a position in real-life time.
ID for objects of type EditElement - e.g.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.