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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TrackUtils.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 : parentTrackID (parent != nullptr ? parent->itemID : EditItemID()),
16 precedingTrackID (preceding != nullptr ? preceding->itemID : EditItemID())
17{
18}
19
21 : parentTrackID (parent),
22 precedingTrackID (preceding)
23{
24}
25
27{
28 juce::Array<Track*> siblingTracks;
29
30 if (auto parent = t.getParentTrack())
31 {
32 parentTrackID = parent->itemID;
33 siblingTracks = parent->getAllSubTracks (false);
34 }
35 else
36 {
37 siblingTracks = getTopLevelTracks (t.edit);
38 }
39
40 int index = siblingTracks.indexOf (&t);
41 jassert (index >= 0);
42
43 if (! insertBefore && index == 0 && parentTrackID.isValid())
44 if (auto tr = siblingTracks[index])
45 precedingTrackID = tr->itemID;
46
47 if (index > 0)
48 precedingTrackID = siblingTracks.getUnchecked (index - (insertBefore ? 1 : 0))->itemID;
49}
50
52{
53 {
54 auto p = v.getParent();
55
56 if (TrackList::isTrack (p))
57 parentTrackID = EditItemID::fromProperty (p, IDs::id);
58 }
59
60 {
61 auto p = v.getSibling (-1);
62
63 while (p.isValid() && (! TrackList::isTrack (p)))
64 p = p.getSibling (-1);
65
66 precedingTrackID = EditItemID::fromProperty (p, IDs::id);
67 }
68}
69
70//==============================================================================
72 : ValueTreeObjectList<Track> (parentTree), edit (e)
73{
74 rebuildObjects();
75 rebuilding = false;
76}
77
79{
80 freeObjects();
81}
82
84{
85 for (auto t : objects)
86 if (t->state == v)
87 return t;
88
89 return {};
90}
91
93{
94 for (auto t : objects)
95 {
96 if (! f (*t))
97 return false;
98
99 if (auto subList = t->getSubTrackList())
100 if (! subList->visitAllRecursive (f))
101 return false;
102 }
103
104 return true;
105}
106
108{
109 for (auto t : objects)
110 if (! f (*t))
111 break;
112}
113
114void TrackList::visitAllTracks (const std::function<bool(Track&)>& f, bool recursive) const
115{
116 if (recursive)
118 else
120}
121
123{
124 return v.hasType (IDs::TRACK) || v.hasType (IDs::FOLDERTRACK) || v.hasType (IDs::AUTOMATIONTRACK);
125}
126
127bool TrackList::isArrangerTrack (const juce::ValueTree& v) noexcept { return v.hasType (IDs::ARRANGERTRACK); }
128bool TrackList::isChordTrack (const juce::ValueTree& v) noexcept { return v.hasType (IDs::CHORDTRACK); }
129bool TrackList::isMarkerTrack (const juce::ValueTree& v) noexcept { return v.hasType (IDs::MARKERTRACK); }
130bool TrackList::isTempoTrack (const juce::ValueTree& v) noexcept { return v.hasType (IDs::TEMPOTRACK); }
131bool TrackList::isMasterTrack (const juce::ValueTree& v) noexcept { return v.hasType (IDs::MASTERTRACK); }
132
134{
135 return isMarkerTrack (v) || isTempoTrack (v) || isChordTrack (v) || isArrangerTrack (v) || isMasterTrack (v)
136 || (v.hasType (IDs::AUTOMATIONTRACK) && isMasterTrack (v.getParent()));
137}
138
139bool TrackList::isTrack (const juce::ValueTree& v) noexcept { return isMovableTrack (v) || isFixedTrack (v); }
140
141bool TrackList::isTrack (const juce::Identifier& i) noexcept
142{
143 return i == IDs::TRACK || i == IDs::FOLDERTRACK || i == IDs::AUTOMATIONTRACK
144 || i == IDs::MARKERTRACK || i == IDs::TEMPOTRACK || i == IDs::CHORDTRACK
145 || i == IDs::ARRANGERTRACK || i == IDs::MASTERTRACK;
146}
147
149{
150 for (int i = v.getNumChildren(); --i >= 0;)
151 if (isMovableTrack (v.getChild (i)))
152 return true;
153
154 return false;
155}
156
157bool TrackList::isSuitableType (const juce::ValueTree& v) const
158{
159 return isTrack (v);
160}
161
162Track* TrackList::createNewObject (const juce::ValueTree& v)
163{
164 juce::ValueTree vt (v);
165 Track::Ptr t;
166
167 if (rebuilding)
168 {
169 // This logic avoids creating new tracks when they are sub-foldered
170 if (! edit.isLoading())
171 if (auto trk = dynamic_cast<Track*> (edit.trackCache.findItem (EditItemID::fromID (v))))
172 t = trk;
173
174 if (t == nullptr)
175 t = edit.loadTrackFrom (vt);
176 }
177 else
178 {
179 if (auto trk = dynamic_cast<Track*> (edit.trackCache.findItem (EditItemID::fromID (v))))
180 t = trk;
181 else
182 t = edit.createTrack (v);
183 }
184
185 if (t != nullptr)
186 {
187 t->incReferenceCount();
188 edit.updateTrackStatusesAsync();
189 return t.get();
190 }
191
193 return {};
194}
195
196void TrackList::deleteObject (Track* t)
197{
198 jassert (t != nullptr);
199 t->decReferenceCount();
200 edit.updateTrackStatusesAsync();
201}
202
203void TrackList::newObjectAdded (Track* t)
204{
205 if (edit.isLoading())
206 return;
207
209 t->refreshCurrentAutoParam();
210
211 if (auto tl = t->getSubTrackList())
212 tl->visitAllRecursive ([] (Track& track)
213 {
214 track.refreshCurrentAutoParam();
215 return true;
216 });
217}
218
219void TrackList::objectRemoved (Track*) {}
220
221void TrackList::objectOrderChanged()
222{
223 edit.updateTrackStatusesAsync();
224}
225
227{
228 struct TrackTypeSorter
229 {
230 static int getPriority (const juce::ValueTree& v) noexcept
231 {
232 if (isMovableTrack (v)) return 6;
233 if (isMasterTrack (v)) return 5;
234 if (isTempoTrack (v)) return 4;
235 if (isMarkerTrack (v)) return 3;
236 if (isChordTrack (v)) return 2;
237 if (isArrangerTrack (v)) return 1;
238 return 0;
239 }
240
241 static int compareElements (const juce::ValueTree& first, const juce::ValueTree& second) noexcept
242 {
243 return getPriority (first) - getPriority (second);
244 }
245 };
246
247 TrackTypeSorter sorter;
248 editState.sort (sorter, um, true);
249}
250
251void TrackList::handleAsyncUpdate()
252{
253 if (! edit.isLoading())
254 sortTracksByType (edit.state, &edit.getUndoManager());
255}
256
257
258//==============================================================================
260 : position (c.getPosition().time),
261 src (c.getTrack()),
262 dst (c.getTrack())
263{
264}
265
267{
268 position = position.getUnionWith (other.position);
269
270 for (const auto& p : other.activeParameters)
271 if (! containsParameter (p.param.get()))
272 activeParameters.add (p);
273}
274
276{
277 for (auto&& p : activeParameters)
278 if (p.param.get() == param)
279 return true;
280
281 return false;
282}
283
285{
286 return src == other.src
287 && dst == other.dst
288 && position.expanded (TimeDuration::fromSeconds (0.0001)).overlaps (other.position);
289}
290
291
292//==============================================================================
293static AutomationCurve* getDestCurve (Track& t, const AutomatableParameter::Ptr& p)
294{
295 if (p != nullptr)
296 {
297 if (auto plugin = p->getPlugin())
298 {
299 auto name = plugin->getName();
300
301 for (auto f : t.getAllPlugins())
302 if (f->getName() == name)
303 if (auto param = f->getAutomatableParameter (plugin->indexOfAutomatableParameter (p)))
304 return &param->getCurve();
305 }
306 }
307
308 return {};
309}
310
311static bool mergeInto (const TrackAutomationSection& s,
313{
314 for (auto& dstSeg : dst)
315 {
316 if (dstSeg.overlaps (s))
317 {
318 dstSeg.mergeIn (s);
319 return true;
320 }
321 }
322
323 return false;
324}
325
326static void mergeSections (const juce::Array<TrackAutomationSection>& src,
328{
329 for (const auto& srcSeg : src)
330 if (! mergeInto (srcSeg, dst))
331 dst.add (srcSeg);
332}
333
334void moveAutomation (const juce::Array<TrackAutomationSection>& origSections, TimeDuration offset, bool copy)
335{
336 if (origSections.isEmpty())
337 return;
338
340 mergeSections (origSections, sections);
341
342 // find all the original curves
343 for (auto&& section : sections)
344 {
345 for (auto element : section.src->getAllAutomatableEditItems())
346 {
347 for (int k = 0; k < element->getNumAutomatableParameters(); k++)
348 {
349 AutomatableParameter::Ptr param = element->getAutomatableParameter (k);
350
351 if (param->getCurve().getNumPoints() > 0)
352 {
353 TrackAutomationSection::ActiveParameters ap;
354 ap.param = param;
355 ap.curve.setState (param->getCurve().state);
356 ap.curve.setParentState (param->getCurve().parentState);
357 ap.curve.setOwnerParameter (param->getCurve().getOwnerParameter());
358
359 section.activeParameters.add (ap);
360 }
361 }
362 }
363
364 for (auto& ap : section.activeParameters)
365 ap.curve.state = ap.curve.state.createCopy();
366 }
367
368 // delete all the old curves
369 if (! copy)
370 {
371 for (auto& section : sections)
372 {
373 auto sectionTime = section.position;
374
375 for (auto&& activeParam : section.activeParameters)
376 {
377 auto param = activeParam.param;
378 auto& curve = param->getCurve();
379 constexpr auto tolerance = TimeDuration::fromSeconds (0.0001);
380
381 auto startValue = curve.getValueAt (sectionTime.getStart() - tolerance);
382 auto endValue = curve.getValueAt (sectionTime.getEnd() + tolerance);
383
384 auto idx = curve.indexBefore (sectionTime.getEnd() + tolerance);
385 auto endCurve = (idx == -1) ? 0.0f : curve.getPointCurve(idx);
386
387 curve.removePointsInRegion (sectionTime.expanded (tolerance));
388
389 if (std::abs (startValue - endValue) < 0.0001f)
390 {
391 curve.addPoint (sectionTime.getStart(), startValue, 0.0f);
392 curve.addPoint (sectionTime.getEnd(), endValue, endCurve);
393 }
394 else if (startValue > endValue)
395 {
396 curve.addPoint (sectionTime.getStart(), startValue, 0.0f);
397 curve.addPoint (sectionTime.getStart(), endValue, 0.0f);
398 curve.addPoint (sectionTime.getEnd(), endValue, endCurve);
399 }
400 else
401 {
402 curve.addPoint (sectionTime.getStart(), startValue, 0.0f);
403 curve.addPoint (sectionTime.getEnd(), startValue, 0.0f);
404 curve.addPoint (sectionTime.getEnd(), endValue, endCurve);
405 }
406
407 curve.removeRedundantPoints (sectionTime.expanded (tolerance));
408 }
409 }
410 }
411
412 // recreate the curves
413 for (auto& section : sections)
414 {
415 for (auto& activeParam : section.activeParameters)
416 {
417 auto sectionTime = section.position;
418
419 if (auto dstCurve = (section.src == section.dst) ? &activeParam.param->getCurve()
420 : getDestCurve (*section.dst, activeParam.param))
421 {
422 constexpr auto errorMargin = TimeDuration::fromSeconds (0.0001);
423
424 auto start = sectionTime.getStart();
425 auto end = sectionTime.getEnd();
426 auto newStart = start + offset;
427 auto newEnd = end + offset;
428
429 auto& srcCurve = activeParam.curve;
430
431 auto idx1 = srcCurve.indexBefore (newEnd + errorMargin);
432 auto endCurve = idx1 < 0 ? 0 : srcCurve.getPointCurve (idx1);
433
434 auto idx2 = srcCurve.indexBefore (start - errorMargin);
435 auto startCurve = idx2 < 0 ? 0 : srcCurve.getPointCurve (idx2);
436
437 auto srcStartVal = srcCurve.getValueAt (start - errorMargin);
438 auto srcEndVal = srcCurve.getValueAt (end + errorMargin);
439
440 auto dstStartVal = dstCurve->getValueAt (newStart - errorMargin);
441 auto dstEndVal = dstCurve->getValueAt (newEnd + errorMargin);
442
443 TimeRange totalRegionWithMargin (newStart - errorMargin, newEnd + errorMargin);
444 TimeRange startWithMargin (newStart - errorMargin, newStart + errorMargin);
445 TimeRange endWithMargin (newEnd - errorMargin, newEnd + errorMargin);
446
448
449 for (int i = 0; i < srcCurve.getNumPoints(); ++i)
450 {
451 auto pt = srcCurve.getPoint (i);
452
453 if (pt.time >= start - errorMargin && pt.time <= sectionTime.getEnd() + errorMargin)
454 origPoints.add (pt);
455 }
456
457 dstCurve->removePointsInRegion (totalRegionWithMargin);
458
459 for (const auto& pt : origPoints)
460 dstCurve->addPoint (pt.time + offset, pt.value, pt.curve);
461
462 auto startPoints = dstCurve->getPointsInRegion (startWithMargin);
463 auto endPoints = dstCurve->getPointsInRegion (endWithMargin);
464
465 dstCurve->removePointsInRegion (startWithMargin);
466 dstCurve->removePointsInRegion (endWithMargin);
467
468 dstCurve->addPoint (newStart, dstStartVal, startCurve);
469 dstCurve->addPoint (newStart, srcStartVal, startCurve);
470
471 for (auto& point : startPoints)
472 dstCurve->addPoint (newStart, point.value, point.curve);
473
474 for (auto& point : endPoints)
475 dstCurve->addPoint (newEnd, point.value, point.curve);
476
477 dstCurve->addPoint (newEnd, srcEndVal, endCurve);
478 dstCurve->addPoint (newEnd, dstEndVal, endCurve);
479
480 dstCurve->removeRedundantPoints (totalRegionWithMargin);
481 }
482 }
483 }
484
485 // activate the automation curves on the new tracks
486 juce::Array<Track*> src, dst;
487
488 for (auto& section : sections)
489 {
490 if (section.src != section.dst)
491 {
492 if (! src.contains (section.src.get()))
493 {
494 src.add (section.src.get());
495 dst.add (section.dst.get());
496 }
497 }
498 }
499
500 for (int i = 0; i < src.size(); ++i)
501 {
502 if (auto ap = src.getUnchecked (i)->getCurrentlyShownAutoParam())
503 {
504 for (auto p : dst.getUnchecked (i)->getAllAutomatableParams())
505 {
506 if (p->getPluginAndParamName() == ap->getPluginAndParamName())
507 {
508 dst.getUnchecked (i)->setCurrentlyShownAutoParam (p);
509 break;
510 }
511 }
512 }
513 }
514}
515
516}} // namespace tracktion { inline namespace engine
ElementType getUnchecked(int index) const
bool isEmpty() const noexcept
int indexOf(ParameterType elementToLookFor) const
void add(const ElementType &newElement)
bool isValid() const noexcept
void sort(ElementComparator &comparator, UndoManager *undoManager, bool retainOrderOfEquivalentItems)
The Tracktion Edit class!
juce::ValueTree state
The ValueTree of the Edit state.
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
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.
Base class for EditItems that live in a Track, e.g.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
#define jassert(expression)
#define jassertfalse
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
juce::Array< Track * > getTopLevelTracks(const Edit &edit)
Returns all of the non-foldered tracks in an Edit.
bool isArrangerTrack(const Track &t)
Returns true if this is an ArrangerTrack.
void moveAutomation(const juce::Array< TrackAutomationSection > &origSections, TimeDuration offset, bool copy)
Moves a set of automation optionally applying an offset and copying the automation (rather than movin...
bool isMarkerTrack(const Track &t)
Returns true if this is a MarkerTrack.
bool isChordTrack(const Track &t)
Returns true if this is a ChordTrack.
Plugin::Array getAllPlugins(const Edit &edit, bool includeMasterVolume)
Returns all the plugins in a given Edit.
bool isMasterTrack(const Track &t)
Returns true if this is a MasterTrack.
bool isTempoTrack(const Track &t)
Returns true if this is a TempoTrack.
Represents a duration in real-life time.
ID for objects of type EditElement - e.g.
Holds a reference to a section of automation for a given Track.
Track::Ptr src
The time range of the automation section.
TrackAutomationSection() noexcept=default
Construts an empty section.
bool containsParameter(AutomatableParameter *) const
Tests whether this section contains a given parameter.
void mergeIn(const TrackAutomationSection &)
The destination Track.
juce::Array< ActiveParameters > activeParameters
A list of parameteres and their curves.
bool overlaps(const TrackAutomationSection &) const
Tests whether another section overlaps with this one.
TrackInsertPoint(Track *parent, Track *preceding)
Creates an insertion point with a parent and preceeding track.
static bool isMarkerTrack(const juce::ValueTree &) noexcept
Returns true if the state is for a MarkerTrack.
static bool isMasterTrack(const juce::ValueTree &) noexcept
Returns true if the state is for a MasterTrack.
Track * getTrackFor(const juce::ValueTree &) const
Returns a Track for a given state.
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.
TrackList(Edit &, const juce::ValueTree &parent)
Creates a TrackList for a parent state.
static bool isChordTrack(const juce::ValueTree &) noexcept
Returns true if the state is for a ChordTrack.
static bool isTempoTrack(const juce::ValueTree &) noexcept
Returns true if the state is for a TempoTrack.
void visitAllTracks(const std::function< bool(Track &)> &, bool recursive) const
Calls the given function on all Track[s], optionally recursively.
void visitAllTopLevel(const std::function< bool(Track &)> &) const
Calls the given function on all top-level Track[s].
static bool isTrack(const juce::ValueTree &) noexcept
Returns true if the given ValeTree is for a known Track type.
static bool isArrangerTrack(const juce::ValueTree &) noexcept
Returns true if the state is for an ArrangerTrack.
static bool hasAnySubTracks(const juce::ValueTree &)
Returns true if the track has any sub tracks.
bool visitAllRecursive(const std::function< bool(Track &)> &) const
Calls the given function on all Track[s].
static bool isMovableTrack(const juce::ValueTree &) noexcept
Returns true if the track is movable.
time