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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TrackCompManager.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
14TrackCompManager::CompSection::CompSection (const juce::ValueTree& v) : state (v)
15{
16 updateTrack();
17 updateEnd();
18}
19
20TrackCompManager::CompSection::~CompSection()
21{
22}
23
24TrackCompManager::CompSection* TrackCompManager::CompSection::createAndIncRefCount (const juce::ValueTree& v)
25{
26 auto cs = new CompSection (v);
27 cs->incReferenceCount();
28 return cs;
29}
30
31void TrackCompManager::CompSection::updateFrom (juce::ValueTree& v, const juce::Identifier& i)
32{
33 if (v == state)
34 {
35 if (i == IDs::track) updateTrack();
36 else if (i == IDs::end) updateEnd();
37 }
38 else
39 {
41 }
42}
43
44void TrackCompManager::CompSection::updateTrack() { track = EditItemID::fromProperty (state, IDs::track); }
45void TrackCompManager::CompSection::updateEnd() { end = static_cast<double> (state[IDs::end]); }
46
47//==============================================================================
48TrackCompManager::TrackComp* TrackCompManager::TrackComp::createAndIncRefCount (Edit& edit, const juce::ValueTree& v)
49{
50 auto tc = new TrackComp (edit, v);
51 tc->incReferenceCount();
52 return tc;
53}
54
55TrackCompManager::TrackComp::TrackComp (Edit& e, const juce::ValueTree& v)
56 : ValueTreeObjectList<CompSection> (v), state (v), edit (e)
57{
58 auto um = &edit.getUndoManager();
59 name.referTo (state, IDs::name, um);
60 timeFormat.referTo (state, IDs::timecodeFormat, um);
61 includedColour.referTo (state, IDs::includedColour, um, juce::Colours::green);
62 excludedColour.referTo (state, IDs::excludedColour, um, juce::Colours::red);
63
64 rebuildObjects();
65}
66
67TrackCompManager::TrackComp::~TrackComp()
68{
69 notifyListenersOfDeletion();
70 freeObjects();
71}
72
73
74juce::String TrackCompManager::TrackComp::getSelectableDescription() { return TRANS("Track Comp Group"); }
75
76void TrackCompManager::TrackComp::setTimeFormat (TimeFormat t)
77{
78 const TimeFormat current = timeFormat.get();
79
80 if (current == t)
81 return;
82
83 timeFormat = t;
84 convertTimes (current, t);
85}
86
87
88juce::Array<TimeRange> TrackCompManager::TrackComp::getMuteTimes (const juce::Array<TimeRange>& nonMuteTimes)
89{
90 juce::Array<TimeRange> muteTimes;
91 muteTimes.ensureStorageAllocated (nonMuteTimes.size() + 1);
92
93 auto lastTime = 0_tp;
94
95 if (! nonMuteTimes.isEmpty())
96 {
97 lastTime = std::min (lastTime, nonMuteTimes.getFirst().getStart());
98
99 for (auto r : nonMuteTimes)
100 {
101 muteTimes.add ({ lastTime, r.getStart() });
102 lastTime = r.getEnd();
103 }
104 }
105
106 muteTimes.add ({ lastTime, TimePosition::fromSeconds (std::numeric_limits<double>::max()) });
107
108 return muteTimes;
109}
110
111juce::Array<TimeRange> TrackCompManager::TrackComp::getNonMuteTimes (Track& t, TimeDuration crossfadeTime) const
112{
113 auto halfCrossfade = crossfadeTime / 2.0;
114 juce::Array<TimeRange> nonMuteTimes;
115
116 auto& ts = edit.tempoSequence;
117 const bool convertFromBeats = timeFormat == beats;
118
119 for (const auto& sec : getSectionsForTrack (&t))
120 {
121 auto s = sec.timeRange.getStart();
122 auto e = sec.timeRange.getEnd();
123
124 if (convertFromBeats)
125 {
126 s = ts.toTime (BeatPosition::fromBeats (s)).inSeconds();
127 e = ts.toTime (BeatPosition::fromBeats (e)).inSeconds();
128 }
129
130 nonMuteTimes.add ({ TimePosition::fromSeconds (s) - halfCrossfade,
131 TimePosition::fromSeconds (e) + halfCrossfade });
132 }
133
134 // remove any overlaps
135 for (int i = 0; i < nonMuteTimes.size() - 1; ++i)
136 {
137 auto& r1 = nonMuteTimes.getReference (i);
138 auto& r2 = nonMuteTimes.getReference (i + 1);
139
140 if (r1.getEnd() > r2.getStart())
141 {
142 auto diff = (r1.getEnd() - r2.getStart()) / 2.0;
143 r1 = r1.withEnd (r1.getEnd() - diff);
144 jassert (r1.getEnd() > r1.getStart());
145 r2 = r2.withStart (r2.getStart() + diff);
146 jassert (r2.getEnd() > r2.getStart());
147 }
148 }
149
150 return nonMuteTimes;
151}
152
153TimeRange TrackCompManager::TrackComp::getTimeRange() const
154{
156
157 const auto crossfadeTimeMs = edit.engine.getPropertyStorage().getProperty (SettingID::compCrossfadeMs, 20.0);
158 const auto crossfadeTime = TimeDuration::fromSeconds (static_cast<double> (crossfadeTimeMs) / 1000.0);
159 const auto halfCrossfade = crossfadeTime / 2.0;
160
161 auto& ts = edit.tempoSequence;
162 bool convertFromBeats = timeFormat == beats;
163
164 for (const auto& sec : getSectionsForTrack ({}))
165 {
166 if (! sec.compSection->getTrack().isValid())
167 continue;
168
169 auto s = sec.timeRange.getStart();
170 auto e = sec.timeRange.getEnd();
171
172 if (convertFromBeats)
173 {
174 s = ts.toTime (BeatPosition::fromBeats (s)).inSeconds();
175 e = ts.toTime (BeatPosition::fromBeats (e)).inSeconds();
176 }
177
178 TimeRange secTime (TimePosition::fromSeconds (s) - halfCrossfade,
179 TimePosition::fromSeconds (e) + halfCrossfade);
180 time = time.isEmpty() ? secTime : time.getUnionWith (secTime);
181 }
182
183 return time;
184}
185
186void TrackCompManager::TrackComp::setName (const juce::String& n)
187{
188 name = n;
189}
190
191EditItemID TrackCompManager::TrackComp::getSourceTrackID (const juce::ValueTree& v)
192{
193 return EditItemID::fromProperty (v, IDs::track);
194}
195
196void TrackCompManager::TrackComp::setSourceTrackID (juce::ValueTree& v, EditItemID newID, juce::UndoManager* um)
197{
198 v.setProperty (IDs::track, newID, um);
199}
200
201void TrackCompManager::TrackComp::setSectionTrack (CompSection& cs, const Track::Ptr& t)
202{
203 jassert (cs.state.getParent() == state);
204 auto um = &edit.getUndoManager();
205 setSourceTrackID (cs.state, t != nullptr ? t->itemID : EditItemID(), um);
206}
207
208void TrackCompManager::TrackComp::removeSection (CompSection& cs)
209{
210 auto um = &edit.getUndoManager();
211 auto previous = cs.state.getSibling (-1);
212 auto next = cs.state.getSibling (1);
213 bool remove = false;
214
215 if (next.isValid())
216 {
217 if (getSourceTrackID (next).isValid())
218 setSourceTrackID (cs.state, {}, um);
219 else
220 remove = true;
221 }
222 else if (previous.isValid())
223 {
224 if (! getSourceTrackID (previous).isValid())
225 state.removeChild (previous, um);
226
227 remove = true;
228 }
229
230 if (remove)
231 state.removeChild (cs.state, um);
232}
233
234TrackCompManager::CompSection* TrackCompManager::TrackComp::moveSectionToEndAt (CompSection* cs, double newEndTime)
235{
236 if (cs == nullptr)
237 return {};
238
239 return moveSection (cs, newEndTime - cs->getEnd());
240}
241
242TrackCompManager::CompSection* TrackCompManager::TrackComp::moveSection (CompSection* cs, double timeDelta)
243{
244 if (timeDelta == 0.0 || cs == nullptr)
245 return cs;
246
247 bool needToAddSectionAtStart = false;
248 const int sectionIndex = objects.indexOf (cs);
249
250 // first section
251 if (sectionIndex == 0)
252 {
253 if (timeDelta > 0.0)
254 needToAddSectionAtStart = true;
255 else
256 return cs;
257 }
258
259 // move section
260 auto um = &edit.getUndoManager();
261 juce::WeakReference<CompSection> prevCs = objects[sectionIndex - 1];
262 juce::Range<double> oldSectionTimes (prevCs == nullptr ? 0.0 : prevCs->getEnd(), cs->getEnd());
263 auto newSectionTimes = oldSectionTimes + timeDelta;
264
265 if (newSectionTimes.getStart() < 0.0)
266 newSectionTimes = newSectionTimes.movedToStartAt (0.0);
267
268 if (oldSectionTimes == newSectionTimes)
269 return cs;
270
271 removeSectionsWithinRange (oldSectionTimes.getUnionWith (newSectionTimes), cs);
272 prevCs = objects[objects.indexOf (cs) - 1];
273
274 if (prevCs != nullptr)
275 prevCs->state.setProperty (IDs::end, newSectionTimes.getStart(), um);
276
277 cs->state.setProperty (IDs::end, newSectionTimes.getEnd(), um);
278
279 if (needToAddSectionAtStart)
280 addSection (nullptr, timeDelta);
281
282 return cs;
283}
284
285TrackCompManager::CompSection* TrackCompManager::TrackComp::moveSectionEndTime (CompSection* cs, double newTime)
286{
287 const auto minSectionLength = 0.01;
288
289 juce::WeakReference<CompSection> prevCs = objects[objects.indexOf (cs) - 1];
290 juce::Range<double> oldSectionTimes (prevCs == nullptr ? 0.0 : prevCs->getEnd(), cs->getEnd());
291 auto newSectionTimes = oldSectionTimes.withEnd (newTime);
292 auto um = &edit.getUndoManager();
293
294 if (newSectionTimes.getEnd() >= oldSectionTimes.getEnd())
295 {
296 // If expanding remove all sections within the new range
297 removeSectionsWithinRange (newSectionTimes, cs);
298 cs->state.setProperty (IDs::end, newSectionTimes.getEnd(), um);
299 return cs;
300 }
301
302 if (newSectionTimes.getLength() >= minSectionLength)
303 {
304 cs->state.setProperty (IDs::end, newSectionTimes.getEnd(), um);
305 return cs;
306 }
307
308 if (newSectionTimes.getLength() < minSectionLength)
309 {
310 newSectionTimes = oldSectionTimes.withLength (minSectionLength);
311 cs->state.setProperty (IDs::end, newSectionTimes.getEnd(), um);
312 return cs;
313 }
314
315 return cs;
316}
317
318int TrackCompManager::TrackComp::removeSectionsWithinRange (juce::Range<double> timeRange, CompSection* sectionToKeep)
319{
320 int numRemoved = 0;
321 auto sections = getSectionsForTrack ({});
322
323 for (int i = sections.size(); --i >= 0;)
324 {
325 auto& section = sections.getReference (i);
326 auto sectionTime = section.timeRange;
327
328 if (section.compSection != sectionToKeep
329 && (timeRange.contains (sectionTime) || sectionTime.getLength() < 0.0))
330 {
331 state.removeChild (section.compSection->state, &edit.getUndoManager());
332 ++numRemoved;
333 }
334
335 if (sectionTime.getStart() < timeRange.getStart())
336 break;
337 }
338
339 return numRemoved;
340}
341
343{
344 static int compareElements (const juce::ValueTree& f, const juce::ValueTree& s) noexcept
345 {
346 return double (f[IDs::end]) < double (s[IDs::end]) ? -1 : 1;
347 }
348};
349
350juce::ValueTree TrackCompManager::TrackComp::addSection (EditItemID trackID, double endTime,
352{
353 auto newSection = createValueTree (IDs::COMPSECTION,
354 IDs::track, trackID,
355 IDs::end, endTime);
356
357 int insertIndex = 0;
358 const int numSections = state.getNumChildren();
359
360 for (int i = numSections; --i >= 0;)
361 {
362 auto v = state.getChild (i);
363
364 if (double (v[IDs::end]) < endTime)
365 break;
366
367 insertIndex = i;
368 }
369
370 state.addChild (newSection, insertIndex, um);
371
372 SectionSorter sorter;
373 state.sort (sorter, &edit.getUndoManager(), false);
374
375 return newSection;
376}
377
378TrackCompManager::CompSection* TrackCompManager::TrackComp::addSection (Track::Ptr t, double endTime)
379{
380 auto trackID = t == nullptr ? EditItemID() : t->itemID;
381 auto cs = getCompSectionFor (addSection (trackID, endTime, &edit.getUndoManager()));
382 jassert (cs != nullptr);
383 return cs;
384}
385
386TrackCompManager::CompSection* TrackCompManager::TrackComp::splitSectionAtTime (double time)
387{
388 EditItemID trackID;
389
390 if (auto cs = findSectionAtTime (nullptr, time))
391 trackID = cs->getTrack();
392
393 return getCompSectionFor (addSection (trackID, time, &edit.getUndoManager()));
394}
395
396juce::Array<TrackCompManager::TrackComp::Section> TrackCompManager::TrackComp::getSectionsForTrack (const Track::Ptr& track) const
397{
398 juce::Array<Section> sections;
399 double lastTime = 0.0;
400 auto trackId = track == nullptr ? EditItemID() : track->itemID;
401
402 for (auto cs : objects)
403 {
404 const auto t = cs->getEnd();
405
406 if (! trackId.isValid() || cs->getTrack() == trackId)
407 sections.add (Section { cs, { lastTime, t } });
408
409 lastTime = t;
410 }
411
412 return sections;
413}
414
415TrackCompManager::CompSection* TrackCompManager::TrackComp::findSectionWithEdgeTimeWithin (const Track::Ptr& track,
416 juce::Range<double> timeRange,
417 bool& startEdge) const
418{
419 for (const auto& section : getSectionsForTrack (track))
420 {
421 if (timeRange.contains (section.timeRange.getStart()))
422 {
423 startEdge = true;
424 return objects[objects.indexOf (section.compSection) - 1];
425 }
426
427 if (timeRange.contains (section.timeRange.getEnd()))
428 {
429 startEdge = false;
430 return section.compSection;
431 }
432 }
433
434 return {};
435}
436
437TrackCompManager::CompSection* TrackCompManager::TrackComp::findSectionAtTime (const Track::Ptr& track, double time) const
438{
439 auto trackID = track != nullptr ? track->itemID : EditItemID();
440
441 for (const auto& section : getSectionsForTrack (track))
442 {
443 auto* cs = section.compSection;
444
445 if ((track == nullptr || cs->getTrack() == trackID)
446 && section.timeRange.contains (time))
447 return cs;
448 }
449
450 return {};
451}
452
453TrackCompManager::CompSection* TrackCompManager::TrackComp::createNewObject (const juce::ValueTree& v)
454{
455 return CompSection::createAndIncRefCount (v);
456}
457
458bool TrackCompManager::TrackComp::isSuitableType (const juce::ValueTree& v) const { return v.hasType (IDs::COMPSECTION); }
459void TrackCompManager::TrackComp::deleteObject (CompSection* cs) { cs->decReferenceCount(); }
460void TrackCompManager::TrackComp::newObjectAdded (CompSection*) {}
461void TrackCompManager::TrackComp::objectRemoved (CompSection*) {}
462void TrackCompManager::TrackComp::objectOrderChanged() {}
463
464void TrackCompManager::TrackComp::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i)
465{
466 if (v.hasType (IDs::COMPSECTION))
467 if (CompSection* cs = getCompSectionFor (v))
468 cs->updateFrom (v, i);
469}
470
471TrackCompManager::CompSection* TrackCompManager::TrackComp::getCompSectionFor (const juce::ValueTree& v)
472{
473 jassert (v.hasType (IDs::COMPSECTION));
474
475 for (auto cs : objects)
476 if (cs->state == v)
477 return cs;
478
479 return {};
480}
481
482void TrackCompManager::TrackComp::convertFromSecondsToBeats()
483{
484 auto& ts = edit.tempoSequence;
485 auto um = &edit.getUndoManager();
486
487 for (auto cs : objects)
488 {
489 const auto s = cs->getEnd();
490 const auto b = ts.toBeats (TimePosition::fromSeconds (s)).inBeats();
491 cs->state.setProperty (IDs::end, b, um);
492 jassert (cs->getEnd() == b);
493 }
494}
495
496void TrackCompManager::TrackComp::convertFromBeatsToSeconds()
497{
498 auto& ts = edit.tempoSequence;
499 auto um = &edit.getUndoManager();
500
501 for (auto cs : objects)
502 {
503 const auto b = cs->getEnd();
504 const auto s = ts.toTime (BeatPosition::fromBeats (b)).inSeconds();
505 cs->state.setProperty (IDs::end, s, um);
506 jassert (cs->getEnd() == s);
507 }
508}
509
510void TrackCompManager::TrackComp::convertTimes (TimeFormat o, TimeFormat n)
511{
512 if (o == n)
513 return;
514
515 if (o == seconds)
516 {
517 convertFromSecondsToBeats();
518 return;
519 }
520
521 convertFromBeatsToSeconds();
522}
523
524//==============================================================================
526{
528 {
529 rebuildObjects();
530 }
531
532 ~TrackCompList() override
533 {
534 freeObjects();
535 }
536
537 bool isSuitableType (const juce::ValueTree& v) const override { return v.hasType (IDs::TRACKCOMP); }
538 TrackComp* createNewObject (const juce::ValueTree& v) override { return TrackComp::createAndIncRefCount (edit, v); }
539 void deleteObject (TrackComp* tc) override { tc->decReferenceCount(); }
540 void newObjectAdded (TrackComp*) override {}
541 void objectRemoved (TrackComp*) override {}
542 void objectOrderChanged() override {}
543
544private:
545 Edit& edit;
547};
548
549
550//==============================================================================
551TrackCompManager::TrackCompManager (Edit& e) : edit (e) {}
552TrackCompManager::~TrackCompManager() {}
553
554void TrackCompManager::initialise (const juce::ValueTree& v)
555{
556 jassert (v.hasType (IDs::TRACKCOMPS));
557 state = v;
558 trackCompList = std::make_unique<TrackCompList> (edit, v);
559}
560
561int TrackCompManager::getNumGroups() const
562{
563 return state.getNumChildren();
564}
565
566juce::StringArray TrackCompManager::getCompNames() const
567{
568 juce::StringArray names;
569 auto numComps = state.getNumChildren();
570
571 for (int i = 0; i < numComps; ++i)
572 {
573 auto v = state.getChild (i);
574 jassert (v.hasType (IDs::TRACKCOMP));
575
576 auto name = v.getProperty (IDs::name).toString();
577
578 if (name.isEmpty())
579 name = juce::String (TRANS("Comp Group")) + " " + juce::String (i);
580
581 names.add (name);
582 }
583
584 return names;
585}
586
587juce::String TrackCompManager::getCompName (int index)
588{
589 if (index == -1)
590 return "<" + TRANS("None") + ">";
591
592 auto name = getCompNames()[index];
593
594 if (name.isNotEmpty())
595 return name;
596
597 jassert (juce::isPositiveAndBelow (index, getNumGroups()));
598 return TRANS("Comp Group") + " " + juce::String (index);
599}
600
601void TrackCompManager::setCompName (int index, const juce::String& name)
602{
603 jassert (juce::isPositiveAndBelow (index, getNumGroups()));
604
605 auto v = state.getChild (index);
606
607 if (v.isValid())
608 v.setProperty (IDs::name, name, &edit.getUndoManager());
609}
610
611int TrackCompManager::addGroup (const juce::String& name)
612{
613 auto v = createValueTree (IDs::TRACKCOMP,
614 IDs::name, name);
615
616 state.addChild (v, -1, &edit.getUndoManager());
617
618 return getNumGroups() - 1;
619}
620
621void TrackCompManager::removeGroup (int index)
622{
623 for (auto at : getAudioTracks (edit))
624 if (at->getCompGroup() == index)
625 at->setCompGroup (-1);
626
627 state.removeChild (index, &edit.getUndoManager());
628}
629
630juce::Array<Track*> TrackCompManager::getTracksInComp (int index)
631{
632 juce::Array<Track*> tracks;
633
634 for (auto at : getAudioTracks (edit))
635 if (at->getCompGroup() == index)
636 tracks.add (at);
637
638 return tracks;
639}
640
641TrackCompManager::TrackComp::Ptr TrackCompManager::getTrackComp (AudioTrack* at)
642{
643 return at == nullptr ? nullptr : trackCompList->objects[at->getCompGroup()];
644}
645
646}} // namespace tracktion { inline namespace engine
bool isEmpty() const noexcept
void ensureStorageAllocated(int minNumElements)
int size() const noexcept
ElementType getFirst() const noexcept
void add(const ElementType &newElement)
ElementType & getReference(int index) noexcept
bool isValid() const noexcept
const String & toString() const noexcept
constexpr ValueType getStart() const noexcept
constexpr ValueType getEnd() const noexcept
constexpr Range withEnd(const ValueType newEnd) const noexcept
constexpr Range getUnionWith(Range other) const noexcept
constexpr Range movedToStartAt(const ValueType newStart) const noexcept
constexpr Range withLength(const ValueType newLength) const noexcept
constexpr bool contains(const ValueType position) const noexcept
void decReferenceCount() noexcept
void add(String stringToAdd)
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
The Tracktion Edit class!
T end(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
typedef double
T min(T... args)
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
T next(T... args)
remove
ID for objects of type EditElement - e.g.
time