11namespace tracktion {
inline namespace engine
14AutomationCurve::AutomationCurve()
15 : state (IDs::AUTOMATIONCURVE)
20 : parentState (p), state (v)
26AutomationCurve::AutomationCurve (
const AutomationCurve& o)
27 : parentState (o.parentState), state (o.state), ownerParam (o.ownerParam)
31AutomationCurve::~AutomationCurve()
38 jassert (state.hasType (IDs::AUTOMATIONCURVE));
39 jassert (state.getParent() == parentState);
47void AutomationCurve::setOwnerParameter (AutomatableParameter* p)
52 state.setProperty (IDs::paramID, p->paramID,
nullptr);
57 return ownerParam !=
nullptr ? &ownerParam->getEdit().getUndoManager() :
nullptr;
61int AutomationCurve::getNumPoints() const noexcept
63 return state.getNumChildren();
66AutomationCurve::AutomationPoint AutomationCurve::getPoint (
int index)
const noexcept
68 auto child = state.getChild (index);
70 if (! child.isValid() && ownerParam !=
nullptr)
71 return AutomationPoint ({}, ownerParam->getCurrentValue(), 0);
73 return AutomationPoint (TimePosition::fromSeconds (
static_cast<double> (child.getProperty (IDs::t))),
74 child.getProperty (IDs::v),
75 child.getProperty (IDs::c));
78TimePosition AutomationCurve::getPointTime (
int index)
const noexcept
80 return TimePosition::fromSeconds (
static_cast<double> (state.getChild (index).getProperty (IDs::t)));
83float AutomationCurve::getPointValue (
int index)
const noexcept
85 if (index >= getNumPoints() && ownerParam !=
nullptr)
86 return ownerParam->getCurrentBaseValue();
88 return state.getChild (index).getProperty (IDs::v);
91float AutomationCurve::getPointCurve (
int index)
const noexcept
93 return state.getChild (index).getProperty (IDs::c);
96int AutomationCurve::indexBefore (TimePosition t)
const
98 for (
int i = getNumPoints(); --i >= 0;)
99 if (getPointTime (i) <= t)
105int AutomationCurve::nextIndexAfter (TimePosition t)
const
107 auto num = getNumPoints();
109 for (
int i = 0; i < num; ++i)
110 if (getPointTime (i) >= t)
116TimeDuration AutomationCurve::getLength()
const
118 return toDuration (getPointTime (getNumPoints() - 1));
122float AutomationCurve::getValueAt (TimePosition timePos)
const
124 TRACKTION_ASSERT_MESSAGE_THREAD
125 jassert (getOwnerParameter() !=
nullptr);
127 const auto index = nextIndexAfter (timePos);
128 const auto time = timePos.inSeconds();
131 return getPointValue (0);
133 auto numPoints = getNumPoints();
135 if (index >= numPoints)
136 return getPointValue (numPoints - 1);
138 auto p1 = state.getChild (index - 1);
139 auto p2 = state.getChild (index);
141 const double time1 = p1.getProperty (IDs::t);
142 const float curve1 = p1.getProperty (IDs::c);
143 const float value1 = p1.getProperty (IDs::v);
145 const double time2 = p2.getProperty (IDs::t);
146 const float value2 = p2.getProperty (IDs::v);
150 auto alpha = (
float) ((time - time1) / (time2 - time1));
151 return value1 + alpha * (value2 - value1);
154 if (curve1 >= -0.5f && curve1 <= 0.5f)
156 auto bezierPoint = getBezierPoint (index - 1);
157 return getBezierYFromX (time, time1, value1, toTime (bezierPoint.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), bezierPoint.value, time2, value2);
162 getBezierEnds (index - 1, x1, y1, x2, y2);
164 if (time >= time1 && time <= x1)
167 if (time >= x2 && time <= time2)
170 auto bezierPoint = getBezierPoint (index - 1);
171 return getBezierYFromX (time, x1, y1, toTime (bezierPoint.time, getOwnerParameter()->getEdit().tempoSequence).inSeconds(), bezierPoint.value, x2, y2);
174static double getDistanceFromLine (
double& x,
double& y,
175 double x1,
double y1,
176 double x2,
double y2)
180 auto length =
hypot (dx, dy);
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);
185 auto nearX = x1 + (x2 - x1) * alongLine;
186 auto nearY =
y1 + (y2 -
y1) * alongLine;
199int AutomationCurve::getNearestPoint (TimePosition& t,
float& v,
double xToYRatio)
const
201 auto numPoints = getNumPoints();
205 if (t <= getPointTime (0))
207 v = getPointValue (0);
211 if (t >= getPointTime (numPoints - 1))
213 v = getPointValue (numPoints - 1);
217 double bestDist = 1e10;
219 double bestValue = v;
222 for (
int i = 0; i < numPoints - 1; ++i)
224 auto p1 = state.getChild (i);
225 auto p2 = state.getChild (i + 1);
227 const auto time1 = TimePosition::fromSeconds (
static_cast<double> (p1.getProperty (IDs::t)));
228 const float value1 = p1.getProperty (IDs::v);
230 const auto time2 = TimePosition::fromSeconds (
static_cast<double> (p2.getProperty (IDs::t)));
231 const float value2 = p2.getProperty (IDs::v);
233 auto x = t.inSeconds();
234 auto y = xToYRatio * v;
236 auto dist = getDistanceFromLine (x, y,
237 time1.inSeconds(), xToYRatio * value1,
238 time2.inSeconds(), xToYRatio * value2);
244 bestTime = TimePosition::fromSeconds (x);
251 v = (
float) bestValue;
258 v = getPointValue (0);
259 return t > getPointTime (0) ? 1 : 0;
262 if (ownerParam !=
nullptr)
263 v = ownerParam->getCurrentValue();
273 return createValueTree (IDs::POINT,
274 IDs::t,
time.inSeconds(),
279int AutomationCurve::addPoint (TimePosition time,
float value,
float curve)
282 for (i = getNumPoints(); --i >= 0;)
283 if (getPointTime (i) <= time)
286 addPointAtIndex (++i, time, value, curve);
290void AutomationCurve::addPointAtIndex (
int index, TimePosition time,
float value,
float curve)
292 state.addChild (AutomationPoint (time, value, curve).toValueTree(), index, getUndoManager());
293 checkParenthoodStatus();
296void AutomationCurve::removePoint (
int index)
298 state.removeChild (index, getUndoManager());
299 checkParenthoodStatus();
302void AutomationCurve::checkParenthoodStatus()
304 bool hasParent = state.getParent() == parentState;
305 bool needsParent = getNumPoints() > 0;
309 if (needsParent != hasParent)
312 parentState.addChild (state, -1, getUndoManager());
314 parentState.removeChild (state, getUndoManager());
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()); }
322int AutomationCurve::movePoint (
int index, TimePosition newTime,
float newValue,
bool removeInterveningPoints)
326 if (removeInterveningPoints)
328 auto oldTime = getPointTime (index);
329 const bool movingPointBack = newTime < oldTime;
331 auto t1 =
std::min (newTime, oldTime) - TimeDuration::fromSeconds (0.00001);
332 auto t2 =
std::max (newTime, oldTime) + TimeDuration::fromSeconds (0.00001);
334 for (
int i = getNumPoints(); --i >= 0;)
336 auto t = getPointTime (i);
356 if (t < t2 && i != index)
367 newTime =
std::max (getPointTime (index - 1), newTime);
369 newTime =
std::max (TimePosition(), newTime);
371 if (index < getNumPoints() - 1)
372 newTime =
std::min (newTime, getPointTime (index + 1));
374 if (ownerParam !=
nullptr)
375 newValue = ownerParam->getValueRange().clipValue (ownerParam->snapToState (newValue));
377 auto v = state.getChild (index);
379 v.setProperty (IDs::t, newTime.inSeconds(), getUndoManager());
380 v.setProperty (IDs::v, newValue, getUndoManager());
391void AutomationCurve::clear()
393 state.removeAllChildren (getUndoManager());
399 auto numPoints = getNumPoints();
401 for (
int i = 0; i < numPoints; ++i)
403 auto v = state.getChild (i);
404 auto t = TimePosition::fromSeconds (
static_cast<double> (v.getProperty (IDs::t)));
406 if (range.contains (t))
407 results.
add (AutomationPoint (t, v.getProperty (IDs::v), v.getProperty (IDs::c)));
413void AutomationCurve::removePointsInRegion (TimeRange range)
415 for (
int i = getNumPoints(); --i >= 0;)
417 auto t = getPointTime (i);
419 if (t < range.getStart())
422 if (t < range.getEnd())
427void AutomationCurve::removeRedundantPoints (TimeRange range)
429 constexpr auto threshold = 0.0001;
431 for (
int i = getNumPoints(); --i >= 0;)
433 auto t = getPointTime (i);
435 if (! range.contains (t))
438 auto v = getPointValue (i);
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;
444 if (sameLeft && sameRight)
451 if (i < getNumPoints() - 1
452 && std::abs (getPointValue (i + 1) - v) < threshold
453 && std::abs ((getPointTime (i + 1) - t).inSeconds()) < threshold)
461 && i < getNumPoints() - 1
462 && std::abs ((getPointTime (i - 1) - t).inSeconds()) < threshold
463 && std::abs ((getPointTime (i + 1) - t).inSeconds()) < threshold)
469 if (getNumPoints() <= 1)
474void AutomationCurve::removeRegionAndCloseGap (TimeRange range)
476 auto valAtStart = getValueAt (range.getStart());
477 auto valAtEnd = getValueAt (range.getEnd());
479 if (getNumPoints() == 0)
482 for (
int i = getNumPoints(); --i >= 0;)
484 auto t = getPointTime (i);
486 if (t >= range.getStart() && t <= range.getEnd())
490 for (
int i = 0; i < getNumPoints(); ++i)
492 auto t = getPointTime (i);
494 if (t >= range.getEnd())
495 movePoint (i, t - range.getLength(), getPointValue(i),
false);
498 if (getValueAt (range.getStart()) != valAtStart)
499 addPoint (range.getStart(), valAtStart, 0.0f);
501 if (valAtStart != valAtEnd)
502 addPoint (range.getStart(), valAtEnd, 0.0f);
505int AutomationCurve::countPointsInRegion (TimeRange range)
const
509 for (
int i = getNumPoints(); --i >= 0;)
511 auto t = getPointTime(i);
513 if (t < range.getStart())
516 if (t < range.getEnd())
523void AutomationCurve::mergeOtherCurve (
const AutomationCurve& source,
525 TimePosition sourceStartTime,
526 TimeDuration fadeLength,
527 bool leaveOpenAtStart,
530 auto sourceEndTime = sourceStartTime + destRange.getLength();
531 auto dstValueAtStart = getValueAt (destRange.getStart());
532 auto dstValueAtEnd = getValueAt (destRange.getEnd());
534 auto srcValueAtStart = source.getValueAt (sourceStartTime);
535 auto srcValueAtEnd = source.getValueAt (sourceEndTime);
537 removePointsInRegion (destRange);
539 if (fadeLength == TimeDuration() && dstValueAtStart != srcValueAtStart)
540 addPoint (destRange.getStart(), dstValueAtStart, 0.0f);
542 if (! leaveOpenAtStart)
543 addPoint (destRange.getStart(), srcValueAtStart, 0.0f);
545 bool pointsInFadeZoneStart =
false, pointsInFadeZoneEnd =
false;
547 for (
int i = 0; i < source.getNumPoints(); ++i)
549 auto t = source.getPointTime (i) + (destRange.getStart() - sourceStartTime);
551 if (t >= destRange.getStart() && t <= destRange.getEnd())
553 auto v = source.getPointValue(i);
554 auto c = source.getPointCurve(i);
557 if (t <= destRange.getStart() + fadeLength)
559 pointsInFadeZoneStart =
true;
561 if (fadeLength > TimeDuration())
562 v = (
float) (dstValueAtStart + (v - dstValueAtStart) * ((t - destRange.getStart()) / fadeLength));
564 else if (t >= destRange.getEnd() - fadeLength)
566 pointsInFadeZoneEnd =
true;
568 if (fadeLength > TimeDuration())
569 v = (
float) (v + (dstValueAtEnd - v) * 1.0 - ((destRange.getEnd() - t) / fadeLength));
576 if (fadeLength > TimeDuration() && ! pointsInFadeZoneStart)
577 addPoint (destRange.getStart() + fadeLength - TimeDuration::fromSeconds (0.0001), dstValueAtStart, 0.0f);
579 if (! leaveOpenEnded)
581 if (! pointsInFadeZoneEnd)
582 addPoint (destRange.getEnd() - fadeLength, srcValueAtEnd, 0.0f);
584 addPoint (destRange.getEnd(), dstValueAtEnd, 0.0f);
588void AutomationCurve::simplify (TimeRange range,
double minTimeDifference,
float minValueDifference)
590 auto minDist =
std::sqrt (minTimeDifference * minTimeDifference
591 + minValueDifference * minValueDifference);
593 for (
int i = 1; i < getNumPoints(); ++i)
595 auto time2 = getPointTime (i);
597 if (range.contains (time2))
599 auto time1 = getPointTime (i - 1);
602 if (std::abs ((time1 - time2).inSeconds()) < minTimeDifference
603 && std::abs (getPointValue (i - 1) - getPointValue (i)) < minValueDifference)
611 if (i < getNumPoints() - 1)
613 double x = time2.inSeconds();
614 double y = getPointValue (i);
616 auto dist = getDistanceFromLine (x, y, time1.inSeconds(), getPointValue (i - 1),
617 getPointTime (i + 1).inSeconds(), getPointValue (i + 1));
630void AutomationCurve::addToAllTimes (TimeDuration delta)
632 if (delta != TimeDuration())
633 for (
int i = getNumPoints(); --i >= 0;)
634 setPointTime (i, getPointTime (i) + delta);
637void AutomationCurve::rescaleAllTimes (
double factor)
640 for (
int i = getNumPoints(); --i >= 0;)
641 setPointTime (i, TimePosition::fromSeconds (getPointTime (i).inSeconds() * factor));
644void AutomationCurve::rescaleValues (
float factor, TimeRange range)
646 auto limits = getValueLimits();
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));
654void AutomationCurve::addToValues (
float valueDelta, TimeRange range)
656 auto limits = getValueLimits();
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));
664CurvePoint AutomationCurve::getBezierPoint (
int index)
const noexcept
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);
677 auto xc = x1 + run / 2;
678 auto yc =
y1 + rise / 2;
680 auto x = xc - run / 2 * -c;
681 auto y = yc + rise / 2 * -c;
689 auto xc = x1 + run / 2;
690 auto yc = y2 + rise / 2;
692 auto x = xc - run / 2 * -c;
693 auto y = yc - rise / 2 * -c;
698double AutomationCurve::getBezierXfromT (
double t,
double x1,
double xb,
double x2)
702 return (x1 + x2) / 2.0 * t + x1;
704 return (
std::pow (1.0 - t, 2.0) * x1) + 2 * t * (1 - t) * xb +
std::pow (t, 2.0) * x2;
707float AutomationCurve::getBezierYFromX (
double x,
double x1,
float y1,
double xb,
float yb,
double x2,
float y2)
710 if (x1 == x2 || y1 == y2)
717 auto a = x1 - 2 * xb + x2;
718 auto b = -2 * x1 + 2 * xb;
730 t = (-b +
std::sqrt (b * b - 4 * a * c)) / (2 * a);
732 if (t < 0.0f || t > 1.0f)
733 t = (-b -
std::sqrt (b * b - 4 * a * c)) / (2 * a);
737 return (
float) ((
std::pow (1 - t, 2) *
y1) + 2 * t * (1 - t) * yb +
std::pow (t, 2) * y2);
740CurvePoint AutomationCurve::getBezierHandle (
int index)
const noexcept
742 jassert (getOwnerParameter() !=
nullptr);
744 auto x1 = getPointTime (index).inSeconds();
745 auto y1 = getPointValue (index);
746 auto c = getPointCurve (index);
748 auto x2 = getPointTime (index + 1).inSeconds();
749 auto y2 = getPointValue (index + 1);
751 if (x1 == x2 || y1 == y2)
752 return { TimePosition::fromSeconds ((x1 + x2) / 2), (
y1 + y2) / 2 };
755 return { TimePosition::fromSeconds ((x1 + x2) / 2), (
y1 + y2) / 2 };
757 if (c >= -0.5 && c <= 0.5)
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);
763 return { TimePosition::fromSeconds (x), y };
766 if (c > -1.0 && c < 1.0)
770 getBezierEnds (index, x1end, y1end, x2end, y2end);
772 auto bp = getBezierPoint (index);
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 };
781 getBezierEnds (index, x1end, y1end, x2end, y2end);
782 return { TimePosition::fromSeconds (x1end), y1end };
785void AutomationCurve::getBezierEnds (
int index,
double& x1out,
float& y1out,
double& x2out,
float& y2out)
const noexcept
787 auto x1 = getPointTime (index).inSeconds();
788 auto y1 = getPointValue (index);
789 auto c = getPointCurve (index);
791 auto x2 = getPointTime (index + 1).inSeconds();
792 auto y2 = getPointValue (index + 1);
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));
804 y2out = (
y1 < y2) ? (y2 - rise) : (y2 + rise);
809 y1out = (
y1 < y2) ? (y1 + rise) : (
y1 - rise);
816void AutomationCurve::removeAllAutomationCurvesRecursively (
const juce::ValueTree& v)
818 for (
int i = v.getNumChildren(); --i >= 0;)
820 if (v.getChild (i).hasType (IDs::AUTOMATIONCURVE))
823 removeAllAutomationCurvesRecursively (v.getChild (i));
829 if (ownerParam !=
nullptr)
830 return ownerParam->getValueRange();
832 return { 0.0f, 1.0f };
848 else if (strength == 1)
854 auto range = curve.getValueLimits().getLength();
857 auto numPointsBefore = curve.getNumPoints();
858 curve.simplify (
time, td, vd);
859 auto numPointsAfter = curve.getNumPoints();
861 return numPointsBefore - numPointsAfter;
void add(const ElementType &newElement)
bool isValid() const noexcept
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.