JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_AudioProcessorValueTreeState.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29//==============================================================================
30
32 const String& parameterName,
37 parameterName,
40 attributes.getAudioParameterFloatAttributes()),
41 unsnappedDefault (valueRange.convertTo0to1 (defaultParameterValue)),
42 discrete (attributes.getDiscrete()),
43 boolean (attributes.getBoolean())
44{
45}
46
47float AudioProcessorValueTreeState::Parameter::getDefaultValue() const { return unsnappedDefault; }
49
52
53void AudioProcessorValueTreeState::Parameter::valueChanged (float newValue)
54{
55 if (approximatelyEqual ((float) lastValue, newValue))
56 return;
57
58 lastValue = newValue;
59 NullCheckedInvocation::invoke (onValueChanged);
60}
61
62//==============================================================================
64{
65private:
67
68public:
69 explicit ParameterAdapter (RangedAudioParameter& parameterIn)
70 : parameter (parameterIn),
71 // For legacy reasons, the unnormalised value should *not* be snapped on construction
72 unnormalisedValue (getRange().convertFrom0to1 (parameter.getDefaultValue()))
73 {
74 parameter.addListener (this);
75
76 if (auto* ptr = dynamic_cast<Parameter*> (&parameter))
77 ptr->onValueChanged = [this] { parameterValueChanged ({}, {}); };
78 }
79
80 ~ParameterAdapter() override { parameter.removeListener (this); }
81
82 void addListener (Listener* l) { listeners.add (l); }
83 void removeListener (Listener* l) { listeners.remove (l); }
84
85 RangedAudioParameter& getParameter() { return parameter; }
86 const RangedAudioParameter& getParameter() const { return parameter; }
87
88 const NormalisableRange<float>& getRange() const { return parameter.getNormalisableRange(); }
89
90 float getDenormalisedDefaultValue() const { return denormalise (parameter.getDefaultValue()); }
91
92 void setDenormalisedValue (float value)
93 {
94 if (! approximatelyEqual (value, (float) unnormalisedValue))
95 setNormalisedValue (normalise (value));
96 }
97
98 float getDenormalisedValueForText (const String& text) const
99 {
100 return denormalise (parameter.getValueForText (text));
101 }
102
103 String getTextForDenormalisedValue (float value) const
104 {
105 return parameter.getText (normalise (value), 0);
106 }
107
108 float getDenormalisedValue() const { return unnormalisedValue; }
109 std::atomic<float>& getRawDenormalisedValue() { return unnormalisedValue; }
110
111 bool flushToTree (const Identifier& key, UndoManager* um)
112 {
113 auto needsUpdateTestValue = true;
114
115 if (! needsUpdate.compare_exchange_strong (needsUpdateTestValue, false))
116 return false;
117
118 if (auto* valueProperty = tree.getPropertyPointer (key))
119 {
120 if (! approximatelyEqual ((float) *valueProperty, unnormalisedValue.load()))
121 {
122 ScopedValueSetter<bool> svs (ignoreParameterChangedCallbacks, true);
123 tree.setProperty (key, unnormalisedValue.load(), um);
124 }
125 }
126 else
127 {
128 tree.setProperty (key, unnormalisedValue.load(), nullptr);
129 }
130
131 return true;
132 }
133
134 ValueTree tree;
135
136private:
137 void parameterGestureChanged (int, bool) override {}
138
139 void parameterValueChanged (int, float) override
140 {
141 const auto newValue = denormalise (parameter.getValue());
142
143 if (! listenersNeedCalling && approximatelyEqual ((float) unnormalisedValue, newValue))
144 return;
145
146 unnormalisedValue = newValue;
147 listeners.call ([this] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); });
148 listenersNeedCalling = false;
149 needsUpdate = true;
150 }
151
152 float denormalise (float normalised) const
153 {
154 return getParameter().convertFrom0to1 (normalised);
155 }
156
157 float normalise (float denormalised) const
158 {
159 return getParameter().convertTo0to1 (denormalised);
160 }
161
162 void setNormalisedValue (float value)
163 {
164 if (ignoreParameterChangedCallbacks)
165 return;
166
167 parameter.setValueNotifyingHost (value);
168 }
169
170 class LockedListeners
171 {
172 public:
173 template <typename Fn>
174 void call (Fn&& fn)
175 {
176 const CriticalSection::ScopedLockType lock (mutex);
177 listeners.call (std::forward<Fn> (fn));
178 }
179
180 void add (Listener* l)
181 {
182 const CriticalSection::ScopedLockType lock (mutex);
183 listeners.add (l);
184 }
185
186 void remove (Listener* l)
187 {
188 const CriticalSection::ScopedLockType lock (mutex);
189 listeners.remove (l);
190 }
191
192 private:
193 CriticalSection mutex;
194 ListenerList<Listener> listeners;
195 };
196
197 RangedAudioParameter& parameter;
198 LockedListeners listeners;
199 std::atomic<float> unnormalisedValue { 0.0f };
200 std::atomic<bool> needsUpdate { true }, listenersNeedCalling { true };
201 bool ignoreParameterChangedCallbacks { false };
202};
203
204//==============================================================================
210{
211 struct PushBackVisitor final : ParameterLayout::Visitor
212 {
213 explicit PushBackVisitor (AudioProcessorValueTreeState& stateIn)
214 : state (&stateIn) {}
215
216 void visit (std::unique_ptr<RangedAudioParameter> param) const override
217 {
218 if (param == nullptr)
219 {
221 return;
222 }
223
224 state->addParameterAdapter (*param);
225 state->processor.addParameter (param.release());
226 }
227
228 void visit (std::unique_ptr<AudioProcessorParameterGroup> group) const override
229 {
230 if (group == nullptr)
231 {
233 return;
234 }
235
236 for (const auto param : group->getParameters (true))
237 {
238 if (const auto rangedParam = dynamic_cast<RangedAudioParameter*> (param))
239 {
240 state->addParameterAdapter (*rangedParam);
241 }
242 else
243 {
244 // If you hit this assertion then you are attempting to add a parameter that is
245 // not derived from RangedAudioParameter to the AudioProcessorValueTreeState.
247 }
248 }
249
250 state->processor.addParameterGroup (std::move (group));
251 }
252
254 };
255
256 for (auto& item : parameterLayout.parameters)
257 item->accept (PushBackVisitor (*this));
258
260}
261
263 : processor (p), undoManager (um)
264{
265 startTimerHz (10);
266 state.addListener (this);
267}
268
273
274//==============================================================================
276 const String& paramName,
277 const String& labelText,
279 float defaultVal,
282 bool isMetaParameter,
287{
290 .withStringFromValueFunction ([fn = std::move (valueToTextFunction)] (float v, int) { return fn (v); })
291 .withValueFromStringFunction (std::move (textToValueFunction))
292 .withMeta (isMetaParameter)
293 .withAutomatable (isAutomatableParameter)
294 .withDiscrete (isDiscreteParameter)
295 .withCategory (category)
296 .withBoolean (isBooleanParameter);
297
298 return createAndAddParameter (std::make_unique<Parameter> (paramID,
299 paramName,
300 range,
302 std::move (attributes)));
303}
304
306{
307 if (param == nullptr)
308 return nullptr;
309
310 // All parameters must be created before giving this manager a ValueTree state!
311 jassert (! state.isValid());
312
313 if (getParameter (param->paramID) != nullptr)
314 return nullptr;
315
316 addParameterAdapter (*param);
317
318 processor.addParameter (param.get());
319
320 return param.release();
321}
322
323//==============================================================================
324void AudioProcessorValueTreeState::addParameterAdapter (RangedAudioParameter& param)
325{
326 adapterTable.emplace (param.paramID, std::make_unique<ParameterAdapter> (param));
327}
328
329AudioProcessorValueTreeState::ParameterAdapter* AudioProcessorValueTreeState::getParameterAdapter (StringRef paramID) const
330{
331 auto it = adapterTable.find (paramID);
332 return it == adapterTable.end() ? nullptr : it->second.get();
333}
334
336{
337 if (auto* p = getParameterAdapter (paramID))
338 p->addListener (listener);
339}
340
342{
343 if (auto* p = getParameterAdapter (paramID))
344 p->removeListener (listener);
345}
346
348{
349 if (auto* adapter = getParameterAdapter (paramID))
350 if (adapter->tree.isValid())
351 return adapter->tree.getPropertyAsValue (valuePropertyID, undoManager);
352
353 return {};
354}
355
357{
358 if (auto* p = getParameterAdapter (paramID))
359 return p->getRange();
360
361 return {};
362}
363
365{
366 if (auto adapter = getParameterAdapter (paramID))
367 return &adapter->getParameter();
368
369 return nullptr;
370}
371
373{
374 if (auto* p = getParameterAdapter (paramID))
375 return &p->getRawDenormalisedValue();
376
377 return nullptr;
378}
379
381{
382 ScopedLock lock (valueTreeChanging);
383 flushParameterValuesToValueTree();
384 return state.createCopy();
385}
386
388{
389 ScopedLock lock (valueTreeChanging);
390
391 state = newState;
392
393 if (undoManager != nullptr)
395}
396
397void AudioProcessorValueTreeState::setNewState (ValueTree vt)
398{
399 jassert (vt.getParent() == state);
400
401 if (auto* p = getParameterAdapter (vt.getProperty (idPropertyID).toString()))
402 {
403 p->tree = vt;
404 p->setDenormalisedValue (p->tree.getProperty (valuePropertyID, p->getDenormalisedDefaultValue()));
405 }
406}
407
408void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees()
409{
410 ScopedLock lock (valueTreeChanging);
411
412 for (auto& p : adapterTable)
413 p.second->tree = ValueTree();
414
415 for (const auto& child : state)
416 setNewState (child);
417
418 for (auto& p : adapterTable)
419 {
420 auto& adapter = *p.second;
421
422 if (! adapter.tree.isValid())
423 {
424 adapter.tree = ValueTree (valueType);
425 adapter.tree.setProperty (idPropertyID, adapter.getParameter().paramID, nullptr);
426 state.appendChild (adapter.tree, nullptr);
427 }
428 }
429
430 flushParameterValuesToValueTree();
431}
432
433void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
434{
435 if (tree.hasType (valueType) && tree.getParent() == state)
436 setNewState (tree);
437}
438
439void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree)
440{
441 if (parent == state && tree.hasType (valueType))
442 setNewState (tree);
443}
444
445void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v)
446{
447 if (v == state)
448 updateParameterConnectionsToChildTrees();
449}
450
451bool AudioProcessorValueTreeState::flushParameterValuesToValueTree()
452{
453 ScopedLock lock (valueTreeChanging);
454
455 bool anyUpdated = false;
456
457 for (auto& p : adapterTable)
458 anyUpdated |= p.second->flushToTree (valuePropertyID, undoManager);
459
460 return anyUpdated;
461}
462
463void AudioProcessorValueTreeState::timerCallback()
464{
465 auto anythingUpdated = flushParameterValuesToValueTree();
466
467 startTimer (anythingUpdated ? 1000 / 50
468 : jlimit (50, 500, getTimerInterval() + 20));
469}
470
471//==============================================================================
472template <typename Attachment, typename Control>
473std::unique_ptr<Attachment> makeAttachment (const AudioProcessorValueTreeState& stateToUse,
474 const String& parameterID,
475 Control& control)
476{
477 if (auto* parameter = stateToUse.getParameter (parameterID))
478 return std::make_unique<Attachment> (*parameter, control, stateToUse.undoManager);
479
481 return nullptr;
482}
483
484AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& stateToUse,
485 const String& parameterID,
486 Slider& slider)
487 : attachment (makeAttachment<SliderParameterAttachment> (stateToUse, parameterID, slider))
488{
489}
490
491AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& stateToUse,
492 const String& parameterID,
493 ComboBox& combo)
494 : attachment (makeAttachment<ComboBoxParameterAttachment> (stateToUse, parameterID, combo))
495{
496}
497
498AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& stateToUse,
499 const String& parameterID,
500 Button& button)
501 : attachment (makeAttachment<ButtonParameterAttachment> (stateToUse, parameterID, button))
502{
503}
504
505//==============================================================================
506//==============================================================================
507#if JUCE_UNIT_TESTS
508
509struct ParameterAdapterTests final : public UnitTest
510{
512 : UnitTest ("Parameter Adapter", UnitTestCategories::audioProcessorParameters)
513 {}
514
515 void runTest() override
516 {
517 beginTest ("The default value is returned correctly");
518 {
519 const auto test = [&] (NormalisableRange<float> range, float value)
520 {
521 AudioParameterFloat param ({}, {}, range, value);
522
523 AudioProcessorValueTreeState::ParameterAdapter adapter (param);
524
525 expectEquals (adapter.getDenormalisedDefaultValue(), value);
526 };
527
528 test ({ -100, 100 }, 0);
529 test ({ -2.5, 12.5 }, 10);
530 }
531
532 beginTest ("Denormalised parameter values can be retrieved");
533 {
534 const auto test = [&] (NormalisableRange<float> range, float value)
535 {
536 AudioParameterFloat param ({}, {}, range, {});
537 AudioProcessorValueTreeState::ParameterAdapter adapter (param);
538
539 adapter.setDenormalisedValue (value);
540
541 expectEquals (adapter.getDenormalisedValue(), value);
542 expectEquals (adapter.getRawDenormalisedValue().load(), value);
543 };
544
545 test ({ -20, -10 }, -15);
546 test ({ 0, 7.5 }, 2.5);
547 }
548
549 beginTest ("Floats can be converted to text");
550 {
551 const auto test = [&] (NormalisableRange<float> range, float value, String expected)
552 {
553 AudioParameterFloat param ({}, {}, range, {});
554 AudioProcessorValueTreeState::ParameterAdapter adapter (param);
555
556 expectEquals (adapter.getTextForDenormalisedValue (value), expected);
557 };
558
559 test ({ -100, 100 }, 0, "0.0000000");
560 test ({ -2.5, 12.5 }, 10, "10.0000000");
561 test ({ -20, -10 }, -15, "-15.0000000");
562 test ({ 0, 7.5 }, 2.5, "2.5000000");
563 }
564
565 beginTest ("Text can be converted to floats");
566 {
567 const auto test = [&] (NormalisableRange<float> range, String text, float expected)
568 {
569 AudioParameterFloat param ({}, {}, range, {});
570 AudioProcessorValueTreeState::ParameterAdapter adapter (param);
571
572 expectEquals (adapter.getDenormalisedValueForText (text), expected);
573 };
574
575 test ({ -100, 100 }, "0.0", 0);
576 test ({ -2.5, 12.5 }, "10.0", 10);
577 test ({ -20, -10 }, "-15.0", -15);
578 test ({ 0, 7.5 }, "2.5", 2.5);
579 }
580 }
581};
582
584
585namespace
586{
587template <typename ValueType>
588inline bool operator== (const NormalisableRange<ValueType>& a,
590{
591 return std::tie (a.start, a.end, a.interval, a.skew, a.symmetricSkew)
592 == std::tie (b.start, b.end, b.interval, b.skew, b.symmetricSkew);
593}
594
595template <typename ValueType>
596inline bool operator!= (const NormalisableRange<ValueType>& a,
598{
599 return ! (a == b);
600}
601} // namespace
602
603class AudioProcessorValueTreeStateTests final : public UnitTest
604{
605private:
606 using Parameter = AudioProcessorValueTreeState::Parameter;
607 using ParameterGroup = AudioProcessorParameterGroup;
608 using ParameterLayout = AudioProcessorValueTreeState::ParameterLayout;
609 using Attributes = AudioProcessorValueTreeStateParameterAttributes;
610
611 class TestAudioProcessor final : public AudioProcessor
612 {
613 public:
614 TestAudioProcessor() = default;
615
616 explicit TestAudioProcessor (ParameterLayout layout)
617 : state (*this, nullptr, "state", std::move (layout)) {}
618
619 const String getName() const override { return {}; }
620 void prepareToPlay (double, int) override {}
621 void releaseResources() override {}
622 void processBlock (AudioBuffer<float>&, MidiBuffer&) override {}
623 using AudioProcessor::processBlock;
624 double getTailLengthSeconds() const override { return {}; }
625 bool acceptsMidi() const override { return {}; }
626 bool producesMidi() const override { return {}; }
627 AudioProcessorEditor* createEditor() override { return {}; }
628 bool hasEditor() const override { return {}; }
629 int getNumPrograms() override { return 1; }
630 int getCurrentProgram() override { return {}; }
631 void setCurrentProgram (int) override {}
632 const String getProgramName (int) override { return {}; }
633 void changeProgramName (int, const String&) override {}
634 void getStateInformation (MemoryBlock&) override {}
635 void setStateInformation (const void*, int) override {}
636
637 AudioProcessorValueTreeState state { *this, nullptr };
638 };
639
640 struct Listener final : public AudioProcessorValueTreeState::Listener
641 {
642 void parameterChanged (const String& idIn, float valueIn) override
643 {
644 id = idIn;
645 value = valueIn;
646 }
647
648 String id;
649 float value{};
650 };
651
652public:
654 : UnitTest ("Audio Processor Value Tree State", UnitTestCategories::audioProcessorParameters)
655 {}
656
657 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262)
658 void runTest() override
659 {
660 ScopedJuceInitialiser_GUI scopedJuceInitialiser_gui;
661
662 beginTest ("After calling createAndAddParameter, the number of parameters increases by one");
663 {
665
666 proc.state.createAndAddParameter (std::make_unique<Parameter> (
667 String(),
668 String(),
669 NormalisableRange<float>(),
670 0.0f));
671
672 expectEquals (proc.getParameters().size(), 1);
673 }
674
675 beginTest ("After creating a normal named parameter, we can later retrieve that parameter");
676 {
678
679 const auto key = "id";
680 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
681 key,
682 String(),
683 NormalisableRange<float>(),
684 0.0f));
685
686 expect (proc.state.getParameter (key) == param);
687 }
688
689 beginTest ("After construction, the value tree has the expected format");
690 {
692 std::make_unique<AudioProcessorParameterGroup> ("A", "", "",
693 std::make_unique<AudioParameterBool> ("a", "", false),
694 std::make_unique<AudioParameterFloat> ("b", "", NormalisableRange<float>{}, 0.0f)),
695 std::make_unique<AudioProcessorParameterGroup> ("B", "", "",
696 std::make_unique<AudioParameterInt> ("c", "", 0, 1, 0),
697 std::make_unique<AudioParameterChoice> ("d", "", StringArray { "foo", "bar" }, 0)) });
698
699 const auto valueTree = proc.state.copyState();
700
701 expectEquals (valueTree.getNumChildren(), 4);
702
703 for (auto child : valueTree)
704 {
705 expect (child.hasType ("PARAM"));
706 expect (child.hasProperty ("id"));
707 expect (child.hasProperty ("value"));
708 }
709 }
710
711 beginTest ("Meta parameters can be created");
712 {
714
715 const auto key = "id";
716 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
717 key,
718 String(),
719 NormalisableRange<float>(),
720 0.0f,
721 Attributes().withMeta (true)));
722
723 expect (param->isMetaParameter());
724 }
725
726 beginTest ("Automatable parameters can be created");
727 {
729
730 const auto key = "id";
731 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
732 key,
733 String(),
734 NormalisableRange<float>(),
735 0.0f,
736 Attributes().withAutomatable (true)));
737
738 expect (param->isAutomatable());
739 }
740
741 beginTest ("Discrete parameters can be created");
742 {
744
745 const auto key = "id";
746 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
747 key,
748 String(),
749 NormalisableRange<float>(),
750 0.0f,
751 Attributes().withDiscrete (true)));
752
753 expect (param->isDiscrete());
754 }
755
756 beginTest ("Custom category parameters can be created");
757 {
759
760 const auto key = "id";
761 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
762 key,
763 String(),
764 NormalisableRange<float>(),
765 0.0f,
766 Attributes().withCategory (AudioProcessorParameter::Category::inputMeter)));
767
768 expect (param->category == AudioProcessorParameter::Category::inputMeter);
769 }
770
771 beginTest ("Boolean parameters can be created");
772 {
774
775 const auto key = "id";
776 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
777 key,
778 String(),
779 NormalisableRange<float>(),
780 0.0f,
781 Attributes().withBoolean (true)));
782
783 expect (param->isBoolean());
784 }
785
786 beginTest ("After creating a custom named parameter, we can later retrieve that parameter");
787 {
788 const auto key = "id";
789 auto param = std::make_unique<AudioParameterBool> (key, "", false);
790 const auto paramPtr = param.get();
791
792 TestAudioProcessor proc (std::move (param));
793
794 expect (proc.state.getParameter (key) == paramPtr);
795 }
796
797 beginTest ("After adding a normal parameter that already exists, the AudioProcessor parameters are unchanged");
798 {
800 const auto key = "id";
801 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
802 key,
803 String(),
804 NormalisableRange<float>(),
805 0.0f));
806
807 proc.state.createAndAddParameter (std::make_unique<Parameter> (
808 key,
809 String(),
810 NormalisableRange<float>(),
811 0.0f));
812
813 expectEquals (proc.getParameters().size(), 1);
814 expect (proc.getParameters().getFirst() == param);
815 }
816
817 beginTest ("After setting a parameter value, that value is reflected in the state");
818 {
820 const auto key = "id";
821 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
822 key,
823 String(),
824 NormalisableRange<float>(),
825 0.0f));
826
827 const auto value = 0.5f;
828 param->setValueNotifyingHost (value);
829
830 expectEquals (proc.state.getRawParameterValue (key)->load(), value);
831 }
832
833 beginTest ("After adding an APVTS::Parameter, its value is the default value");
834 {
836 const auto key = "id";
837 const auto value = 5.0f;
838
839 proc.state.createAndAddParameter (std::make_unique<Parameter> (
840 key,
841 String(),
842 NormalisableRange<float> (0.0f, 100.0f, 10.0f),
843 value));
844
845 expectEquals (proc.state.getRawParameterValue (key)->load(), value);
846 }
847
848 beginTest ("Listeners receive notifications when parameters change");
849 {
850 Listener listener;
852 const auto key = "id";
853 const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
854 key,
855 String(),
856 NormalisableRange<float>(),
857 0.0f));
858 proc.state.addParameterListener (key, &listener);
859
860 const auto value = 0.5f;
861 param->setValueNotifyingHost (value);
862
863 expectEquals (listener.id, String { key });
864 expectEquals (listener.value, value);
865 }
866
867 beginTest ("Bool parameters have a range of 0-1");
868 {
869 const auto key = "id";
870
871 TestAudioProcessor proc (std::make_unique<AudioParameterBool> (key, "", false));
872
873 expect (proc.state.getParameterRange (key) == NormalisableRange<float> (0.0f, 1.0f, 1.0f));
874 }
875
876 beginTest ("Float parameters retain their specified range");
877 {
878 const auto key = "id";
879 const auto range = NormalisableRange<float> { -100, 100, 0.7f, 0.2f, true };
880
881 TestAudioProcessor proc (std::make_unique<AudioParameterFloat> (key, "", range, 0.0f));
882
883 expect (proc.state.getParameterRange (key) == range);
884 }
885
886 beginTest ("Int parameters retain their specified range");
887 {
888 const auto key = "id";
889 const auto min = -27;
890 const auto max = 53;
891
892 TestAudioProcessor proc (std::make_unique<AudioParameterInt> (key, "", min, max, 0));
893
894 expect (proc.state.getParameterRange (key) == NormalisableRange<float> (float (min), float (max), 1.0f));
895 }
896
897 beginTest ("Choice parameters retain their specified range");
898 {
899 const auto key = "id";
900 const auto choices = StringArray { "", "", "" };
901
902 TestAudioProcessor proc (std::make_unique<AudioParameterChoice> (key, "", choices, 0));
903
904 expect (proc.state.getParameterRange (key) == NormalisableRange<float> (0.0f, (float) (choices.size() - 1), 1.0f));
905 expect (proc.state.getParameter (key)->getNumSteps() == choices.size());
906 }
907
908 beginTest ("When the parameter value is changed, normal parameter values are updated");
909 {
911 const auto key = "id";
912 const auto initialValue = 0.2f;
913 auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (
914 key,
915 String(),
916 NormalisableRange<float>(),
917 initialValue));
918 proc.state.state = ValueTree { "state" };
919
920 auto value = proc.state.getParameterAsValue (key);
921 expectEquals (float (value.getValue()), initialValue);
922
923 const auto newValue = 0.75f;
924 value = newValue;
925
926 expectEquals (param->getValue(), newValue);
927 expectEquals (proc.state.getRawParameterValue (key)->load(), newValue);
928 }
929
930 beginTest ("When the parameter value is changed, custom parameter values are updated");
931 {
932 const auto key = "id";
933 const auto choices = StringArray ("foo", "bar", "baz");
934 auto param = std::make_unique<AudioParameterChoice> (key, "", choices, 0);
935 const auto paramPtr = param.get();
936 TestAudioProcessor proc (std::move (param));
937
938 const auto newValue = 2.0f;
939 auto value = proc.state.getParameterAsValue (key);
940 value = newValue;
941
942 expectEquals (paramPtr->getCurrentChoiceName(), choices[int (newValue)]);
943 expectEquals (proc.state.getRawParameterValue (key)->load(), newValue);
944 }
945
946 beginTest ("When the parameter value is changed, listeners are notified");
947 {
948 Listener listener;
950 const auto key = "id";
951 proc.state.createAndAddParameter (std::make_unique<Parameter> (
952 key,
953 String(),
954 NormalisableRange<float>(),
955 0.0f));
956 proc.state.addParameterListener (key, &listener);
957 proc.state.state = ValueTree { "state" };
958
959 const auto newValue = 0.75f;
960 proc.state.getParameterAsValue (key) = newValue;
961
962 expectEquals (listener.value, newValue);
963 expectEquals (listener.id, String { key });
964 }
965
966 beginTest ("When the parameter value is changed, listeners are notified");
967 {
968 const auto key = "id";
969 const auto choices = StringArray { "foo", "bar", "baz" };
970 Listener listener;
971 TestAudioProcessor proc (std::make_unique<AudioParameterChoice> (key, "", choices, 0));
972 proc.state.addParameterListener (key, &listener);
973
974 const auto newValue = 2.0f;
975 proc.state.getParameterAsValue (key) = newValue;
976
977 expectEquals (listener.value, newValue);
978 expectEquals (listener.id, String (key));
979 }
980 }
981 JUCE_END_IGNORE_WARNINGS_MSVC
982};
983
985
986#endif
987
988} // namespace juce
A subclass of AudioProcessorParameter that provides an easy way to create a parameter which maps onto...
A base class for listeners that want to know about changes to an AudioProcessorParameter.
@ inputMeter
The following categories tell the host that this parameter is a meter level value and therefore read-...
Advanced properties of an AudioProcessorValueTreeState::Parameter.
A class to contain a set of RangedAudioParameters and AudioProcessorParameterGroups containing Ranged...
A parameter class that maintains backwards compatibility with deprecated AudioProcessorValueTreeState...
bool isBoolean() const override
Returns whether the parameter represents a boolean switch, typically with "On" and "Off" states.
bool isDiscrete() const override
Returns whether the parameter uses discrete values, based on the result of getNumSteps,...
Parameter(const ParameterID &parameterID, const String &parameterName, NormalisableRange< float > valueRange, float defaultValue, const AudioProcessorValueTreeStateParameterAttributes &attributes={})
Constructs a parameter instance.
float getDefaultValue() const override
This should return the default value for this parameter.
int getNumSteps() const override
Returns the number of steps that this parameter's range should be quantised into.
This class contains a ValueTree that is used to manage an AudioProcessor's entire state.
Value getParameterAsValue(StringRef parameterID) const
Returns a Value object that can be used to control a particular parameter.
UndoManager *const undoManager
Provides access to the undo manager that this object is using.
RangedAudioParameter * getParameter(StringRef parameterID) const noexcept
Returns a parameter by its ID string.
AudioProcessor & processor
A reference to the processor with which this state is associated.
void addParameterListener(StringRef parameterID, Listener *listener)
Attaches a callback to one of the parameters, which will be called when the parameter changes.
NormalisableRange< float > getParameterRange(StringRef parameterID) const noexcept
Returns the range that was set when the given parameter was created.
AudioProcessorValueTreeState(AudioProcessor &processorToConnectTo, UndoManager *undoManagerToUse, const Identifier &valueTreeType, ParameterLayout parameterLayout)
Creates a state object for a given processor, and sets up all the parameters that will control that p...
void replaceState(const ValueTree &newState)
Replaces the state value tree.
void removeParameterListener(StringRef parameterID, Listener *listener)
Removes a callback that was previously added with addParameterCallback().
ValueTree copyState()
Returns a copy of the state value tree.
RangedAudioParameter * createAndAddParameter(std::unique_ptr< RangedAudioParameter > parameter)
This function adds a parameter to the attached AudioProcessor and that parameter will be managed by t...
ValueTree state
The state of the whole processor.
std::atomic< float > * getRawParameterValue(StringRef parameterID) const noexcept
Returns a pointer to a floating point representation of a particular parameter which a realtime proce...
Base class for audio processing classes or plugins.
void addParameter(AudioProcessorParameter *)
Adds a parameter to the AudioProcessor.
Automatically locks and unlocks a mutex object.
Represents a string identifier, designed for accessing properties by name.
Holds a set of objects and can invoke a member function callback on each object in the set with a sin...
Represents a mapping between an arbitrary range of values and a normalised 0->1 range.
Range< ValueType > getRange() const noexcept
Returns the extent of the normalisable range.
Combines a parameter ID and a version hint.
This abstract base class is used by some AudioProcessorParameter helper classes.
int getNumSteps() const override
Returns the number of steps for this parameter based on the normalisable range's interval.
Helper class providing an RAII-based mechanism for temporarily setting and then re-setting a value.
A simple class for holding temporary references to a string literal or String.
The JUCE String class!
Definition juce_String.h:53
void stopTimer() noexcept
Stops the timer.
int getTimerInterval() const noexcept
Returns the timer's interval.
Definition juce_Timer.h:116
void startTimerHz(int timerFrequencyHz) noexcept
Starts the timer with an interval specified in Hertz.
void startTimer(int intervalInMilliseconds) noexcept
Starts the timer and sets the length of interval required.
Manages a list of undo/redo commands.
void clearUndoHistory()
Deletes all stored actions in the list.
A powerful tree structure that can be used to hold free-form data, and which can handle its own undo ...
bool isValid() const noexcept
Returns true if this tree refers to some valid data.
void addListener(Listener *listener)
Adds a listener to receive callbacks when this tree is changed in some way.
void appendChild(const ValueTree &child, UndoManager *undoManager)
Appends a new child sub-tree to this tree.
ValueTree createCopy() const
Returns a deep copy of this tree and all its sub-trees.
Represents a shared variant value.
Definition juce_Value.h:51
T get(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define jassertfalse
This will always cause an assertion failure.
typedef int
T lock(T... args)
typedef float
T max(T... args)
T min(T... args)
T move(T... args)
JUCE Namespace.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
T release(T... args)
remove
A listener class that can be attached to an AudioProcessorValueTreeState.
virtual void parameterChanged(const String &parameterID, float newValue)=0
This callback method is called by the AudioProcessorValueTreeState when a parameter changes.
T tie(T... args)