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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_AutomatableParameter.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
14namespace AutomationScaleHelpers
15{
16 inline float getQuadraticBezierControlPoint (float y1, float y2, float curve) noexcept
17 {
18 jassert (curve >= -0.5f && curve <= 0.5f);
19
20 auto c = juce::jlimit (-1.0f, 1.0f, curve * 2.0f);
21
22 if (y2 > y1)
23 {
24 auto rise = y2 - y1;
25 auto yc = y1 + rise / 2;
26 auto y = yc + rise / 2 * -c;
27
28 return y;
29 }
30
31 auto rise = y1 - y2;
32 auto yc = y2 + rise / 2;
33 auto y = yc - rise / 2 * -c;
34
35 return y;
36 }
37
38 inline float getCurvedValue (float value, float start, float end, float curve) noexcept
39 {
40 if (curve == 0.0f)
41 return ((end - start) * value) + start;
42
43 auto control = getQuadraticBezierControlPoint (start, end, curve);
44 return (float) AutomationCurve::getBezierXfromT (value, start, control, end);
45 }
46
47 inline float mapValue (float inputVal, float offset, float value, float curve) noexcept
48 {
49 return inputVal < 0.0 ? offset - getCurvedValue (-inputVal, 0.0f, value, curve)
50 : offset + getCurvedValue (inputVal, 0.0f, value, curve);
51 }
52
54 inline float remapInputValue (float inputVal, juce::Range<float> inputRange)
55 {
57 auto remappedValue = juce::jmap (inputRange.clipValue (inputVal),
58 inputRange.getStart(), inputRange.getEnd(),
59 0.0f, 1.0f);
60 jassert (juce::isPositiveAndNotGreaterThan (remappedValue, 1.0f));
61 return remappedValue;
62 }
63}
64
65//==============================================================================
66AutomatableParameter::ModifierSource::~ModifierSource()
67{
68 masterReference.clear();
69}
70
71//==============================================================================
73{
74 AutomationSource (const juce::ValueTree& v) : state (v) {}
75 virtual ~AutomationSource() = default;
76
81 virtual float getValueAt (TimePosition) = 0;
82
87 virtual bool isEnabledAt (TimePosition) = 0;
88
92 virtual void setPosition (TimePosition) = 0;
93
95 virtual bool isEnabled() = 0;
96
98 virtual float getCurrentValue() = 0;
99
100 juce::ValueTree state;
101};
102
103//==============================================================================
105{
107 : AutomationSource (ass->state),
108 assignment (std::move (ass))
109 {
110 }
111
112 virtual AutomatableParameter::ModifierSource* getModifierSource() = 0;
113
115};
116
117//==============================================================================
119{
120 ModifierAutomationSource (Modifier::Ptr mod, const juce::ValueTree& assignmentState)
121 : AutomationModifierSource (mod->createAssignment (assignmentState)),
122 modifier (std::move (mod))
123 {
124 jassert (state.hasProperty (IDs::source));
125 }
126
127 AutomatableParameter::ModifierSource* getModifierSource() override
128 {
129 return modifier.get();
130 }
131
132 // Modifiers will be updated at the start of each block so can't be repositioned
133 float getValueAt (TimePosition) override { return getCurrentValue(); }
134 bool isEnabledAt (TimePosition) override { return true; }
135
136 void setPosition (TimePosition newEditTime) override
137 {
138 editTimeToReturn = newEditTime;
139 }
140
141 bool isEnabled() override
142 {
143 return getBoolParamValue (*modifier->enabledParam);
144 }
145
146 float getCurrentValue() override
147 {
148 float baseValue = modifier->getCurrentValue();
149
150 const auto currentTime = modifier->getCurrentTime();
151 const auto deltaTime = currentTime - editTimeToReturn;
152
153 if (deltaTime > 0s && deltaTime < Modifier::maxHistoryTime)
154 baseValue = modifier->getValueAt (deltaTime);
155
156 return AutomationScaleHelpers::mapValue (baseValue, assignment->offset, assignment->value, assignment->curve);
157 }
158
159 const Modifier::Ptr modifier;
160 TimePosition editTimeToReturn;
161
163};
164
165//==============================================================================
167{
168public:
170 : AutomationSource (getState (ap)),
171 parameter (ap),
172 curve (ap.parentState, state)
173 {
174 deferredUpdateTimer.setCallback ([this]
175 {
176 deferredUpdateTimer.stopTimer();
177 updateInterpolatedPoints();
178 });
179
180 curve.setOwnerParameter (&ap);
181 }
182
183 void triggerAsyncCurveUpdate()
184 {
185 if (! parameter.getEdit().isLoading())
186 deferredUpdateTimer.startTimer (10);
187 }
188
189 void updateInterpolatedPoints()
190 {
191 jassert (! parameter.getEdit().isLoading());
193 TRACKTION_ASSERT_MESSAGE_THREAD
194
196
197 if (curve.getNumPoints() > 0)
198 {
199 auto s = std::make_unique<AutomationIterator> (parameter);
200
201 if (! s->isEmpty())
202 newStream = std::move (s);
203 }
204
205 {
206 const juce::ScopedLock sl (parameterStreamLock);
207 automationActive.store (newStream != nullptr, std::memory_order_relaxed);
208 parameterStream = std::move (newStream);
209
210 if (! parameterStream)
211 parameter.updateToFollowCurve (lastTime);
212
213 lastTime = -1.0s;
214 }
215
216 parameter.automatableEditElement.updateActiveParameters();
217 }
218
219 bool isActive() const noexcept
220 {
221 return automationActive.load (std::memory_order_relaxed);
222 }
223
224 float getValueAt (TimePosition time) override
225 {
226 TRACKTION_ASSERT_MESSAGE_THREAD
227 return curve.getValueAt (time);
228 }
229
230 bool isEnabledAt (TimePosition) override
231 {
232 return true;
233 }
234
235 void setPosition (TimePosition time) override
236 {
237 if (! parameter.getEdit().getAutomationRecordManager().isReadingAutomation())
238 if (auto plugin = parameter.getPlugin())
239 if (! plugin->isClipEffectPlugin())
240 return;
241
242 const juce::ScopedLock sl (parameterStreamLock);
243
244 if (lastTime.exchange (time) != time)
245 parameterStream->setPosition (time);
246 }
247
248 bool isEnabled() override
249 {
250 return true;
251 }
252
253 float getCurrentValue() override
254 {
255 const juce::ScopedLock sl (parameterStreamLock);
256 return parameterStream->getCurrentValue();
257 }
258
259 AutomatableParameter& parameter;
260 AutomationCurve curve;
261
262private:
263 LambdaTimer deferredUpdateTimer;
264 juce::CriticalSection parameterStreamLock;
266 std::atomic<bool> automationActive { false };
267 std::atomic<TimePosition> lastTime { TimePosition::fromSeconds (-1.0) };
268
269 static juce::ValueTree getState (AutomatableParameter& ap)
270 {
271 auto v = ap.parentState.getChildWithProperty (IDs::paramID, ap.paramID);
272
273 if (! v.isValid())
274 {
275 // ExternalAutomatableParameter used to use the parameter name as the property value
276 auto oldAutomation = ap.parentState.getChildWithProperty (IDs::name, ap.paramName);
277
278 if (oldAutomation.isValid())
279 return oldAutomation;
280
281 // Internal plugins have always used the paramID as the value
282 return ap.parentState.getChildWithProperty (IDs::name, ap.paramID);
283 }
284
285 return v;
286 }
287
289};
290
291//==============================================================================
293{
294 MacroSource (MacroParameter::Assignment::Ptr macroAssignment, MacroParameter& macroParameter)
295 : AutomationModifierSource (macroAssignment),
296 macro (&macroParameter)
297 {
298 jassert (state.hasType (IDs::MACRO) && state.hasProperty (IDs::source));
299 }
300
301 AutomatableParameter::ModifierSource* getModifierSource() override
302 {
303 return macro.get();
304 }
305
306 float getValueAt (TimePosition time) override
307 {
308 TRACKTION_ASSERT_MESSAGE_THREAD
309 auto macroValue = macro->getCurve().getValueAt (time);
310 const auto range = juce::Range<float>::between (assignment->inputStart.get(), assignment->inputEnd.get());
311 return AutomationScaleHelpers::mapValue (AutomationScaleHelpers::remapInputValue (macroValue, range),
312 assignment->offset, assignment->value, assignment->curve);
313 }
314
315 bool isEnabledAt (TimePosition) override
316 {
317 return true;
318 }
319
320 void setPosition (TimePosition time) override
321 {
322 const juce::ScopedLock sl (streamPositionLock);
323 macro->updateFromAutomationSources (time);
324 auto macroValue = macro->getCurrentValue();
325
326 const auto range = juce::Range<float>::between (assignment->inputStart.get(), assignment->inputEnd.get());
327 currentValue.store (AutomationScaleHelpers::mapValue (AutomationScaleHelpers::remapInputValue (macroValue, range),
328 assignment->offset, assignment->value, assignment->curve),
330 }
331
332 bool isEnabled() override
333 {
334 return true;
335 }
336
337 float getCurrentValue() override
338 {
339 return currentValue.load (std::memory_order_acquire);
340 }
341
342 const MacroParameter::Ptr macro;
343
344private:
345 juce::CriticalSection streamPositionLock;
346 std::atomic<float> currentValue { 0.0f };
347
349};
350
351//==============================================================================
352struct AutomatableParameter::AutomationSourceList : private ValueTreeObjectList<AutomationModifierSource, juce::CriticalSection>
353{
356 parameter (ap)
357 {
358 jassert (! ap.getEdit().isLoading()); // This can't be created before the Edit has loaded
359 // or it won't be able to find the sources
360 rebuildObjects();
361 updateCachedSources();
362
363 if (isActive())
364 parameter.curveSource->triggerAsyncCurveUpdate();
365 }
366
367 ~AutomationSourceList() override
368 {
369 freeObjects();
370 }
371
372 bool isActive() const
373 {
374 auto num = numSources.load (std::memory_order_acquire);
375 jassert (num == objects.size());
376 return num > 0;
377 }
378
379 template<typename Fn>
380 void visitSources (Fn&& f)
381 {
382 if (auto cs = cachedSources)
383 for (auto* as : cs->sources)
384 f (*as);
385 }
386
388 {
389 TRACKTION_ASSERT_MESSAGE_THREAD
390
391 for (auto o : objects)
392 if (o->assignment.get() == &ass)
393 return o;
394
395 return {};
396 }
397
399 {
400 TRACKTION_ASSERT_MESSAGE_THREAD
401
402 for (auto o : objects)
403 if (o->assignment->isForModifierSource (mod))
404 return o;
405
406 return {};
407 }
408
409private:
410 const AutomatableParameter& parameter;
411 std::atomic<int> numSources { 0 };
412
413 // This caching mechanism is to avoid locking on the audio thread and keeps a reference
414 // counted copy of the objects for the visit method to use in a lock free way
415 struct CachedSources : public ReferenceCountedObject
416 {
418 };
419
421
422 void updateCachedSources()
423 {
424 if (objects.isEmpty())
425 {
426 cachedSources.reset();
427 }
428 else
429 {
430 auto cs = new CachedSources();
431
432 for (auto o : objects)
433 cs->sources.add (o);
434
435 cachedSources = cs;
436 }
437 }
438
439 bool isSuitableType (const juce::ValueTree& v) const override
440 {
441 if (v.hasType (IDs::LFO) || v.hasType (IDs::BREAKPOINTOSCILLATOR) || v.hasType (IDs::MACRO)
442 || v.hasType (IDs::STEP) || v.hasType (IDs::ENVELOPEFOLLOWER) || v.hasType (IDs::RANDOM)
443 || v.hasType (IDs::MIDITRACKER))
444 {
445 // Old LFOs will have a paramID field that is a name. We'll convert it when we create the ModifierAutomationSource
446 const auto isLegacyLFO = [&, this] { return v.hasType (IDs::LFO) && v[IDs::paramID].toString() == parameter.paramName; };
447
448 if ((v[IDs::paramID] == parameter.paramID || isLegacyLFO())
449 && EditItemID::fromProperty (v, IDs::source).isValid())
450 {
451 return true;
452 }
453 }
454
455 return false;
456 }
457
458 AutomationModifierSource* createNewObject (const juce::ValueTree& v) override
459 {
461
462 // Convert old LFO name to ID
463 if (v.hasType (IDs::LFO) && v[IDs::paramID].toString() == parameter.paramName)
464 juce::ValueTree (v).setProperty (IDs::paramID, parameter.paramID, nullptr);
465
466 auto getMacroForID = [this] (const juce::String& id) -> MacroParameter*
467 {
468 for (auto mpl : getAllMacroParameterLists (parameter.getEdit()))
469 for (auto mp : mpl->getMacroParameters())
470 if (mp->paramID == id)
471 return mp;
472
473 return {};
474 };
475
476 if (auto mod = findModifierForID (parameter.getEdit(), EditItemID::fromProperty (v, IDs::source)))
477 {
478 if (v.isAChildOf (mod->state))
479 return nullptr;
480
481 as = new ModifierAutomationSource (mod, v);
482 }
483 else if (auto macro = getMacroForID (v[IDs::source].toString()))
484 {
485 if (v.isAChildOf (macro->state))
486 return nullptr;
487
488 as = new MacroSource (new MacroParameter::Assignment (v, *macro), *macro);
489 }
490 else
491 {
492 return nullptr;
493 }
494
495 as->incReferenceCount();
496 ++numSources;
497
498 return as.get();
499 }
500
501 void deleteObject (AutomationModifierSource* as) override
502 {
503 --numSources;
504 as->decReferenceCount();
505 }
506
507 void newObjectAdded (AutomationModifierSource* as) override { objectAddedOrRemoved (as); }
508 void objectRemoved (AutomationModifierSource* as) override { objectAddedOrRemoved (as); }
509 void objectOrderChanged() override { objectAddedOrRemoved (nullptr); }
510
511 void objectAddedOrRemoved (AutomationModifierSource* as)
512 {
513 updateCachedSources();
514
515 auto notifySource = [] (AutomationModifierSource* source)
516 {
517 if (auto mas = dynamic_cast<ModifierAutomationSource*> (source))
518 mas->modifier->changed();
519 else if (auto macro = dynamic_cast<MacroSource*> (source))
520 macro->macro->changed();
521 };
522
523 for (auto s : objects)
524 if (as == nullptr || s != as)
525 notifySource (s);
526
527 notifySource (as);
528
529 parameter.curveSource->triggerAsyncCurveUpdate();
530 }
531};
532
533//==============================================================================
535{
537 : parameter (p) {}
538
539 virtual ~AttachedValue() { cancelPendingUpdate(); }
540
541 virtual void setValue (float v) = 0;
542 virtual float getValue() = 0;
543 virtual float getDefault() = 0;
544 virtual void detach (juce::ValueTree::Listener*) = 0;
545 virtual bool updateIfMatches (juce::ValueTree&, const juce::Identifier&) = 0;
546 virtual void updateParameterFromValue() = 0;
547
548 AutomatableParameter& parameter;
549};
550
552{
554 : AttachedValue (p), value (v)
555 {
556 parameter.setParameter (value, juce::dontSendNotification);
557 }
558
559 void handleAsyncUpdate() override { value.setValue (parameter.currentValue, nullptr); }
560 float getValue() override { return value; }
561 void setValue (float v) override { value = v; }
562 float getDefault() override { return value.getDefault(); }
563 void detach (juce::ValueTree::Listener* l) override { value.getValueTree().removeListener (l); }
564
565 bool updateIfMatches (juce::ValueTree& v, const juce::Identifier& i) override
566 {
567 if (i == value.getPropertyID() && v == value.getValueTree())
568 {
569 value.forceUpdateOfCachedValue();
570 return true;
571 }
572 return false;
573 }
574
575 void updateParameterFromValue() override
576 {
577 parameter.setParameter (value, juce::dontSendNotification);
578 }
579
581};
582
584{
586 : AttachedValue (p), value (v)
587 {
588 parameter.setParameter ((float) value.get(), juce::dontSendNotification);
589 }
590
591 void handleAsyncUpdate() override { value.setValue (juce::roundToInt (parameter.getCurrentValue()), nullptr); }
592 float getValue() override { return (float) value.get(); }
593 void setValue (float v) override { value = juce::roundToInt (v); }
594 float getDefault() override { return (float) value.getDefault(); }
595 void detach (juce::ValueTree::Listener* l) override { value.getValueTree().removeListener (l); }
596
597 bool updateIfMatches (juce::ValueTree& v, const juce::Identifier& i) override
598 {
599 if (i == value.getPropertyID() && v == value.getValueTree())
600 {
601 value.forceUpdateOfCachedValue();
602 return true;
603 }
604 return false;
605 }
606
607 void updateParameterFromValue() override
608 {
609 parameter.setParameter ((float) value.get(), juce::dontSendNotification);
610 }
611
613};
614
616{
618 : AttachedValue (p), value (v)
619 {
620 parameter.setParameter (value.get() ? 1.0f : 0.0f, juce::dontSendNotification);
621 }
622
623 void handleAsyncUpdate() override { value.setValue (parameter.currentValue != 0.0f, nullptr); }
624 float getValue() override { return value; }
625 void setValue (float v) override { value = v != 0 ? true : false; }
626 float getDefault() override { return value.getDefault() ? 1.0f : 0.0f; }
627 void detach (juce::ValueTree::Listener* l) override { value.getValueTree().removeListener (l); }
628
629 bool updateIfMatches (juce::ValueTree& v, const juce::Identifier& i) override
630 {
631 if (i == value.getPropertyID() && v == value.getValueTree())
632 {
633 value.forceUpdateOfCachedValue();
634 return true;
635 }
636 return false;
637 }
638
639 void updateParameterFromValue() override
640 {
641 parameter.setParameter (value.get() ? 1.0f : 0.0f, juce::dontSendNotification);
642 }
643
645};
646
647
648//==============================================================================
649AutomatableParameter::AutomatableParameter (const juce::String& paramID_,
650 const juce::String& name_,
651 AutomatableEditItem& owner,
653 : paramID (paramID_),
654 valueRange (vr),
655 automatableEditElement (owner),
656 paramName (name_),
657 editRef (&automatableEditElement.edit)
658{
659 if (auto p = dynamic_cast<Plugin*> (&owner))
660 {
661 plugin = p;
662 parentState = plugin->state;
663 }
664 else if (auto m = dynamic_cast<Modifier*> (&owner))
665 {
666 modifierOwner = m;
667 parentState = modifierOwner->state;
668 }
669 else if (auto* mpl = dynamic_cast<MacroParameterList*> (&owner))
670 {
671 macroOwner = mpl;
672 parentState = mpl->state;
673 }
674 else
675 {
676 jassertfalse; // Unknown AutomatableEditItem type
677 }
678
679 modifiersState = parentState.getOrCreateChildWithName (IDs::MODIFIERASSIGNMENTS, &owner.edit.getUndoManager());
680 curveSource = std::make_unique<AutomationCurveSource> (*this);
681
682 valueToStringFunction = [] (float value) { return juce::String (value, 3); };
683 stringToValueFunction = [] (const juce::String& s) { return s.getFloatValue(); };
684
685 parentState.addListener (this);
686}
687
688AutomatableParameter::~AutomatableParameter()
689{
690 if (auto edit = editRef.get())
691 edit->getAutomationRecordManager().parameterBeingDeleted (*this);
692
693 notifyListenersOfDeletion();
694
695 automationSourceList.reset();
696
697 if (attachedValue != nullptr)
698 attachedValue->detach (this);
699}
700
701AutomatableParameter::ModifierAssignment::ModifierAssignment (Edit& e, const juce::ValueTree& v)
702 : edit (e), state (v)
703{
704 auto* um = &edit.getUndoManager();
705 offset.referTo (state, IDs::offset, um);
706 value.referTo (state, IDs::value, um);
707 curve.referTo (state, IDs::curve, um);
708
709 inputStart.referTo (state, IDs::start, um, 0.0f);
710 inputEnd.referTo (state, IDs::end, um, 1.0f);
711}
712
714{
715 if (auto existing = getAutomationSourceList().getSourceFor (source))
716 return existing->assignment;
717
719
720 if (auto mod = dynamic_cast<Modifier*> (&source))
721 {
722 if (mod == &automatableEditElement)
723 return {};
724
725 v = createValueTree (mod->state.getType(),
726 IDs::source, mod->itemID);
727 }
728 else if (auto macro = dynamic_cast<MacroParameter*> (&source))
729 {
730 v = createValueTree (IDs::MACRO,
731 IDs::source, macro->paramID);
732 }
733 else
734 {
736 return {};
737 }
738
739 jassert (v.isValid());
740 v.setProperty (IDs::paramID, paramID, nullptr);
741 v.setProperty (IDs::value, value, nullptr);
742
743 if (offset != 0.0f) v.setProperty (IDs::offset, offset, nullptr);
744 if (curve != 0.5f) v.setProperty (IDs::curve, curve, nullptr);
745
746 modifiersState.addChild (v, -1, &getEdit().getUndoManager());
747
748 auto as = getAutomationSourceList().getSourceFor (source);
749 jassert (as != nullptr);
750
751 return as->assignment;
752}
753
755{
756 TRACKTION_ASSERT_MESSAGE_THREAD
757
758 if (auto existing = getAutomationSourceList().getSourceFor (assignment))
759 existing->state.getParent().removeChild (existing->state, &getEdit().getUndoManager());
760 else
762}
763
765{
766 TRACKTION_ASSERT_MESSAGE_THREAD
767
768 if (auto existing = getAutomationSourceList().getSourceFor (source))
769 existing->state.getParent().removeChild (existing->state, &getEdit().getUndoManager());
770 else
772}
773
775{
776 TRACKTION_ASSERT_MESSAGE_THREAD
777 return getAutomationSourceList().isActive();
778}
779
781{
782 TRACKTION_ASSERT_MESSAGE_THREAD
784
785 getAutomationSourceList()
786 .visitSources ([&assignments] (AutomationModifierSource& s) { assignments.addIfNotAlreadyThere (s.assignment); });
787
788 return assignments;
789}
790
792{
794
795 getAutomationSourceList()
796 .visitSources ([&modifiers] (AutomationModifierSource& s) { modifiers.addIfNotAlreadyThere (s.getModifierSource()); });
797
798 return modifiers;
799}
800
801//==============================================================================
803{
804 return curveSource->isActive() || getAutomationSourceList().isActive();
805}
806
807std::optional<float> AutomatableParameter::getDefaultValue() const
808{
809 if (attachedValue != nullptr)
810 return attachedValue->getDefault();
811
812 return {};
813}
814
816{
817 curveSource->updateInterpolatedPoints();
818}
819
821{
822 if (updateParametersRecursionCheck)
823 return;
824
825 const juce::ScopedValueSetter<bool> svs (updateParametersRecursionCheck, true);
826 float newModifierValue = 0.0f;
827
828 getAutomationSourceList()
829 .visitSources ([&newModifierValue, time] (AutomationSource& m) mutable
830 {
831 m.setPosition (time);
832
833 if (m.isEnabled())
834 {
835 float currentModValue = m.getCurrentValue();
836 jassert (! std::isnan (currentModValue));
837 newModifierValue += currentModValue;
838 }
839 });
840
841 const float newBaseValue = [this, time]
842 {
843 if (curveSource->isActive())
844 {
845 curveSource->setPosition (time);
846 return curveSource->getCurrentValue();
847 }
848
849 return currentParameterValue.load();
850 }();
851
852 if (newModifierValue != 0.0f)
853 {
854 auto normalisedBase = valueRange.convertTo0to1 (newBaseValue);
855 currentModifierValue = valueRange.convertFrom0to1 (juce::jlimit (0.0f, 1.0f, normalisedBase + newModifierValue)) - newBaseValue;
856 }
857 else
858 {
859 currentModifierValue = 0.0f;
860 }
861
862 setParameterValue (newBaseValue, true);
863}
864
865//==============================================================================
866void AutomatableParameter::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i)
867{
868 if (v == getCurve().state || v.isAChildOf (getCurve().state))
869 {
870 curveHasChanged();
871 }
872 else if (attachedValue != nullptr && attachedValue->updateIfMatches (v, i))
873 {
874 // N.B.You shouldn't be directly setting the value of an attachedValue managed parameter.
875 // To avoid feedback loops of sync issues, always go via setParameter
876
877 TRACKTION_ASSERT_MESSAGE_THREAD
878 SCOPED_REALTIME_CHECK
879 // N.B. we shouldn't call attachedValue->updateParameterFromValue here as this
880 // will set the base value of the parameter. The change in property could be due
881 // to a Modifier or automation change so we don't want to force that to be the base value
882 listeners.call (&Listener::currentValueChanged, *this);
883 }
884}
885
886void AutomatableParameter::valueTreeChildAdded (juce::ValueTree& parent, juce::ValueTree& newChild)
887{
888 if (parent == getCurve().state || parent == modifiersState)
889 curveHasChanged();
890 else if (parent == parentState && newChild[IDs::name] == paramID)
891 getCurve().setState (newChild);
892}
893
894void AutomatableParameter::valueTreeChildRemoved (juce::ValueTree& parent, juce::ValueTree&, int)
895{
896 if (parent == getCurve().state || parent == modifiersState)
897 curveHasChanged();
898}
899
900void AutomatableParameter::valueTreeChildOrderChanged (juce::ValueTree& parent, int, int)
901{
902 if (parent == getCurve().state || parent == modifiersState)
903 curveHasChanged();
904}
905
906void AutomatableParameter::valueTreeParentChanged (juce::ValueTree&) {}
907void AutomatableParameter::valueTreeRedirected (juce::ValueTree&) { jassertfalse; } // need to handle this?
908
909//==============================================================================
910void AutomatableParameter::attachToCurrentValue (juce::CachedValue<float>& v)
911{
912 currentParameterValue = currentValue = v;
913 jassert (attachedValue == nullptr);
914 attachedValue = std::make_unique<AttachedFloatValue> (*this, v);
915 v.getValueTree().addListener (this);
916}
917
918void AutomatableParameter::attachToCurrentValue (juce::CachedValue<int>& v)
919{
920 currentParameterValue = currentValue = (float) v.get();
921 jassert (attachedValue == nullptr);
922 attachedValue = std::make_unique<AttachedIntValue> (*this, v);
923 v.getValueTree().addListener (this);
924}
925
926void AutomatableParameter::attachToCurrentValue (juce::CachedValue<bool>& v)
927{
928 currentParameterValue = currentValue = v;
929 jassert (attachedValue == nullptr);
930 attachedValue = std::make_unique<AttachedBoolValue> (*this, v);
931 v.getValueTree().addListener (this);
932}
933
934void AutomatableParameter::updateFromAttachedValue()
935{
936 if (attachedValue)
937 attachedValue->updateParameterFromValue();
938}
939
940void AutomatableParameter::detachFromCurrentValue()
941{
942 if (attachedValue == nullptr)
943 return;
944
945 attachedValue->detach (this);
946 attachedValue.reset();
947}
948
949Engine& AutomatableParameter::getEngine() const noexcept
950{
951 return getEdit().engine;
952}
953
954Edit& AutomatableParameter::getEdit() const noexcept
955{
956 return automatableEditElement.edit;
957}
958
959Track* AutomatableParameter::getTrack() const noexcept
960{
961 return plugin != nullptr ? plugin->getOwnerTrack()
962 : modifierOwner != nullptr ? getTrackContainingModifier (getEdit(), modifierOwner)
963 : macroOwner->getTrack();
964}
965
966AutomationCurve& AutomatableParameter::getCurve() const noexcept
967{
968 return curveSource->curve;
969}
970
972{
973 if (macroOwner != nullptr)
974 return {};
975
976 if (plugin != nullptr)
977 return plugin;
978
979 return modifierOwner;
980}
981
983{
984 if (plugin != nullptr)
985 {
987 return plugin->itemID;
988 }
989
990 if (modifierOwner != nullptr)
991 {
992 jassert (Selectable::isSelectableValid (modifierOwner));
993 return modifierOwner->itemID;
994 }
995
996 jassert (macroOwner != nullptr);
997 return macroOwner->itemID;
998}
999
1000juce::String AutomatableParameter::getPluginAndParamName() const
1001{
1002 juce::String s;
1003
1004 if (plugin != nullptr)
1005 s << plugin->getName() + " >> ";
1006 else if (modifierOwner != nullptr)
1007 s << modifierOwner->getName() + " >> ";
1008 else if (auto af = getOwnerPlugin (macroOwner))
1009 s << af->getName() + " >> ";
1010
1011 return s + getParameterName();
1012}
1013
1014juce::String AutomatableParameter::getFullName() const
1015{
1016 juce::String s;
1017
1018 if (auto t = getTrack())
1019 s << t->getName() << " >> ";
1020
1021 return s + getPluginAndParamName();
1022}
1023
1024//==============================================================================
1026{
1027 isRecording = false;
1028}
1029
1030//==============================================================================
1031void AutomatableParameter::setParameterValue (float value, bool isFollowingCurve)
1032{
1033 auto& curve = getCurve();
1034 value = snapToState (getValueRange().clipValue (value));
1035 currentBaseValue = value;
1036
1037 if (currentModifierValue != 0.0f)
1038 value = snapToState (getValueRange().clipValue (value + currentModifierValue));
1039
1040 if (currentValue != value)
1041 {
1042 parameterChanged (value, isFollowingCurve);
1043
1044 auto& ed = getEdit();
1045
1046 if (isFollowingCurve)
1047 {
1048 ed.getParameterChangeHandler().parameterChanged (*this, true);
1049
1050 currentValue = value;
1051
1052 if (attachedValue != nullptr)
1053 attachedValue->triggerAsyncUpdate();
1054 }
1055 else
1056 {
1057 if (! getEdit().isLoading())
1058 jassert (juce::MessageManager::getInstance()->currentThreadHasLockedMessageManager());
1059
1060 curveHasChanged();
1061
1062 if (auto epc = ed.getTransport().getCurrentPlaybackContext())
1063 {
1064 if (! epc->isDragging())
1065 {
1066 auto numPoints = curve.getNumPoints();
1067 auto& arm = ed.getAutomationRecordManager();
1068
1069 if (epc->isPlaying() && arm.isWritingAutomation())
1070 {
1071 auto time = epc->getPosition();
1072
1073 if (! isRecording)
1074 {
1075 isRecording = true;
1076 arm.postFirstAutomationChange (*this, currentValue);
1077 }
1078
1079 arm.postAutomationChange (*this, time, value);
1080 }
1081 else
1082 {
1083 if (numPoints == 1)
1084 curve.movePoint (0, curve.getPointTime (0), value, false);
1085 }
1086 }
1087 }
1088
1089 currentValue = value;
1090
1091 if (attachedValue != nullptr)
1092 {
1093 attachedValue->cancelPendingUpdate();
1094 attachedValue->setValue (value);
1095 }
1096 }
1097
1098 {
1099 SCOPED_REALTIME_CHECK
1100 parameterChangedCaller.triggerAsyncUpdate();
1101 }
1102 }
1103}
1104
1105void AutomatableParameter::setParameter (float value, juce::NotificationType nt)
1106{
1107 currentParameterValue = value;
1108 setParameterValue (value, false);
1109
1111 {
1112 jassert (nt != juce::sendNotificationAsync); // Async notifications not yet supported
1113 TRACKTION_ASSERT_MESSAGE_THREAD
1114 listeners.call (&Listener::parameterChanged, *this, currentValue);
1115
1116 if (attachedValue != nullptr)
1117 {
1118 // Updates the ValueTree via the CachedValue to the current parameter value synchronously
1119 attachedValue->handleAsyncUpdate();
1120 }
1121 }
1122}
1123
1124void AutomatableParameter::setNormalisedParameter (float value, juce::NotificationType nt)
1125{
1126 setParameter (valueRange.convertFrom0to1 (juce::jlimit (0.0f, 1.0f, value)), nt);
1127}
1128
1129juce::String AutomatableParameter::getCurrentValueAsStringWithLabel()
1130{
1131 auto text = getCurrentValueAsString();
1132 auto label = getLabel();
1133
1134 if (! (label.isEmpty() || text.endsWith (label)))
1135 return text + ' ' + label;
1136
1137 return text;
1138}
1139
1140AutomatableParameter::AutomationSourceList& AutomatableParameter::getAutomationSourceList() const
1141{
1142 if (! automationSourceList)
1143 automationSourceList = std::make_unique<AutomationSourceList> (*this);
1144
1145 return *automationSourceList;
1146}
1147
1148void AutomatableParameter::updateToFollowCurve (TimePosition time)
1149{
1150 TRACKTION_ASSERT_MESSAGE_THREAD
1151 float newModifierValue = 0.0f;
1152
1153 getAutomationSourceList()
1154 .visitSources ([&newModifierValue, time] (AutomationModifierSource& m) mutable
1155 {
1156 if (m.isEnabledAt (time))
1157 {
1158 const float sourceModValue = m.getValueAt (time);
1159 jassert (! std::isnan (sourceModValue));
1160 newModifierValue += sourceModValue;
1161 }
1162 });
1163
1164 const float newBaseValue = [this, time]
1165 {
1166 if (hasAutomationPoints() && ! isRecording)
1167 return curveSource->getValueAt (time);
1168
1169 return currentParameterValue.load();
1170 }();
1171
1172 if (newModifierValue != 0.0f)
1173 {
1174 auto normalisedBase = valueRange.convertTo0to1 (newBaseValue);
1175 currentModifierValue = valueRange.convertFrom0to1 (juce::jlimit (0.0f, 1.0f, normalisedBase + newModifierValue)) - newBaseValue;
1176 }
1177 else
1178 {
1179 currentModifierValue = 0.0f;
1180 }
1181
1182 setParameterValue (newBaseValue, true);
1183}
1184
1186{
1187 TRACKTION_ASSERT_MESSAGE_THREAD
1188 listeners.call (&Listener::parameterChangeGestureBegin, *this);
1189}
1190
1192{
1193 TRACKTION_ASSERT_MESSAGE_THREAD
1194 listeners.call (&Listener::parameterChangeGestureEnd, *this);
1195}
1196
1197//==============================================================================
1198void AutomatableParameter::midiControllerMoved (float newPosition)
1199{
1200 setParameter (snapToState (valueRange.convertFrom0to1 (newPosition)), juce::sendNotification);
1201}
1202
1203void AutomatableParameter::midiControllerPressed()
1204{
1205 if (isDiscrete())
1206 {
1207 int state = getStateForValue (getCurrentValue()) + 1;
1208
1209 if (state >= getNumberOfStates())
1210 state = 0;
1211
1212 setParameter (getValueForState (state), juce::sendNotification);
1213 }
1214}
1215
1216//==============================================================================
1217void AutomatableParameter::curveHasChanged()
1218{
1219 TRACKTION_ASSERT_MESSAGE_THREAD
1221 curveSource->triggerAsyncCurveUpdate();
1222 getEdit().getParameterChangeHandler().parameterChanged (*this, false);
1223 listeners.call (&Listener::curveHasChanged, *this);
1224}
1225
1226//==============================================================================
1227AutomatableParameter::ModifierSource* getSourceForAssignment (const AutomatableParameter::ModifierAssignment& ass)
1228{
1229 for (auto modifier : getAllModifierSources (ass.edit))
1230 if (ass.isForModifierSource (*modifier))
1231 return modifier;
1232
1233 return {};
1234}
1235
1237{
1239
1240 edit.visitAllAutomatableParams (true, [&] (AutomatableParameter& param)
1241 {
1242 for (auto ass : param.getAssignments())
1243 {
1244 if (ass->isForModifierSource (m))
1245 {
1246 jassert (! params.contains (param)); // Being modified by the same source twice?
1247 params.add (param);
1248 break;
1249 }
1250 }
1251 });
1252
1253 return params;
1254}
1255
1256AutomatableParameter* getParameter (AutomatableParameter::ModifierAssignment& assignment)
1257{
1258 AutomatableParameter* result = nullptr;
1259
1260 assignment.edit.visitAllAutomatableParams (true, [&] (AutomatableParameter& param)
1261 {
1262 for (auto ass : param.getAssignments())
1263 if (ass == &assignment)
1264 result = &param;
1265 });
1266
1267 return result;
1268}
1269
1270//==============================================================================
1271AutomationIterator::AutomationIterator (const AutomatableParameter& p)
1272{
1273 hiRes = ! p.automatableEditElement.edit.engine.getEngineBehaviour().interpolateAutomation();
1274
1275 if (hiRes)
1276 copy (p);
1277 else
1278 interpolate (p);
1279}
1280
1281void AutomationIterator::copy (const AutomatableParameter& param)
1282{
1283 const auto& curve = param.getCurve();
1284
1285 jassert (curve.getNumPoints() > 0);
1286
1287 for (int i = 0; i < curve.getNumPoints(); i++)
1288 {
1289 auto src = curve.getPoint (i);
1290
1291 AutoPoint dst;
1292 dst.time = src.time;
1293 dst.value = src.value;
1294 dst.curve = src.curve;
1295
1296 points.add (dst);
1297 }
1298}
1299
1300void AutomationIterator::interpolate (const AutomatableParameter& param)
1301{
1302 const auto& curve = param.getCurve();
1303
1304 jassert (curve.getNumPoints() > 0);
1305
1306 const auto timeDelta = TimeDuration::fromSeconds (1.0 / 100.0);
1307 const double minValueDelta = (param.getValueRange().getLength()) / 256.0;
1308
1309 int curveIndex = 0;
1310 int lastCurveIndex = -1;
1311 TimePosition t;
1312 float lastValue = 1.0e10;
1313 auto lastTime = curve.getPointTime (curve.getNumPoints() - 1) + TimeDuration::fromSeconds (1.0);
1314 TimePosition t1;
1315 auto t2 = curve.getPointTime (0);
1316 float v1 = curve.getValueAt (TimePosition());
1317 float v2 = v1;
1318 float vp = v2;
1319 float c = 0;
1320 CurvePoint bp;
1321 double x1end = 0;
1322 double x2end = 0;
1323 float y1end = 0;
1324 float y2end = 0;
1325
1326 while (t < lastTime)
1327 {
1328 while (t >= t2)
1329 {
1330 if (curveIndex >= curve.getNumPoints() - 1)
1331 {
1332 t1 = t2;
1333 v1 = v2;
1334 t2 = lastTime;
1335 break;
1336 }
1337
1338 t1 = t2;
1339 v1 = v2;
1340 c = curve.getPointCurve (curveIndex);
1341
1342 if (c != 0.0f)
1343 {
1344 bp = curve.getBezierPoint (curveIndex);
1345
1346 if (c < -0.5 || c > 0.5)
1347 curve.getBezierEnds (curveIndex, x1end, y1end, x2end, y2end);
1348 }
1349
1350 t2 = curve.getPointTime (++curveIndex);
1351 v2 = curve.getPointValue (curveIndex);
1352 }
1353
1354 float v = v2;
1355
1356 if (t2 != t1)
1357 {
1358 if (c == 0.0f)
1359 {
1360 v = v1 + (v2 - v1) * (float) ((t - t1) / (t2 - t1));
1361 }
1362 else if (c >= -0.5 && c <= 0.5)
1363 {
1364 v = AutomationCurve::getBezierYFromX (t.inSeconds(), t1.inSeconds(), v1, toTime (bp.time, param.getEdit().tempoSequence).inSeconds(), bp.value, t2.inSeconds(), v2);
1365 }
1366 else
1367 {
1368 if (t >= t1 && t <= TimePosition::fromSeconds (x1end))
1369 v = v1;
1370 else if (t >= TimePosition::fromSeconds (x2end) && t <= t2)
1371 v = v2;
1372 else
1373 v = AutomationCurve::getBezierYFromX (t.inSeconds(), x1end, y1end, toTime (bp.time, param.getEdit().tempoSequence).inSeconds(), bp.value, x2end, y2end);
1374 }
1375 }
1376
1377 if (std::abs (v - lastValue) >= minValueDelta || curveIndex != lastCurveIndex)
1378 {
1379 jassert (t >= t1 && t <= t2);
1380
1381 AutoPoint point;
1382 point.time = t;
1383 point.value = v;
1384
1385 jassert (points.isEmpty() || points.getLast().time <= t);
1386
1387 if (points.size() >= 1 && t - points[points.size() - 1].time > timeDelta * 10)
1388 points.add ({t - timeDelta, vp});
1389
1390 points.add (point);
1391
1392 lastValue = v;
1393 lastCurveIndex = curveIndex;
1394 }
1395
1396 vp = v;
1397 t = t + timeDelta;
1398 }
1399}
1400
1401void AutomationIterator::setPosition (TimePosition newTime) noexcept
1402{
1403 if (hiRes)
1404 setPositionHiRes (newTime);
1405 else
1406 setPositionInterpolated (newTime);
1407}
1408
1409void AutomationIterator::setPositionHiRes (TimePosition newTime) noexcept
1410{
1411 jassert (points.size() > 0);
1412
1413 auto newIndex = updateIndex (newTime);
1414
1415 if (newTime < points[0].time)
1416 {
1417 currentIndex = newIndex;
1418 currentValue = points.getReference (0).value;
1419 return;
1420 }
1421
1422 if (newIndex == points.size() - 1)
1423 {
1424 currentIndex = newIndex;
1425 currentValue = points.getReference (newIndex).value;
1426 return;
1427 }
1428
1429 const auto& p1 = points.getReference (newIndex);
1430 const auto& p2 = points.getReference (newIndex + 1);
1431
1432 const auto t = newTime;
1433
1434 const auto t1 = p1.time;
1435 const auto t2 = p2.time;
1436
1437 const auto v1 = p1.value;
1438 const auto v2 = p2.value;
1439
1440 const auto c = p1.curve;
1441
1442 float v = p2.value;
1443
1444 if (t2 != t1)
1445 {
1446 if (c == 0.0f)
1447 {
1448 v = v1 + (v2 - v1) * (float) ((t - t1) / (t2 - t1));
1449 }
1450 else if (c >= -0.5 && c <= 0.5)
1451 {
1452 auto bp = getBezierPoint (p1.time.inSeconds(), p1.value, p2.time.inSeconds(), p2.value, p1.curve);
1453 v = float (getBezierYFromX (t.inSeconds(), t1.inSeconds(), v1, bp.first, bp.second, t2.inSeconds(), v2));
1454 }
1455 else
1456 {
1457 double x1end = 0, x2end = 0;
1458 double y1end = 0, y2end = 0;
1459
1460 auto bp = getBezierPoint (p1.time.inSeconds(), p1.value, p2.time.inSeconds(), p2.value, p1.curve);
1461 getBezierEnds (p1.time.inSeconds(), p1.value,
1462 p2.time.inSeconds(), p2.value,
1463 p1.curve,
1464 x1end, y1end, x2end, y2end);
1465
1466 if (t >= t1 && t <= TimePosition::fromSeconds (x1end))
1467 v = v1;
1468 else if (t >= TimePosition::fromSeconds (x2end) && t <= t2)
1469 v = v2;
1470 else
1471 v = float (getBezierYFromX (t.inSeconds(), x1end, y1end, bp.first, bp.second, x2end, y2end));
1472 }
1473 }
1474 currentIndex = newIndex;
1475 currentValue = v;
1476}
1477
1478void AutomationIterator::setPositionInterpolated (TimePosition newTime) noexcept
1479{
1480 jassert (points.size() > 0);
1481
1482 auto newIndex = updateIndex (newTime);
1483
1484 if (currentIndex != newIndex)
1485 {
1486 jassert (juce::isPositiveAndBelow (newIndex, points.size()));
1487 currentIndex = newIndex;
1488 currentValue = points.getReference (newIndex).value;
1489 }
1490
1491 if (newTime >= points[0].time && newIndex < points.size() - 1)
1492 {
1493 const auto& p1 = points.getReference (newIndex);
1494 const auto& p2 = points.getReference (newIndex + 1);
1495
1496 const auto t = newTime.inSeconds();
1497
1498 const auto t1 = p1.time.inSeconds();
1499 const auto t2 = p2.time.inSeconds();
1500
1501 const auto v1 = p1.value;
1502 const auto v2 = p2.value;
1503
1504 currentValue = std::lerp (v1, v2, float ((t - t1) / (t2 - t1)));
1505 }
1506}
1507
1508int AutomationIterator::updateIndex (TimePosition newTime)
1509{
1510 auto newIndex = currentIndex;
1511
1512 if (! juce::isPositiveAndBelow (newIndex, points.size()))
1513 newIndex = 0;
1514
1515 if (newIndex > 0 && points.getReference (newIndex).time >= newTime)
1516 {
1517 --newIndex;
1518
1519 while (newIndex > 0 && points.getReference (newIndex).time >= newTime)
1520 --newIndex;
1521 }
1522 else
1523 {
1524 while (newIndex < points.size() - 1 && points.getReference (newIndex + 1).time < newTime)
1525 ++newIndex;
1526 }
1527 return newIndex;
1528}
1529
1530//==============================================================================
1531const char* AutomationDragDropTarget::automatableDragString = "automatableParamDrag";
1532
1533AutomationDragDropTarget::AutomationDragDropTarget() {}
1534AutomationDragDropTarget::~AutomationDragDropTarget() {}
1535
1536bool AutomationDragDropTarget::isAutomatableParameterBeingDraggedOver() const
1537{
1538 return isAutoParamCurrentlyOver;
1539}
1540
1541bool AutomationDragDropTarget::isInterestedInDragSource (const SourceDetails& details)
1542{
1543 return details.description == automatableDragString;
1544}
1545
1546void AutomationDragDropTarget::itemDragEnter (const SourceDetails&)
1547{
1548 isAutoParamCurrentlyOver = hasAnAutomatableParameter();
1549
1550 if (auto c = dynamic_cast<juce::Component*> (this))
1551 c->repaint();
1552}
1553
1554void AutomationDragDropTarget::itemDragExit (const SourceDetails&)
1555{
1556 isAutoParamCurrentlyOver = false;
1557
1558 if (auto c = dynamic_cast<juce::Component*> (this))
1559 c->repaint();
1560}
1561
1562void AutomationDragDropTarget::itemDropped (const SourceDetails& dragSourceDetails)
1563{
1564 isAutoParamCurrentlyOver = false;
1565
1566 if (auto c = dynamic_cast<juce::Component*> (this))
1567 c->repaint();
1568
1569 juce::WeakReference<juce::Component> sourceCompRef (dragSourceDetails.sourceComponent);
1570 juce::WeakReference<juce::Component> thisRef (dynamic_cast<juce::Component*> (this));
1571
1572 if (auto source = dynamic_cast<ParameterisableDragDropSource*> (sourceCompRef.get()))
1573 {
1574 source->draggedOntoAutomatableParameterTargetBeforeParamSelection();
1575
1576 auto handleChosenParam = [sourceCompRef] (AutomatableParameter::Ptr param)
1577 {
1578 if (auto src = dynamic_cast<ParameterisableDragDropSource*> (sourceCompRef.get()))
1579 src->draggedOntoAutomatableParameterTarget (param);
1580 };
1581
1582 chooseAutomatableParameter (handleChosenParam,
1583 [thisRef, handleChosenParam]
1584 {
1585 if (auto t = dynamic_cast<AutomationDragDropTarget*> (thisRef.get()))
1586 t->startParameterLearn (handleChosenParam);
1587 });
1588 }
1589}
1590
1591}} // namespace tracktion { inline namespace engine
bool addIfNotAlreadyThere(ParameterType newElement)
bool isValid() const noexcept
const String & toString() const noexcept
ValueType convertFrom0to1(ValueType proportion) const noexcept
ValueType convertTo0to1(ValueType v) const noexcept
constexpr ValueType getStart() const noexcept
constexpr ValueType getEnd() const noexcept
ValueType clipValue(const ValueType value) const noexcept
static constexpr Range between(const ValueType position1, const ValueType position2) noexcept
bool contains(const ObjectClass *objectToLookFor) const noexcept
ObjectClass * add(ObjectClass *newObject)
ReferencedType * get() const noexcept
void decReferenceCount() noexcept
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
Base class for elements that have some kind of automatable parameters.
juce::Array< ModifierSource * > getModifiers() const
Returns all the current ModifierSources currently in use by assignments.
Selectable * getOwnerSelectable() const
Returns the thing that you'd select if you wanted to show this param.
void removeModifier(ModifierAssignment &)
Removes an assignment.
void parameterChangeGestureBegin()
Call to indicate this parameter is about to be changed.
ModifierAssignment::Ptr addModifier(ModifierSource &, float value=1.0f, float offset=0.0f, float curve=0.5f)
Creates an assignment for a given source.
juce::ReferenceCountedArray< ModifierAssignment > getAssignments() const
Returns all the current ModifierAssignments.
bool hasActiveModifierAssignments() const
Returns true if any ModifierSources are currently in use by assignments.
void resetRecordingStatus()
this is called before and after playback or recording.
void updateStream()
Forces the parameter to update its automation stream for reading automation.
void updateFromAutomationSources(TimePosition)
Updates the parameter and modifier values from its current automation sources.
bool isAutomationActive() const
Returns true if the parameter is being dynamically changed somehow, either through automation or a Mo...
EditItemID getOwnerID() const
Returns the thing that you'd select if you wanted to show this param.
void parameterChangeGestureEnd()
Call to indicate this parameter has stopped being to be changed.
float getCurrentValue() override
Should return the current value of the source.
void setPosition(TimePosition time) override
Should set the position of the source to a specific time in the Edit.
bool isEnabledAt(TimePosition) override
Must return if the source is enabled at the given time.
bool isEnabled() override
Should return true if the source is enabled at the current position.
float getValueAt(TimePosition time) override
Must return the value of automation at the given time.
const EditItemID itemID
Every EditItem has an ID which is unique within the edit.
The Tracktion Edit class!
void visitAllAutomatableParams(bool includeTrackParams, const std::function< void(AutomatableParameter &)> &) const
Returns all automatable parameters in an Edit.
ParameterChangeHandler & getParameterChangeHandler() noexcept
Returns the ParameterChangeHandler for the 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.
Engine & engine
A reference to the Engine.
A MacroParameter is an AutomatableParameter which is a collection of Mappings.
void parameterChanged(AutomatableParameter &parameter, bool fromAutomation)
Called by parameters when they are changed.
virtual juce::String getName() const override=0
The name of the type, e.g.
Track * getOwnerTrack() const
Returns the track if it's a track or clip plugin.
Base class for things that can be selected, and whose properties can appear in the properties panel.
static bool isSelectableValid(const Selectable *) noexcept
checks whether this object has been deleted.
T is_pointer_v
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
T lerp(T... args)
T load(T... args)
typedef float
constexpr Type jmap(Type value0To1, Type targetRangeMin, Type targetRangeMax)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
NotificationType
sendNotificationAsync
sendNotification
dontSendNotification
bool isPositiveAndNotGreaterThan(Type1 valueToTest, Type2 upperLimit) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
AutomatableParameter::ModifierSource * getSourceForAssignment(const AutomatableParameter::ModifierAssignment &ass)
Iterates an Edit looking for the source of this assignment.
juce::ReferenceCountedArray< AutomatableParameter > getAllParametersBeingModifiedBy(Edit &edit, AutomatableParameter::ModifierSource &m)
Iterates an Edit looking for all parameters that are being modified by the given ModifierSource.
juce::Array< MacroParameterList * > getAllMacroParameterLists(const Edit &edit)
Returns all the MacroParameterLists in an Edit.
juce::Array< AutomatableParameter::ModifierSource * > getAllModifierSources(const Edit &edit)
Returns all the ModifierSources in an Edit.
bool getBoolParamValue(const AutomatableParameter &ap)
Returns a bool version of an AutomatableParameter.
AutomatableParameter * getParameter(AutomatableParameter::ModifierAssignment &assignment)
Iterates an Edit looking for the parameter that this ModifierAssignment has been made from.
Modifier::Ptr findModifierForID(ModifierList &ml, EditItemID modifierID)
Returns a Modifier if it can be found in the list.
Track * getTrackContainingModifier(const Edit &edit, const Modifier::Ptr &m)
Returns the Track containing a Modifier.
TimePosition toTime(BeatPosition bp, const TempoSequence &ts)
Converts a BeatPosition to a TimePosition given a TempoSequence.
Plugin::Ptr getOwnerPlugin(MacroParameterList *mpl)
If this MacroParameterList belongs to an Plugin, this will return it.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
virtual void currentValueChanged(AutomatableParameter &)
Called when the current value of the parameter changed, either from setting the parameter,...
virtual void parameterChanged(AutomatableParameter &, float)
Called when the parameter is changed by the plugin or host, not from automation.
virtual void curveHasChanged(AutomatableParameter &)=0
Called when the automation curve has changed, point time, value or curve.
Connects a modifier source to an AutomatableParameter.
Base class for things that can be used to modify parameters.
virtual float getValueAt(TimePosition)=0
Must return the value of automation at the given time.
virtual bool isEnabledAt(TimePosition)=0
Must return if the source is enabled at the given time.
virtual bool isEnabled()=0
Should return true if the source is enabled at the current position.
virtual float getCurrentValue()=0
Should return the current value of the source.
virtual void setPosition(TimePosition)=0
Should set the position of the source to a specific time in the Edit.
ID for objects of type EditElement - e.g.
An Assignment between a MacroParameter and an AutomatableParameter.
float getCurrentValue() override
Should return the current value of the source.
void setPosition(TimePosition time) override
Should set the position of the source to a specific time in the Edit.
bool isEnabledAt(TimePosition) override
Must return if the source is enabled at the given time.
bool isEnabled() override
Should return true if the source is enabled at the current position.
float getValueAt(TimePosition time) override
Must return the value of automation at the given time.
bool isEnabledAt(TimePosition) override
Must return if the source is enabled at the given time.
float getValueAt(TimePosition) override
Must return the value of automation at the given time.
void setPosition(TimePosition newEditTime) override
Should set the position of the source to a specific time in the Edit.
float getCurrentValue() override
Should return the current value of the source.
bool isEnabled() override
Should return true if the source is enabled at the current position.
Bass class for parameter Modifiers.
time
float remapInputValue(float inputVal, juce::Range< float > inputRange)
Remaps an input value from a given input range to 0-1.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.
y1