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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ClipOwner.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15 private juce::AsyncUpdater
16{
17 ClipList (ClipOwner& co, Edit& e, const juce::ValueTree& parentTree)
18 : ValueTreeObjectList<Clip> (parentTree),
19 edit (e),
20 clipOwner (co)
21 {
22 rebuildObjects();
23
24 editLoadedCallback = std::make_unique<Edit::LoadFinishedCallback<ClipList>> (*this, edit);
25 }
26
27 ~ClipList() override
28 {
29 for (auto c : objects)
30 {
31 c->flushStateToValueTree();
32 c->setParent (nullptr);
33 }
34
35 freeObjects();
36 }
37
38 bool isSuitableType (const juce::ValueTree& v) const override
39 {
40 return Clip::isClipState (v);
41 }
42
43 Clip* createNewObject (const juce::ValueTree& v) override
44 {
45 if (auto newClip = Clip::createClipForState (v, clipOwner))
46 {
47 clipOwner.clipCreated (*newClip);
48 newClip->incReferenceCount();
49
50 return newClip.get();
51 }
52
54 return {};
55 }
56
57 void deleteObject (Clip* c) override
58 {
59 jassert (c != nullptr);
60 if (c == nullptr)
61 return;
62
63 c->decReferenceCount();
64 }
65
66 void newObjectAdded (Clip* c) override
67 {
68 objectAddedOrRemoved (c);
69
70 if (c && ! edit.getUndoManager().isPerformingUndoRedo())
72 }
73
74 void objectRemoved (Clip* c) override { objectAddedOrRemoved (c); }
75 void objectOrderChanged() override { objectAddedOrRemoved (nullptr); }
76
77 void objectAddedOrRemoved (Clip* c)
78 {
79 if (c == nullptr || c->type != TrackItem::Type::unknown)
80 {
81 if (c == nullptr)
82 clipOwner.clipOrderChanged();
83 else
84 clipOwner.clipAddedOrRemoved();
85
86 if (! edit.isLoading() && ! edit.getUndoManager().isPerformingUndoRedo())
88 }
89 }
90
91 Edit& edit;
92 ClipOwner& clipOwner;
94
95 void valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& id) override
96 {
97 if (Clip::isClipState (v))
98 {
99 if (id == IDs::start || id == IDs::length)
100 {
103
104 clipOwner.clipPositionChanged();
105 }
106 }
107 }
108
109 void handleAsyncUpdate() override
110 {
111 sortClips (parent, &edit.getUndoManager());
112 }
113
114 static void sortClips (juce::ValueTree& state, juce::UndoManager* um)
115 {
116 struct Sorter
117 {
118 static int getPriority (const juce::Identifier& i)
119 {
120 if (i == IDs::AUTOMATIONTRACK) return 0;
121 if (Clip::isClipState (i)) return 1;
122 if (i == IDs::PLUGIN) return 2;
123 if (i == IDs::OUTPUTDEVICES) return 3;
124 if (i == IDs::LFOS) return 4;
125
126 return -1;
127 }
128
129 int compareElements (const juce::ValueTree& first, const juce::ValueTree& second) const noexcept
130 {
131 auto priority = getPriority (first.getType()) - getPriority (second.getType());
132
133 if (priority != 0)
134 return priority;
135
136 if (Clip::isClipState (first) && Clip::isClipState (second))
137 {
138 double t1 = first[IDs::start];
139 double t2 = second[IDs::start];
140 return t1 < t2 ? -1 : (t2 < t1 ? 1 : 0);
141 }
142
143 return 0;
144 }
145 };
146
147 Sorter clipSorter;
148 state.sort (clipSorter, um, true);
149 }
150
151 void editFinishedLoading()
152 {
153 editLoadedCallback = nullptr;
154
155 for (auto c : objects)
156 if (auto acb = dynamic_cast<AudioClipBase*> (c))
157 acb->updateAutoCrossfadesAsync (false);
158
159 clipOwner.clipPositionChanged();
160 }
161
163};
164
165
166//==============================================================================
167//==============================================================================
170
172{
173 if (clipList)
174 return;
175
177 ClipList::sortClips (clipParentState, &edit.getUndoManager());
178
179 clipList = std::make_unique<ClipList> (*this, edit, clipParentState);
180}
181
183{
184 assert (clipList && "You must call initialiseClipOwner before any other methods");
185 return clipList->objects;
186}
187
188//==============================================================================
189//==============================================================================
191{
192 for (auto c : co.getClips())
193 if (c->state == v)
194 return c;
195
196 return {};
197}
198
200{
201 for (auto c : co.getClips())
202 if (c->itemID == id)
203 return c;
204
205 return {};
206}
207
208//==============================================================================
209namespace clip_owner
210{
211 inline void updateClipState (juce::ValueTree& state, const juce::String& name,
212 EditItemID itemID, ClipPosition position)
213 {
214 addValueTreeProperties (state,
215 IDs::name, name,
216 IDs::start, position.getStart().inSeconds(),
217 IDs::length, position.getLength().inSeconds(),
218 IDs::offset, position.getOffset().inSeconds());
219
220 itemID.writeID (state, nullptr);
221 }
222
223 inline juce::ValueTree createNewClipState (const juce::String& name, TrackItem::Type type,
224 EditItemID itemID, ClipPosition position)
225 {
227 updateClipState (state, name, itemID, position);
228 return state;
229 }
230
231 inline juce::String incrementLastDigit (const juce::String& in)
232 {
233 int digitCount = 0;
234
235 for (int i = in.length(); --i >= 0;)
236 {
238 digitCount++;
239 else
240 break;
241 }
242
243 if (digitCount == 0)
244 return in + " 2";
245
246 return in.dropLastCharacters (digitCount)
248 }
249}
250
252{
254 jassert (clipState.isValid());
255 jassert (! clipState.getParent().isValid());
256
257 auto& edit = clipOwner.getClipOwnerEdit();
258 auto& engineBehaviour = edit.engine.getEngineBehaviour();
259
260 if (clipState.hasType (IDs::MIDICLIP))
261 {
262 setPropertyIfMissing (clipState, IDs::sync, engineBehaviour.areMidiClipsRemappedWhenTempoChanges()
264 }
265 else if (clipState.hasType (IDs::AUDIOCLIP) || clipState.hasType (IDs::EDITCLIP))
266 {
267 if (! clipState.getChildWithName (IDs::LOOPINFO).isValid())
268 {
269 auto sourceFile = SourceFileReference::findFileFromString (edit, clipState[IDs::source]);
270
271 if (sourceFile.exists())
272 {
273 auto loopInfo = AudioFile (edit.engine, sourceFile).getInfo().loopInfo;
274
275 if (loopInfo.getRootNote() != -1)
276 clipState.setProperty (IDs::autoPitch, true, nullptr);
277
278 if (loopInfo.isLoopable())
279 {
280 clipState.setProperty (IDs::autoTempo, true, nullptr);
281 clipState.setProperty (IDs::stretchMode, true, nullptr);
282 clipState.setProperty (IDs::elastiqueMode, (int) TimeStretcher::elastiquePro, nullptr);
283
284 auto& ts = edit.tempoSequence;
285
286 auto startBeat = ts.toBeats (TimePosition::fromSeconds (static_cast<double> (clipState[IDs::start])));
287 auto endBeat = startBeat + BeatDuration::fromBeats (loopInfo.getNumBeats());
288 auto newLength = ts.toTime (endBeat) - ts.toTime (startBeat);
289
290 clipState.setProperty (IDs::length, newLength.inSeconds(), nullptr);
291 }
292
293 auto loopSate = loopInfo.state;
294
295 if (loopSate.getNumProperties() > 0 || loopSate.getNumChildren() > 0)
296 clipState.addChild (loopSate.createCopy(), -1, nullptr);
297 }
298 }
299
300 if (! clipState.hasProperty (IDs::sync))
301 {
302 if (clipState.getProperty (IDs::autoTempo))
303 clipState.setProperty (IDs::sync, (int) engineBehaviour.areAutoTempoClipsRemappedWhenTempoChanges()
305 else
306 clipState.setProperty (IDs::sync, (int) engineBehaviour.areAudioClipsRemappedWhenTempoChanges()
308 }
309
310 if (! clipState.hasProperty (IDs::autoCrossfade))
311 if (edit.engine.getPropertyStorage().getProperty (SettingID::xFade, 0))
312 clipState.setProperty (IDs::autoCrossfade, true, nullptr);
313 }
314
315 if (clipOwner.getClips().size() < engineBehaviour.getEditLimits().maxClipsInTrack)
316 {
317 if (auto clipSlot = dynamic_cast<ClipSlot*> (clipOwner.getClipOwnerSelectable()))
318 if (auto existingClip = clipSlot->getClip())
319 existingClip->removeFromParent();
320
321 clipOwner.getClipOwnerState().addChild (clipState, -1, &edit.getUndoManager());
322
323 if (auto newClip = findClipForState (clipOwner, clipState))
324 {
325 if (auto at = dynamic_cast<AudioTrack*> (clipOwner.getClipOwnerSelectable()))
326 {
327 if (newClip->getColour() == newClip->getDefaultColour())
328 {
329 auto col = at->getColour();
330
331 float hue = col.isTransparent() ? ((at->getIndexInEditTrackList() % 18) * 1.0f / 18.0f) : col.getHue();
332 newClip->setColour (newClip->getDefaultColour().withHue (hue));
333 }
334
335 if (auto acb = dynamic_cast<AudioClipBase*> (newClip))
336 {
337 if (engineBehaviour.autoAddClipEdgeFades())
338 if (! (clipState.hasProperty (IDs::fadeIn) && clipState.hasProperty (IDs::fadeOut)))
339 acb->applyEdgeFades();
340
341 const auto defaults = engineBehaviour.getClipDefaults();
342
343 if (! clipState.hasProperty (IDs::proxyAllowed))
344 acb->setUsesProxy (defaults.useProxyFile);
345
346 if (! clipState.hasProperty (IDs::resamplingQuality))
347 acb->setResamplingQuality (defaults.resamplingQuality);
348 }
349 }
350 else if (auto cs = dynamic_cast<ClipSlot*> (clipOwner.getClipOwnerSelectable()))
351 {
352 if (newClip->getColour() == newClip->getDefaultColour())
353 {
354 auto col = cs->track.getColour();
355 float hue = col.isTransparent() ? ((cs->track.getIndexInEditTrackList() % 18) * 1.0f / 18.0f) : col.getHue();
356 newClip->setColour (newClip->getDefaultColour().withHue (hue));
357 }
358
359 if (auto acb = dynamic_cast<AudioClipBase*> (newClip))
360 {
361 if (acb->effectsEnabled())
362 acb->enableEffects (false, false);
363
364 acb->setUsesProxy (false);
365 acb->setAutoTempo (true);
366 acb->setStart (0_tp, false, true);
367
368 if (! acb->isLooping())
369 acb->setLoopRangeBeats ({ 0_bp, acb->getLengthInBeats() });
370 }
371 else if (auto mc = dynamic_cast<MidiClip*> (newClip))
372 {
373 mc->setUsesProxy (false);
374 mc->setStart (0_tp, false, true);
375
376 if (! mc->isLooping ())
377 mc->setLoopRangeBeats (mc->getEditBeatRange());
378 }
379 else if (auto sc = dynamic_cast<StepClip*> (newClip))
380 {
381 sc->setStart (0_tp, false, true);
382
383 if (! sc->isLooping())
384 sc->setLoopRangeBeats ({ 0_bp, sc->getLengthInBeats() });
385 }
386 }
387
388 return newClip;
389 }
390 }
391 else
392 {
393 edit.engine.getUIBehaviour().showWarningMessage (TRANS("Can't add any more clips to this track!"));
394 }
395
397 return {};
398}
399
401 const juce::ValueTree& stateToUse, const juce::String& name, TrackItem::Type type,
402 ClipPosition position, DeleteExistingClips deleteExistingClips, bool allowSpottingAdjustment)
403{
405 auto& edit = parent.getClipOwnerEdit();
406
407 if (position.getStart() >= Edit::getMaximumEditEnd())
408 return {};
409
410 if (position.time.getEnd() > Edit::getMaximumEditEnd())
411 position.time.getEnd() = Edit::getMaximumEditEnd();
412
413 if (auto track = dynamic_cast<Track*> (&parent))
414 track->setFrozen (false, Track::groupFreeze);
415
416 if (deleteExistingClips == DeleteExistingClips::yes)
417 deleteRegion (parent, position.time);
418
419 auto newClipID = edit.createNewItemID();
420
421 juce::ValueTree newState;
422
423 if (stateToUse.isValid())
424 {
425 jassert (stateToUse.hasType (TrackItem::clipTypeToXMLType (type)));
426 newState = stateToUse;
427 clip_owner::updateClipState (newState, name, newClipID, position);
428 }
429 else
430 {
431 newState = clip_owner::createNewClipState (name, type, newClipID, position);
432 }
433
434 if (auto newClip = insertClipWithState (parent, newState))
435 {
436 if (allowSpottingAdjustment)
437 newClip->setStart (std::max (0_tp, newClip->getPosition().getStart() - toDuration (newClip->getSpottingPoint())), false, false);
438
439 return newClip;
440 }
441
442 return {};
443}
444
445Clip* insertNewClip (ClipOwner& parent, TrackItem::Type type, const juce::String& name, EditTimeRange pos)
446{
447 return insertNewClip (parent, type, name, { toTime (pos, parent.getClipOwnerEdit().tempoSequence), 0_td });
448}
449
450Clip* insertNewClip (ClipOwner& parent, TrackItem::Type type, EditTimeRange pos)
451{
452 return insertNewClip (parent, type, TrackItem::getSuggestedNameForNewItem (type), pos);
453}
454
455Clip* insertNewClip (ClipOwner& parent, TrackItem::Type type, const juce::String& name, ClipPosition position)
456{
458
459 if (auto newClip = insertClipWithState (parent, {}, name, type, position, DeleteExistingClips::no, false))
460 return newClip;
461
462 return {};
463}
464
466 ClipPosition position, DeleteExistingClips deleteExistingClips)
467{
468 auto& edit = parent.getClipOwnerEdit();
469
470 if (auto proj = getProjectForEdit (edit))
471 {
472 if (auto source = proj->createNewItem (sourceFile, ProjectItem::waveItemType(),
473 name, {}, ProjectItem::Category::imported, true))
474 return insertWaveClip (parent, name, source->getID(), position, deleteExistingClips);
475
477 return {};
478 }
479
480 // Insert with a relative path if possible, otherwise an absolute
481 {
482 auto newState = clip_owner::createNewClipState (name, TrackItem::Type::wave, edit.createNewItemID(), position);
483 const bool useRelativePath = edit.filePathResolver && edit.editFileRetriever && edit.editFileRetriever().existsAsFile();
484 newState.setProperty (IDs::source, SourceFileReference::findPathFromFile (edit, sourceFile, useRelativePath), nullptr);
485
486 if (auto c = insertClipWithState (parent, newState, name, TrackItem::Type::wave, position, deleteExistingClips, false))
487 {
488 if (auto wc = dynamic_cast<WaveAudioClip*> (c))
489 return *wc;
490
492 }
493 }
494
496 return {};
497}
498
500 ClipPosition position, DeleteExistingClips deleteExistingClips)
501{
503 auto& edit = parent.getClipOwnerEdit();
504
505 auto newState = clip_owner::createNewClipState (name, TrackItem::Type::wave, edit.createNewItemID(), position);
506 newState.setProperty (IDs::source, sourceID.toString(), nullptr);
507
508 if (auto c = insertClipWithState (parent, newState, name, TrackItem::Type::wave, position, deleteExistingClips, false))
509 {
510 if (auto wc = dynamic_cast<WaveAudioClip*> (c))
511 return *wc;
512
514 }
515
516 return {};
517}
518
519MidiClip::Ptr insertMIDIClip (ClipOwner& parent, const juce::String& name, TimeRange position)
520{
521 if (auto t = dynamic_cast<AudioTrack*> (&parent))
522 if (! t->containsAnyMIDIClips())
523 t->setVerticalScaleToDefault();
524
525 if (auto c = insertNewClip (parent, TrackItem::Type::midi, name, position))
526 {
527 if (auto mc = dynamic_cast<MidiClip*> (c))
528 return *mc;
529
531 }
532
533 return {};
534}
535
536MidiClip::Ptr insertMIDIClip (ClipOwner& parent, TimeRange position)
537{
538 return insertMIDIClip (parent, TrackItem::getSuggestedNameForNewItem (TrackItem::Type::midi), position);
539}
540
541EditClip::Ptr insertEditClip (ClipOwner& parent, TimeRange position, ProjectItemID sourceID)
542{
544
545 auto name = TrackItem::getSuggestedNameForNewItem (TrackItem::Type::edit);
546 auto newState = clip_owner::createNewClipState (name, TrackItem::Type::edit, parent.getClipOwnerEdit().createNewItemID(), { position, TimeDuration() });
547 newState.setProperty (IDs::source, sourceID.toString(), nullptr);
548
549 if (auto c = insertClipWithState (parent, newState, name, TrackItem::Type::edit, { position, 0_td }, DeleteExistingClips::no, false))
550 {
551 if (auto ec = dynamic_cast<EditClip*> (c))
552 return *ec;
553
555 }
556
557 return {};
558}
559
560//==============================================================================
561juce::Array<Clip*> deleteRegion (ClipOwner& parent, TimeRange range)
562{
563 juce::Array<Clip*> newClips;
564
566 if (auto track = dynamic_cast<Track*> (&parent))
567 {
568 track->setFrozen (false, Track::groupFreeze);
569 track->setFrozen (false, Track::individualFreeze);
570 }
571
572 // make a copied list first, as they'll get moved out-of-order..
573 Clip::Array clipsToDo;
574
575 for (auto c : parent.getClips())
576 if (c->getPosition().time.overlaps (range))
577 clipsToDo.add (c);
578
579 for (int i = clipsToDo.size(); --i >= 0;)
580 newClips.addArray (deleteRegion (*clipsToDo.getUnchecked (i), range));
581
582 return newClips;
583}
584
586{
587 juce::Array<Clip*> newClips;
588
590 if (auto track = dynamic_cast<Track*> (c.getParent()))
591 {
592 track->setFrozen (false, Track::groupFreeze);
593 track->setFrozen (false, Track::individualFreeze);
594 }
595
596 auto pos = c.getPosition();
597
598 if (range.contains (pos.time))
599 {
600 c.removeFromParent();
601 }
602 else if (pos.getStart() < range.getStart() && pos.getEnd() > range.getEnd())
603 {
604 if (auto newClip = split (c, range.getStart()))
605 {
606 newClip->setStart (range.getEnd(), true, false);
607 c.setEnd (range.getStart(), true);
608 c.deselect();
609
610 newClips.add (newClip);
611 }
612 }
613 else
614 {
615 c.trimAwayOverlap (range);
616 }
617
618 return newClips;
619}
620
622{
624 juce::Array<Clip*> newClips;
625
626 // Make a copied list first, as they'll get moved out-of-order..
627 Clip::Array clipsToDo;
628
629 for (auto c : parent.getClips())
630 if (c->getPosition().time.contains (time))
631 clipsToDo.add (c);
632
633 for (auto c : clipsToDo)
634 newClips.add (split (*c, time));
635
636 return newClips;
637}
638
639Clip* split (Clip& clip, const TimePosition time)
640{
642 auto parent = clip.getParent();
643
644 if (parent == nullptr)
645 return {};
646
647 auto& edit = clip.edit;
648
649 if (auto track = dynamic_cast<Track*> (parent))
650 track->setFrozen (false, Track::groupFreeze);
651
652 if (clip.getPosition().time.reduced (0.001s).contains (time)
653 && ! clip.isGrouped())
654 {
655 auto newClipState = clip.state.createCopy();
656 edit.createNewItemID().writeID (newClipState, nullptr);
657
658 if (auto newClip = insertClipWithState (*parent, newClipState))
659 {
660 // special case for waveaudio clips that may have fade in/out
661 if (auto acb = dynamic_cast<AudioClipBase*> (newClip))
662 acb->setFadeIn ({});
663
664 if (auto acb = dynamic_cast<AudioClipBase*> (&clip))
665 acb->setFadeOut ({});
666
667 // need to do this after setting the fades, so the fades don't
668 // get mucked around with..
669 const bool isChord = dynamic_cast<ChordClip*> (&clip) != nullptr;
670 newClip->setStart (time, ! isChord, false);
671 clip.setEnd (time, true);
672
673 // special case for marker clips, set the marker number
674 if (auto mc1 = dynamic_cast<MarkerClip*> (&clip))
675 {
676 if (auto mc2 = dynamic_cast<MarkerClip*> (newClip))
677 {
678 auto id = edit.getMarkerManager().getNextUniqueID (mc1->getMarkerID());
679 mc2->setMarkerID (id);
680
681 if (mc1->getName() == (TRANS("Marker") + " " + juce::String (mc1->getMarkerID())))
682 mc2->setName (TRANS("Marker") + " " + juce::String (id));
683 else
684 mc2->setName (clip_owner::incrementLastDigit (mc1->getName()));
685 }
686 }
687
688 // fiddle with offsets for looped clips
689 if (newClip->getLoopLengthBeats() > BeatDuration())
690 {
691 auto extra = juce::roundToInt (std::floor ((newClip->getOffsetInBeats()
692 / newClip->getLoopLengthBeats()) + 0.00001));
693
694 if (extra != 0)
695 {
696 auto newOffsetBeats = newClip->getOffsetInBeats() - (newClip->getLoopLengthBeats() * extra);
697 auto offset = TimeDuration::fromSeconds (newOffsetBeats.inBeats() / edit.tempoSequence.getBeatsPerSecondAt (newClip->getPosition().getStart()));
698
699 newClip->setOffset (offset);
700 }
701 }
702
703 return newClip;
704 }
705 }
706
707 return {};
708}
709
711{
712 for (auto& c : co.getClips())
713 if (c->isMidi())
714 return true;
715
716 return false;
717}
718
719
720//==============================================================================
721//==============================================================================
722bool isMasterTrack (const Track& t) { return dynamic_cast<const MasterTrack*> (&t) != nullptr; }
723bool isTempoTrack (const Track& t) { return dynamic_cast<const TempoTrack*> (&t) != nullptr; }
724bool isAutomationTrack (const Track& t) { return dynamic_cast<const AutomationTrack*> (&t) != nullptr; }
725bool isAudioTrack (const Track& t) { return dynamic_cast<const AudioTrack*> (&t) != nullptr; }
726bool isFolderTrack (const Track& t) { return dynamic_cast<const FolderTrack*> (&t) != nullptr; }
727bool isMarkerTrack (const Track& t) { return dynamic_cast<const MarkerTrack*> (&t) != nullptr; }
728bool isChordTrack (const Track& t) { return dynamic_cast<const ChordTrack*> (&t) != nullptr; }
729bool isArrangerTrack (const Track& t) { return dynamic_cast<const ArrangerTrack*> (&t) != nullptr; }
730
731bool isAudioTrack (const ClipOwner& t) { return dynamic_cast<const AudioTrack*> (&t) != nullptr; }
732bool isFolderTrack (const ClipOwner& t) { return dynamic_cast<const FolderTrack*> (&t) != nullptr; }
733bool isMarkerTrack (const ClipOwner& t) { return dynamic_cast<const MarkerTrack*> (&t) != nullptr; }
734bool isChordTrack (const ClipOwner& t) { return dynamic_cast<const ChordTrack*> (&t) != nullptr; }
735bool isArrangerTrack (const ClipOwner& t) { return dynamic_cast<const ArrangerTrack*> (&t) != nullptr; }
736
737//==============================================================================
738bool canContainMIDI (const ClipOwner& co)
739{
740 if (auto track = dynamic_cast<const Track*> (&co))
741 return isAudioTrack (*track);
742
743 return false;
744}
745
747{
748 if (dynamic_cast<const ContainerClip*> (&co) != nullptr)
749 return true;
750
751 if (auto track = dynamic_cast<const Track*> (&co))
752 return isAudioTrack (*track);
753
754 return false;
755}
756
757bool isOnTop (const Track& track)
758{
759 return isMarkerTrack (track) || isTempoTrack (track) || isChordTrack (track) || isArrangerTrack (track) || isMasterTrack (track)
760 || (isAutomationTrack (track) && track.getParentTrack() != nullptr && track.getParentTrack()->isMasterTrack());
761}
762
763}} // namespace tracktion { inline namespace engine
assert
void addArray(const Type *elementsToAdd, int numElementsToAdd)
int size() const noexcept
void add(const ElementType &newElement)
static bool isDigit(char character) noexcept
int length() const noexcept
String dropLastCharacters(int numberToDrop) const
int getTrailingIntValue() const noexcept
bool isPerformingUndoRedo() const
bool hasType(const Identifier &typeName) const noexcept
bool isValid() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getParent() const noexcept
const var & getProperty(const Identifier &name) const noexcept
Identifier getType() const noexcept
ValueTree getChildWithName(const Identifier &type) 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.
Base class for items that can contain clips.
ClipOwner()
Constructs an empty ClipOwner.
const juce::Array< Clip * > & getClips() const
Returns the clips this owner contains.
virtual void clipOrderChanged()=0
Called when clips have moved times so that their order has changed.
void initialiseClipOwner(Edit &, juce::ValueTree clipParentState)
Must be called once from the subclass constructor to init the clip owner.
virtual Selectable * getClipOwnerSelectable()=0
Must return the selectable if this ClipOwner is one.
virtual Edit & getClipOwnerEdit()=0
Must return the Edit this ClipOwner belongs to.
virtual void clipCreated(Clip &)=0
Called when a clip is created which could be during Edit load.
virtual void clipAddedOrRemoved()=0
Called when a clip is added or removed.
virtual void clipPositionChanged()=0
Called when a clip start or end position has changed.
virtual juce::ValueTree & getClipOwnerState()=0
Must return the state of this ClipOwner.
Represents a slot on a track that a Clip can live in to be played as a launched clip.
A clip in an edit.
static bool isClipState(const juce::ValueTree &)
Checks whether a ValueTree is some kind of clip state.
void setStart(TimePosition newStart, bool preserveSync, bool keepLength)
Sets the start time of the clip.
static Ptr createClipForState(const juce::ValueTree &, ClipOwner &targetParent)
Creates a clip for a given ValueTree representation.
@ syncBarsBeats
Sync to beats.
@ syncAbsolute
Sync to abslute time.
A clip that can contain multiple other clips and mix their output together.
This is the main source of an Edit clip and is responsible for managing its properties.
The Tracktion Edit class!
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for this Edit.
EditItemID createNewItemID() const
Returns a new EditItemID to use for a new EditItem.
Engine & engine
A reference to the Engine.
virtual void newClipAdded(Clip &, bool fromRecording)
Returns the defaults to be applied to new clips.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
A track to represent the master plugins.
An ID representing one of the items in a Project.
A track to represent the "global" items such as tempo, key changes etc.
@ elastiquePro
Elastique Pro good all round (.
Type
Defines the types of item that can live on Track[s].
@ unknown
A placeholder for unknown items.
static juce::Identifier clipTypeToXMLType(Type)
Returns an Identifier version of a TrackItem::Type.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
bool isRecordingStopping() const
Returns true if a recording is currently being stopped.
An audio clip that uses an audio file as its source.
T floor(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
T max(T... args)
int roundToInt(const FloatType value) noexcept
bool containsAnyMIDIClips(const ClipOwner &co)
Returns true if the clip owner contains any MIDI clips.
bool canContainMIDI(const ClipOwner &co)
Returns true if this Track can contain MidiClip[s].
Clip * findClipForState(ClipOwner &co, const juce::ValueTree &v)
Returns a clip with the given state if the ClipOwner contains it.
bool canContainAudio(const ClipOwner &co)
Returns true if this Track can contain WaveAudioClip[s].
Clip * findClipForID(ClipOwner &co, EditItemID id)
Returns a clip with the given ID if the ClipOwner contains it.
bool isOnTop(const Track &track)
Returns true if this a global Track and should be on top of others.
bool isAutomationTrack(const Track &t)
Returns true if this is an AutomationTrack.
bool isFolderTrack(const Track &t)
Returns true if this is a FolderTrack.
juce::Array< Clip * > deleteRegion(ClipOwner &parent, TimeRange range)
Removes a region of a ClipOwner and returns any newly created clips.
bool isAudioTrack(const Track &t)
Returns true if this is an AudioTrack.
bool isArrangerTrack(const Track &t)
Returns true if this is an ArrangerTrack.
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.
Project::Ptr getProjectForEdit(const Edit &e)
Tries to find the project that contains this edit (but may return nullptr!)
bool isMarkerTrack(const Track &t)
Returns true if this is a MarkerTrack.
EditClip::Ptr insertEditClip(ClipOwner &parent, TimeRange position, ProjectItemID sourceID)
Inserts a new EditClip into the ClipOwner's clip list.
bool isChordTrack(const Track &t)
Returns true if this is a ChordTrack.
Clip * insertNewClip(ClipOwner &parent, TrackItem::Type type, const juce::String &name, EditTimeRange pos)
Inserts a new clip with the given type and name.
DeleteExistingClips
Determines behaviour for overwriting clips.
Clip * insertClipWithState(ClipOwner &clipOwner, juce::ValueTree clipState)
Inserts a clip with the given state in to the ClipOwner's clip list.
bool isMasterTrack(const Track &t)
Returns true if this is a MasterTrack.
juce::Array< Clip * > split(ClipOwner &parent, TimePosition time)
Splits the given clp owner at the time and returns any newly created clips.
bool isTempoTrack(const Track &t)
Returns true if this is a TempoTrack.
Represents a duration in beats.
Represents a position in real-life time.
Represents the position of a clip on a timeline.
ID for objects of type EditElement - e.g.
Represents a time range in an Edit stored as either time or beats.
time
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.