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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AutomationCurve.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
14AutomationCurve::AutomationCurve()
15 : state (IDs::AUTOMATIONCURVE)
16{
17}
18
19AutomationCurve::AutomationCurve (const juce::ValueTree& p, const juce::ValueTree& v)
20 : parentState (p), state (v)
21{
22 if (! state.isValid())
23 state = juce::ValueTree (IDs::AUTOMATIONCURVE);
24}
25
26AutomationCurve::AutomationCurve (const AutomationCurve& o)
27 : parentState (o.parentState), state (o.state), ownerParam (o.ownerParam)
28{
29}
30
31AutomationCurve::~AutomationCurve()
32{
33}
34
35void AutomationCurve::setState (const juce::ValueTree& v)
36{
37 state = v;
38 jassert (state.hasType (IDs::AUTOMATIONCURVE));
39 jassert (state.getParent() == parentState);
40}
41
42void AutomationCurve::setParentState (const juce::ValueTree& v)
43{
44 parentState = v;
45}
46
47void AutomationCurve::setOwnerParameter (AutomatableParameter* p)
48{
49 ownerParam = p;
50
51 if (p != nullptr)
52 state.setProperty (IDs::paramID, p->paramID, nullptr);
53}
54
55juce::UndoManager* AutomationCurve::getUndoManager() const
56{
57 return ownerParam != nullptr ? &ownerParam->getEdit().getUndoManager() : nullptr;
58}
59
60//==============================================================================
61int AutomationCurve::getNumPoints() const noexcept
62{
63 return state.getNumChildren();
64}
65
66AutomationCurve::AutomationPoint AutomationCurve::getPoint (int index) const noexcept
67{
68 auto child = state.getChild (index);
69
70 if (! child.isValid() && ownerParam != nullptr)
71 return AutomationPoint ({}, ownerParam->getCurrentValue(), 0);
72
73 return AutomationPoint (TimePosition::fromSeconds (static_cast<double> (child.getProperty (IDs::t))),
74 child.getProperty (IDs::v),
75 child.getProperty (IDs::c));
76}
77
78TimePosition AutomationCurve::getPointTime (int index) const noexcept
79{
80 return TimePosition::fromSeconds (static_cast<double> (state.getChild (index).getProperty (IDs::t)));
81}
82
83float AutomationCurve::getPointValue (int index) const noexcept
84{
85 if (index >= getNumPoints() && ownerParam != nullptr)
86 return ownerParam->getCurrentBaseValue();
87
88 return state.getChild (index).getProperty (IDs::v);
89}
90
91float AutomationCurve::getPointCurve (int index) const noexcept
92{
93 return state.getChild (index).getProperty (IDs::c);
94}
95
96int AutomationCurve::indexBefore (TimePosition t) const
97{
98 for (int i = getNumPoints(); --i >= 0;)
99 if (getPointTime (i) <= t)
100 return i;
101
102 return -1;
103}
104
105int AutomationCurve::nextIndexAfter (TimePosition t) const
106{
107 auto num = getNumPoints();
108
109 for (int i = 0; i < num; ++i)
110 if (getPointTime (i) >= t)
111 return i;
112
113 return num;
114}
115
116TimeDuration AutomationCurve::getLength() const
117{
118 return toDuration (getPointTime (getNumPoints() - 1));
119}
120
121//==============================================================================
122float AutomationCurve::getValueAt (TimePosition timePos) const
123{
124 TRACKTION_ASSERT_MESSAGE_THREAD
125 jassert (getOwnerParameter() != nullptr);
126
127 const auto index = nextIndexAfter (timePos);
128 const auto time = timePos.inSeconds();
129
130 if (index <= 0)
131 return getPointValue (0);
132
133 auto numPoints = getNumPoints();
134
135 if (index >= numPoints)
136 return getPointValue (numPoints - 1);
137
138 auto p1 = state.getChild (index - 1);
139 auto p2 = state.getChild (index);
140
141 const double time1 = p1.getProperty (IDs::t);
142 const float curve1 = p1.getProperty (IDs::c);
143 const float value1 = p1.getProperty (IDs::v);
144
145 const double time2 = p2.getProperty (IDs::t);
146 const float value2 = p2.getProperty (IDs::v);
147
148 if (curve1 == 0.0f)
149 {
150 auto alpha = (float) ((time - time1) / (time2 - time1));
151 return value1 + alpha * (value2 - value1);
152 }
153
154 if (curve1 >= -0.5f && curve1 <= 0.5f)
155 {
156 auto bezierPoint = getBezierPoint (index - 1);
157 return getBezierYFromX (time, time1, value1, toTime (bezierPoint.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), bezierPoint.value, time2, value2);
158 }
159
160 double x1, x2;
161 float y1, y2;
162 getBezierEnds (index - 1, x1, y1, x2, y2);
163
164 if (time >= time1 && time <= x1)
165 return value1;
166
167 if (time >= x2 && time <= time2)
168 return value2;
169
170 auto bezierPoint = getBezierPoint (index - 1);
171 return getBezierYFromX (time, x1, y1, toTime (bezierPoint.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), bezierPoint.value, x2, y2);
172}
173
174static double getDistanceFromLine (double& x, double& y,
175 double x1, double y1,
176 double x2, double y2)
177{
178 auto dx = x2 - x1;
179 auto dy = y2 - y1;
180 auto length = hypot (dx, dy);
181
182 auto alongLine = (length > 0) ? juce::jlimit (0.0, 1.0, ((x - x1) * dx + (y - y1) * dy) / (length * length))
183 : ((x < x1) ? 0.0 : 1.0);
184
185 auto nearX = x1 + (x2 - x1) * alongLine;
186 auto nearY = y1 + (y2 - y1) * alongLine;
187 x -= nearX;
188 x *= x;
189 y -= nearY;
190 y *= y;
191
192 auto dist = std::sqrt (x + y);
193 x = nearX;
194 y = nearY;
195
196 return dist;
197}
198
199int AutomationCurve::getNearestPoint (TimePosition& t, float& v, double xToYRatio) const
200{
201 auto numPoints = getNumPoints();
202
203 if (numPoints > 1)
204 {
205 if (t <= getPointTime (0))
206 {
207 v = getPointValue (0);
208 return 0;
209 }
210
211 if (t >= getPointTime (numPoints - 1))
212 {
213 v = getPointValue (numPoints - 1);
214 return numPoints;
215 }
216
217 double bestDist = 1e10;
218 auto bestTime = t;
219 double bestValue = v;
220 int nextIndex = 0;
221
222 for (int i = 0; i < numPoints - 1; ++i)
223 {
224 auto p1 = state.getChild (i);
225 auto p2 = state.getChild (i + 1);
226
227 const auto time1 = TimePosition::fromSeconds (static_cast<double> (p1.getProperty (IDs::t)));
228 const float value1 = p1.getProperty (IDs::v);
229
230 const auto time2 = TimePosition::fromSeconds (static_cast<double> (p2.getProperty (IDs::t)));
231 const float value2 = p2.getProperty (IDs::v);
232
233 auto x = t.inSeconds();
234 auto y = xToYRatio * v;
235
236 auto dist = getDistanceFromLine (x, y,
237 time1.inSeconds(), xToYRatio * value1,
238 time2.inSeconds(), xToYRatio * value2);
239 y /= xToYRatio;
240
241 if (dist < bestDist)
242 {
243 bestDist = dist;
244 bestTime = TimePosition::fromSeconds (x);
245 bestValue = y;
246 nextIndex = i + 1;
247 }
248 }
249
250 t = bestTime;
251 v = (float) bestValue;
252
253 return nextIndex;
254 }
255
256 if (numPoints > 0)
257 {
258 v = getPointValue (0);
259 return t > getPointTime (0) ? 1 : 0;
260 }
261
262 if (ownerParam != nullptr)
263 v = ownerParam->getCurrentValue();
264 else
265 v = 0.0f;
266
267 return 0;
268}
269
270//==============================================================================
271juce::ValueTree AutomationCurve::AutomationPoint::toValueTree() const
272{
273 return createValueTree (IDs::POINT,
274 IDs::t, time.inSeconds(),
275 IDs::v, value,
276 IDs::c, curve);
277}
278
279int AutomationCurve::addPoint (TimePosition time, float value, float curve)
280{
281 int i;
282 for (i = getNumPoints(); --i >= 0;)
283 if (getPointTime (i) <= time)
284 break;
285
286 addPointAtIndex (++i, time, value, curve);
287 return i;
288}
289
290void AutomationCurve::addPointAtIndex (int index, TimePosition time, float value, float curve)
291{
292 state.addChild (AutomationPoint (time, value, curve).toValueTree(), index, getUndoManager());
293 checkParenthoodStatus();
294}
295
296void AutomationCurve::removePoint (int index)
297{
298 state.removeChild (index, getUndoManager());
299 checkParenthoodStatus();
300}
301
302void AutomationCurve::checkParenthoodStatus()
303{
304 bool hasParent = state.getParent() == parentState;
305 bool needsParent = getNumPoints() > 0;
306
307 jassert (hasParent || ! state.getParent().isValid());
308
309 if (needsParent != hasParent)
310 {
311 if (needsParent)
312 parentState.addChild (state, -1, getUndoManager());
313 else
314 parentState.removeChild (state, getUndoManager());
315 }
316}
317
318void AutomationCurve::setPointTime (int index, TimePosition newTime) { state.getChild (index).setProperty (IDs::t, newTime.inSeconds(), getUndoManager()); }
319void AutomationCurve::setPointValue (int index, float newValue) { state.getChild (index).setProperty (IDs::v, newValue, getUndoManager()); }
320void AutomationCurve::setCurveValue (int index, float newCurve) { state.getChild (index).setProperty (IDs::c, newCurve, getUndoManager()); }
321
322int AutomationCurve::movePoint (int index, TimePosition newTime, float newValue, bool removeInterveningPoints)
323{
324 if (juce::isPositiveAndBelow (index, getNumPoints()))
325 {
326 if (removeInterveningPoints)
327 {
328 auto oldTime = getPointTime (index);
329 const bool movingPointBack = newTime < oldTime;
330
331 auto t1 = std::min (newTime, oldTime) - TimeDuration::fromSeconds (0.00001);
332 auto t2 = std::max (newTime, oldTime) + TimeDuration::fromSeconds (0.00001);
333
334 for (int i = getNumPoints(); --i >= 0;)
335 {
336 auto t = getPointTime (i);
337
338 if (t < t1)
339 break;
340
341 // If points lay at the same time, don't remove them
342 if (t == oldTime)
343 {
344 if (movingPointBack)
345 {
346 if (index < i)
347 break;
348 }
349 else
350 {
351 if (index > i)
352 break;
353 }
354 }
355
356 if (t < t2 && i != index)
357 {
358 if (i < index)
359 --index;
360
361 removePoint (i);
362 }
363 }
364 }
365
366 if (index > 0)
367 newTime = std::max (getPointTime (index - 1), newTime);
368 else
369 newTime = std::max (TimePosition(), newTime);
370
371 if (index < getNumPoints() - 1)
372 newTime = std::min (newTime, getPointTime (index + 1));
373
374 if (ownerParam != nullptr)
375 newValue = ownerParam->getValueRange().clipValue (ownerParam->snapToState (newValue));
376
377 auto v = state.getChild (index);
378
379 v.setProperty (IDs::t, newTime.inSeconds(), getUndoManager());
380 v.setProperty (IDs::v, newValue, getUndoManager());
381 }
382 else
383 {
385 }
386
387 return index;
388}
389
390//==============================================================================
391void AutomationCurve::clear()
392{
393 state.removeAllChildren (getUndoManager());
394}
395
396juce::Array<AutomationCurve::AutomationPoint> AutomationCurve::getPointsInRegion (TimeRange range) const
397{
399 auto numPoints = getNumPoints();
400
401 for (int i = 0; i < numPoints; ++i)
402 {
403 auto v = state.getChild (i);
404 auto t = TimePosition::fromSeconds (static_cast<double> (v.getProperty (IDs::t)));
405
406 if (range.contains (t))
407 results.add (AutomationPoint (t, v.getProperty (IDs::v), v.getProperty (IDs::c)));
408 }
409
410 return results;
411}
412
413void AutomationCurve::removePointsInRegion (TimeRange range)
414{
415 for (int i = getNumPoints(); --i >= 0;)
416 {
417 auto t = getPointTime (i);
418
419 if (t < range.getStart())
420 break;
421
422 if (t < range.getEnd())
423 removePoint (i);
424 }
425}
426
427void AutomationCurve::removeRedundantPoints (TimeRange range)
428{
429 constexpr auto threshold = 0.0001;
430
431 for (int i = getNumPoints(); --i >= 0;)
432 {
433 auto t = getPointTime (i);
434
435 if (! range.contains (t))
436 continue;
437
438 auto v = getPointValue (i);
439
440 bool sameLeft = i <= 0 || std::abs (getPointValue (i - 1) - v) < threshold;
441 bool sameRight = i >= getNumPoints() - 1 || std::abs (getPointValue (i + 1) - v) < threshold;
442
443 // if points to left and right have same value
444 if (sameLeft && sameRight)
445 {
446 removePoint (i);
447 continue;
448 }
449
450 // if point to right is exact same
451 if (i < getNumPoints() - 1
452 && std::abs (getPointValue (i + 1) - v) < threshold
453 && std::abs ((getPointTime (i + 1) - t).inSeconds()) < threshold)
454 {
455 removePoint (i);
456 continue;
457 }
458
459 // if points to left and right are at same time
460 if (i > 0
461 && i < getNumPoints() - 1
462 && std::abs ((getPointTime (i - 1) - t).inSeconds()) < threshold
463 && std::abs ((getPointTime (i + 1) - t).inSeconds()) < threshold)
464 {
465 removePoint (i);
466 continue;
467 }
468
469 if (getNumPoints() <= 1)
470 break;
471 }
472}
473
474void AutomationCurve::removeRegionAndCloseGap (TimeRange range)
475{
476 auto valAtStart = getValueAt (range.getStart());
477 auto valAtEnd = getValueAt (range.getEnd());
478
479 if (getNumPoints() == 0)
480 return;
481
482 for (int i = getNumPoints(); --i >= 0;)
483 {
484 auto t = getPointTime (i);
485
486 if (t >= range.getStart() && t <= range.getEnd())
487 removePoint (i);
488 }
489
490 for (int i = 0; i < getNumPoints(); ++i)
491 {
492 auto t = getPointTime (i);
493
494 if (t >= range.getEnd())
495 movePoint (i, t - range.getLength(), getPointValue(i), false);
496 }
497
498 if (getValueAt (range.getStart()) != valAtStart)
499 addPoint (range.getStart(), valAtStart, 0.0f);
500
501 if (valAtStart != valAtEnd)
502 addPoint (range.getStart(), valAtEnd, 0.0f);
503}
504
505int AutomationCurve::countPointsInRegion (TimeRange range) const
506{
507 int num = 0;
508
509 for (int i = getNumPoints(); --i >= 0;)
510 {
511 auto t = getPointTime(i);
512
513 if (t < range.getStart())
514 break;
515
516 if (t < range.getEnd())
517 ++num;
518 }
519
520 return num;
521}
522
523void AutomationCurve::mergeOtherCurve (const AutomationCurve& source,
524 TimeRange destRange,
525 TimePosition sourceStartTime,
526 TimeDuration fadeLength,
527 bool leaveOpenAtStart,
528 bool leaveOpenEnded)
529{
530 auto sourceEndTime = sourceStartTime + destRange.getLength();
531 auto dstValueAtStart = getValueAt (destRange.getStart());
532 auto dstValueAtEnd = getValueAt (destRange.getEnd());
533
534 auto srcValueAtStart = source.getValueAt (sourceStartTime);
535 auto srcValueAtEnd = source.getValueAt (sourceEndTime);
536
537 removePointsInRegion (destRange);
538
539 if (fadeLength == TimeDuration() && dstValueAtStart != srcValueAtStart)
540 addPoint (destRange.getStart(), dstValueAtStart, 0.0f);
541
542 if (! leaveOpenAtStart)
543 addPoint (destRange.getStart(), srcValueAtStart, 0.0f);
544
545 bool pointsInFadeZoneStart = false, pointsInFadeZoneEnd = false;
546
547 for (int i = 0; i < source.getNumPoints(); ++i)
548 {
549 auto t = source.getPointTime (i) + (destRange.getStart() - sourceStartTime);
550
551 if (t >= destRange.getStart() && t <= destRange.getEnd())
552 {
553 auto v = source.getPointValue(i);
554 auto c = source.getPointCurve(i);
555
556 // see if this point is in a fade zone..
557 if (t <= destRange.getStart() + fadeLength)
558 {
559 pointsInFadeZoneStart = true;
560
561 if (fadeLength > TimeDuration())
562 v = (float) (dstValueAtStart + (v - dstValueAtStart) * ((t - destRange.getStart()) / fadeLength));
563 }
564 else if (t >= destRange.getEnd() - fadeLength)
565 {
566 pointsInFadeZoneEnd = true;
567
568 if (fadeLength > TimeDuration())
569 v = (float) (v + (dstValueAtEnd - v) * 1.0 - ((destRange.getEnd() - t) / fadeLength));
570 }
571
572 addPoint (t, v, c);
573 }
574 }
575
576 if (fadeLength > TimeDuration() && ! pointsInFadeZoneStart)
577 addPoint (destRange.getStart() + fadeLength - TimeDuration::fromSeconds (0.0001), dstValueAtStart, 0.0f);
578
579 if (! leaveOpenEnded)
580 {
581 if (! pointsInFadeZoneEnd)
582 addPoint (destRange.getEnd() - fadeLength, srcValueAtEnd, 0.0f);
583
584 addPoint (destRange.getEnd(), dstValueAtEnd, 0.0f);
585 }
586}
587
588void AutomationCurve::simplify (TimeRange range, double minTimeDifference, float minValueDifference)
589{
590 auto minDist = std::sqrt (minTimeDifference * minTimeDifference
591 + minValueDifference * minValueDifference);
592
593 for (int i = 1; i < getNumPoints(); ++i)
594 {
595 auto time2 = getPointTime (i);
596
597 if (range.contains (time2))
598 {
599 auto time1 = getPointTime (i - 1);
600
601 // look for points too close together
602 if (std::abs ((time1 - time2).inSeconds()) < minTimeDifference
603 && std::abs (getPointValue (i - 1) - getPointValue (i)) < minValueDifference)
604 {
605 removePoint (i);
606 i = std::max (i - 3, 1);
607 }
608 else
609 {
610 // see if three points are in-line
611 if (i < getNumPoints() - 1)
612 {
613 double x = time2.inSeconds();
614 double y = getPointValue (i);
615
616 auto dist = getDistanceFromLine (x, y, time1.inSeconds(), getPointValue (i - 1),
617 getPointTime (i + 1).inSeconds(), getPointValue (i + 1));
618
619 if (dist < minDist)
620 {
621 removePoint (i);
622 i = std::max (i - 3, 1);
623 }
624 }
625 }
626 }
627 }
628}
629
630void AutomationCurve::addToAllTimes (TimeDuration delta)
631{
632 if (delta != TimeDuration())
633 for (int i = getNumPoints(); --i >= 0;)
634 setPointTime (i, getPointTime (i) + delta);
635}
636
637void AutomationCurve::rescaleAllTimes (double factor)
638{
639 if (factor != 1.0f)
640 for (int i = getNumPoints(); --i >= 0;)
641 setPointTime (i, TimePosition::fromSeconds (getPointTime (i).inSeconds() * factor));
642}
643
644void AutomationCurve::rescaleValues (float factor, TimeRange range)
645{
646 auto limits = getValueLimits();
647
648 if (factor != 1.0f)
649 for (int i = getNumPoints(); --i >= 0;)
650 if (range.contains (getPointTime (i)))
651 setPointValue (i, juce::jlimit (limits.getStart(), limits.getEnd(), getPointValue (i) * factor));
652}
653
654void AutomationCurve::addToValues (float valueDelta, TimeRange range)
655{
656 auto limits = getValueLimits();
657
658 if (valueDelta != 0)
659 for (int i = getNumPoints(); --i >= 0;)
660 if (range.contains (getPointTime (i)))
661 setPointValue (i, juce::jlimit (limits.getStart(), limits.getEnd(), getPointValue (i) + valueDelta));
662}
663
664CurvePoint AutomationCurve::getBezierPoint (int index) const noexcept
665{
666 auto x1 = getPointTime (index);
667 auto y1 = getPointValue (index);
668 auto x2 = getPointTime (index + 1);
669 auto y2 = getPointValue (index + 1);
670 auto c = juce::jlimit (-1.0f, 1.0f, getPointCurve (index) * 2.0f);
671
672 if (y2 > y1)
673 {
674 auto run = x2 - x1;
675 auto rise = y2 - y1;
676
677 auto xc = x1 + run / 2;
678 auto yc = y1 + rise / 2;
679
680 auto x = xc - run / 2 * -c;
681 auto y = yc + rise / 2 * -c;
682
683 return { x, y };
684 }
685
686 auto run = x2 - x1;
687 auto rise = y1 - y2;
688
689 auto xc = x1 + run / 2;
690 auto yc = y2 + rise / 2;
691
692 auto x = xc - run / 2 * -c;
693 auto y = yc - rise / 2 * -c;
694
695 return { x, y };
696}
697
698double AutomationCurve::getBezierXfromT (double t, double x1, double xb, double x2)
699{
700 // test for straight lines and bail out
701 if (x1 == x2)
702 return (x1 + x2) / 2.0 * t + x1;
703
704 return (std::pow (1.0 - t, 2.0) * x1) + 2 * t * (1 - t) * xb + std::pow (t, 2.0) * x2;
705}
706
707float AutomationCurve::getBezierYFromX (double x, double x1, float y1, double xb, float yb, double x2, float y2)
708{
709 // test for straight lines and bail out
710 if (x1 == x2 || y1 == y2)
711 return y1;
712
713 // ok, we have a bezier curve with one control point,
714 // we know x, we need to find y
715
716 // flip the bezier equation around so its an quadratic equation
717 auto a = x1 - 2 * xb + x2;
718 auto b = -2 * x1 + 2 * xb;
719 auto c = x1 - x;
720
721 // solve for t, [0..1]
722 double t;
723
724 if (a == 0.0f)
725 {
726 t = -c / b;
727 }
728 else
729 {
730 t = (-b + std::sqrt (b * b - 4 * a * c)) / (2 * a);
731
732 if (t < 0.0f || t > 1.0f)
733 t = (-b - std::sqrt (b * b - 4 * a * c)) / (2 * a);
734 }
735
736 // find y using the t we just found
737 return (float) ((std::pow (1 - t, 2) * y1) + 2 * t * (1 - t) * yb + std::pow (t, 2) * y2);
738}
739
740CurvePoint AutomationCurve::getBezierHandle (int index) const noexcept
741{
742 jassert (getOwnerParameter() != nullptr);
743
744 auto x1 = getPointTime (index).inSeconds();
745 auto y1 = getPointValue (index);
746 auto c = getPointCurve (index);
747
748 auto x2 = getPointTime (index + 1).inSeconds();
749 auto y2 = getPointValue (index + 1);
750
751 if (x1 == x2 || y1 == y2)
752 return { TimePosition::fromSeconds ((x1 + x2) / 2), (y1 + y2) / 2 };
753
754 if (c == 0.0f)
755 return { TimePosition::fromSeconds ((x1 + x2) / 2), (y1 + y2) / 2 };
756
757 if (c >= -0.5 && c <= 0.5)
758 {
759 auto bp = getBezierPoint (index);
760 auto x = getBezierXfromT (0.5, x1, toTime (bp.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), x2);
761 auto y = getBezierYFromX (x, x1, y1, toTime (bp.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), bp.value, x2, y2);
762
763 return { TimePosition::fromSeconds (x), y };
764 }
765
766 if (c > -1.0 && c < 1.0)
767 {
768 double x1end, x2end;
769 float y1end, y2end;
770 getBezierEnds (index, x1end, y1end, x2end, y2end);
771
772 auto bp = getBezierPoint (index);
773
774 auto x = getBezierXfromT (0.5, x1end, toTime (bp.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), x2end);
775 auto y = getBezierYFromX (x, x1end, y1end, toTime (bp.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), bp.value, x2end, y2end);
776 return { TimePosition::fromSeconds (x), y };
777 }
778
779 double x1end, x2end;
780 float y1end, y2end;
781 getBezierEnds (index, x1end, y1end, x2end, y2end);
782 return { TimePosition::fromSeconds (x1end), y1end };
783}
784
785void AutomationCurve::getBezierEnds (int index, double& x1out, float& y1out, double& x2out, float& y2out) const noexcept
786{
787 auto x1 = getPointTime (index).inSeconds();
788 auto y1 = getPointValue (index);
789 auto c = getPointCurve (index);
790
791 auto x2 = getPointTime (index + 1).inSeconds();
792 auto y2 = getPointValue (index + 1);
793
794 auto minic = (std::abs (c) - 0.5f) * 2.0f;
795 auto run = (minic) * (x2 - x1);
796 auto rise = (minic) * ((y2 > y1) ? (y2 - y1) : (y1 - y2));
797
798 if (c > 0.0f)
799 {
800 x1out = x1 + run;
801 y1out = y1;
802
803 x2out = x2;
804 y2out = (y1 < y2) ? (y2 - rise) : (y2 + rise);
805 }
806 else
807 {
808 x1out = x1;
809 y1out = (y1 < y2) ? (y1 + rise) : (y1 - rise);
810
811 x2out = x2 - run;
812 y2out = y2;
813 }
814}
815
816void AutomationCurve::removeAllAutomationCurvesRecursively (const juce::ValueTree& v)
817{
818 for (int i = v.getNumChildren(); --i >= 0;)
819 {
820 if (v.getChild (i).hasType (IDs::AUTOMATIONCURVE))
821 juce::ValueTree (v).removeChild (i, nullptr);
822 else
823 removeAllAutomationCurvesRecursively (v.getChild (i));
824 }
825}
826
827juce::Range<float> AutomationCurve::getValueLimits() const
828{
829 if (ownerParam != nullptr)
830 return ownerParam->getValueRange();
831
832 return { 0.0f, 1.0f };
833}
834
835//==============================================================================
836int simplify (AutomationCurve& curve, int strength, TimeRange time)
837{
839
840 double td = 0.08;
841 float vd = 0.06f;
842
843 if (strength == 0)
844 {
845 td = 0.03;
846 vd = 0.004f;
847 }
848 else if (strength == 1)
849 {
850 td = 0.05;
851 vd = 0.03f;
852 }
853
854 auto range = curve.getValueLimits().getLength();
855 vd *= range;
856
857 auto numPointsBefore = curve.getNumPoints();
858 curve.simplify (time, td, vd);
859 auto numPointsAfter = curve.getNumPoints();
860
861 return numPointsBefore - numPointsAfter;
862}
863
864}} // namespace tracktion { inline namespace engine
void add(const ElementType &newElement)
bool isValid() const noexcept
hypot
#define jassert(expression)
#define jassertfalse
typedef float
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndNotGreaterThan(Type1 valueToTest, Type2 upperLimit) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int simplify(AutomationCurve &curve, int strength, TimeRange time)
Removes points from the curve to simplfy it and returns the number of points removed.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
T pow(T... args)
T sqrt(T... args)
time
y1