|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2017 - ROLI Ltd.
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 5 End-User License
- Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
- 27th April 2017).
-
- End User License Agreement: www.juce.com/juce-5-licence
- Privacy Policy: www.juce.com/juce-5-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- //==============================================================================
- AudioProcessorValueTreeState::Parameter::Parameter (const String& parameterID,
- const String& parameterName,
- const String& labelText,
- NormalisableRange<float> valueRange,
- float defaultValue,
- std::function<String (float)> valueToTextFunction,
- std::function<float (const String&)> textToValueFunction,
- bool isMetaParameter,
- bool isAutomatableParameter,
- bool isDiscrete,
- AudioProcessorParameter::Category category,
- bool isBoolean)
- : AudioParameterFloat (parameterID,
- parameterName,
- valueRange,
- defaultValue,
- labelText,
- category,
- valueToTextFunction == nullptr ? std::function<String (float v, int)>()
- : [valueToTextFunction](float v, int) { return valueToTextFunction (v); },
- std::move (textToValueFunction)),
- unsnappedDefault (valueRange.convertTo0to1 (defaultValue)),
- metaParameter (isMetaParameter),
- automatable (isAutomatableParameter),
- discrete (isDiscrete),
- boolean (isBoolean)
- {
- }
-
- float AudioProcessorValueTreeState::Parameter::getDefaultValue() const { return unsnappedDefault; }
- int AudioProcessorValueTreeState::Parameter::getNumSteps() const { return RangedAudioParameter::getNumSteps(); }
-
- bool AudioProcessorValueTreeState::Parameter::isMetaParameter() const { return metaParameter; }
- bool AudioProcessorValueTreeState::Parameter::isAutomatable() const { return automatable; }
- bool AudioProcessorValueTreeState::Parameter::isDiscrete() const { return discrete; }
- bool AudioProcessorValueTreeState::Parameter::isBoolean() const { return boolean; }
-
- //==============================================================================
- class AudioProcessorValueTreeState::ParameterAdapter : private AudioProcessorParameter::Listener
- {
- private:
- using Listener = AudioProcessorValueTreeState::Listener;
-
- public:
- explicit ParameterAdapter (RangedAudioParameter& parameterIn)
- : parameter (parameterIn),
- // For legacy reasons, the unnormalised value should *not* be snapped on construction
- unnormalisedValue (getRange().convertFrom0to1 (parameter.getDefaultValue()))
- {
- parameter.addListener (this);
- }
-
- ~ParameterAdapter() override { parameter.removeListener (this); }
-
- void addListener (Listener* l) { listeners.add (l); }
- void removeListener (Listener* l) { listeners.remove (l); }
-
- RangedAudioParameter& getParameter() { return parameter; }
- const RangedAudioParameter& getParameter() const { return parameter; }
-
- const NormalisableRange<float>& getRange() const { return parameter.getNormalisableRange(); }
-
- float getDenormalisedDefaultValue() const { return denormalise (parameter.getDefaultValue()); }
-
- void setDenormalisedValue (float value)
- {
- if (value == unnormalisedValue)
- return;
-
- setNormalisedValue (normalise (value));
- }
-
- float getDenormalisedValueForText (const String& text) const
- {
- return denormalise (parameter.getValueForText (text));
- }
-
- String getTextForDenormalisedValue (float value) const
- {
- return parameter.getText (normalise (value), 0);
- }
-
- float getDenormalisedValue() const { return unnormalisedValue; }
- float& getRawDenormalisedValue() { return unnormalisedValue; }
-
- bool flushToTree (const Identifier& key, UndoManager* um)
- {
- auto needsUpdateTestValue = true;
-
- if (! needsUpdate.compare_exchange_strong (needsUpdateTestValue, false))
- return false;
-
- if (auto valueProperty = tree.getPropertyPointer (key))
- {
- if ((float) *valueProperty != unnormalisedValue)
- {
- ScopedValueSetter<bool> svs (ignoreParameterChangedCallbacks, true);
- tree.setProperty (key, unnormalisedValue, um);
- }
- }
- else
- {
- tree.setProperty (key, unnormalisedValue, nullptr);
- }
-
- return true;
- }
-
- ValueTree tree;
-
- private:
- void parameterGestureChanged (int, bool) override {}
-
- void parameterValueChanged (int, float) override
- {
- const auto newValue = denormalise (parameter.getValue());
-
- if (unnormalisedValue == newValue && ! listenersNeedCalling)
- return;
-
- unnormalisedValue = newValue;
- listeners.call ([=](Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); });
- listenersNeedCalling = false;
- needsUpdate = true;
- }
-
- float denormalise (float normalised) const
- {
- return getParameter().convertFrom0to1 (normalised);
- }
-
- float normalise (float denormalised) const
- {
- return getParameter().convertTo0to1 (denormalised);
- }
-
- void setNormalisedValue (float value)
- {
- if (ignoreParameterChangedCallbacks)
- return;
-
- parameter.setValueNotifyingHost (value);
- }
-
- RangedAudioParameter& parameter;
- ListenerList<Listener> listeners;
- float unnormalisedValue{};
- std::atomic<bool> needsUpdate { true };
- bool listenersNeedCalling { true }, ignoreParameterChangedCallbacks { false };
- };
-
- //==============================================================================
- AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo,
- UndoManager* undoManagerToUse,
- const Identifier& valueTreeType,
- ParameterLayout parameterLayout)
- : AudioProcessorValueTreeState (processorToConnectTo, undoManagerToUse)
- {
- struct PushBackVisitor : ParameterLayout::Visitor
- {
- explicit PushBackVisitor (AudioProcessorValueTreeState& stateIn)
- : state (&stateIn) {}
-
- void visit (std::unique_ptr<RangedAudioParameter> param) const override
- {
- if (param == nullptr)
- {
- jassertfalse;
- return;
- }
-
- state->addParameterAdapter (*param);
- state->processor.addParameter (param.release());
- }
-
- void visit (std::unique_ptr<AudioProcessorParameterGroup> group) const override
- {
- if (group == nullptr)
- {
- jassertfalse;
- return;
- }
-
- for (const auto param : group->getParameters (true))
- {
- if (const auto rangedParam = dynamic_cast<RangedAudioParameter*> (param))
- {
- state->addParameterAdapter (*rangedParam);
- }
- else
- {
- // If you hit this assertion then you are attempting to add a parameter that is
- // not derived from RangedAudioParameter to the AudioProcessorValueTreeState.
- jassertfalse;
- }
- }
-
- state->processor.addParameterGroup (move (group));
- }
-
- AudioProcessorValueTreeState* state;
- };
-
- for (auto& item : parameterLayout.parameters)
- item->accept (PushBackVisitor (*this));
-
- state = ValueTree (valueTreeType);
- }
-
- AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um)
- : processor (p), undoManager (um)
- {
- startTimerHz (10);
- state.addListener (this);
- }
-
- AudioProcessorValueTreeState::~AudioProcessorValueTreeState() {}
-
- //==============================================================================
- RangedAudioParameter* AudioProcessorValueTreeState::createAndAddParameter (const String& paramID,
- const String& paramName,
- const String& labelText,
- NormalisableRange<float> range,
- float defaultVal,
- std::function<String (float)> valueToTextFunction,
- std::function<float (const String&)> textToValueFunction,
- bool isMetaParameter,
- bool isAutomatableParameter,
- bool isDiscreteParameter,
- AudioProcessorParameter::Category category,
- bool isBooleanParameter)
- {
- return createAndAddParameter (std::make_unique<Parameter> (paramID,
- paramName,
- labelText,
- range,
- defaultVal,
- std::move (valueToTextFunction),
- std::move (textToValueFunction),
- isMetaParameter,
- isAutomatableParameter,
- isDiscreteParameter,
- category,
- isBooleanParameter));
- }
-
- RangedAudioParameter* AudioProcessorValueTreeState::createAndAddParameter (std::unique_ptr<RangedAudioParameter> param)
- {
- // All parameters must be created before giving this manager a ValueTree state!
- jassert (! state.isValid());
-
- if (getParameter (param->paramID) != nullptr)
- return nullptr;
-
- addParameterAdapter (*param);
-
- processor.addParameter (param.get());
-
- return param.release();
- }
-
- //==============================================================================
- void AudioProcessorValueTreeState::addParameterAdapter (RangedAudioParameter& param)
- {
- adapterTable.emplace (param.paramID, std::make_unique<ParameterAdapter> (param));
- }
-
- AudioProcessorValueTreeState::ParameterAdapter* AudioProcessorValueTreeState::getParameterAdapter (StringRef paramID) const
- {
- auto it = adapterTable.find (paramID);
- return it == adapterTable.end() ? nullptr : it->second.get();
- }
-
- void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener)
- {
- if (auto* p = getParameterAdapter (paramID))
- p->addListener (listener);
- }
-
- void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener)
- {
- if (auto* p = getParameterAdapter (paramID))
- p->removeListener (listener);
- }
-
- Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const
- {
- if (auto* adapter = getParameterAdapter (paramID))
- if (adapter->tree.isValid())
- return adapter->tree.getPropertyAsValue (valuePropertyID, undoManager);
-
- return {};
- }
-
- NormalisableRange<float> AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept
- {
- if (auto* p = getParameterAdapter (paramID))
- return p->getRange();
-
- return {};
- }
-
- RangedAudioParameter* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept
- {
- if (auto adapter = getParameterAdapter (paramID))
- return &adapter->getParameter();
-
- return nullptr;
- }
-
- float* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept
- {
- if (auto* p = getParameterAdapter (paramID))
- return &p->getRawDenormalisedValue();
-
- return nullptr;
- }
-
- ValueTree AudioProcessorValueTreeState::copyState()
- {
- ScopedLock lock (valueTreeChanging);
- flushParameterValuesToValueTree();
- return state.createCopy();
- }
-
- void AudioProcessorValueTreeState::replaceState (const ValueTree& newState)
- {
- ScopedLock lock (valueTreeChanging);
-
- state = newState;
-
- if (undoManager != nullptr)
- undoManager->clearUndoHistory();
- }
-
- void AudioProcessorValueTreeState::setNewState (ValueTree vt)
- {
- jassert (vt.getParent() == state);
-
- if (auto* p = getParameterAdapter (vt.getProperty (idPropertyID).toString()))
- {
- p->tree = vt;
- p->setDenormalisedValue (p->tree.getProperty (valuePropertyID, p->getDenormalisedDefaultValue()));
- }
- }
-
- void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees()
- {
- ScopedLock lock (valueTreeChanging);
-
- for (auto& p : adapterTable)
- p.second->tree = ValueTree();
-
- for (const auto& child : state)
- setNewState (child);
-
- for (auto& p : adapterTable)
- {
- auto& adapter = *p.second;
-
- if (! adapter.tree.isValid())
- {
- adapter.tree = ValueTree (valueType);
- adapter.tree.setProperty (idPropertyID, adapter.getParameter().paramID, nullptr);
- state.appendChild (adapter.tree, nullptr);
- }
- }
-
- flushParameterValuesToValueTree();
- }
-
- void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
- {
- if (tree.hasType (valueType) && tree.getParent() == state)
- setNewState (tree);
- }
-
- void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree)
- {
- if (parent == state && tree.hasType (valueType))
- setNewState (tree);
- }
-
- void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree&, ValueTree&, int)
- {
- }
-
- void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v)
- {
- if (v == state)
- updateParameterConnectionsToChildTrees();
- }
-
- void AudioProcessorValueTreeState::valueTreeChildOrderChanged (ValueTree&, int, int) {}
- void AudioProcessorValueTreeState::valueTreeParentChanged (ValueTree&) {}
-
- bool AudioProcessorValueTreeState::flushParameterValuesToValueTree()
- {
- ScopedLock lock (valueTreeChanging);
-
- bool anyUpdated = false;
-
- for (auto& p : adapterTable)
- anyUpdated |= p.second->flushToTree (valuePropertyID, undoManager);
-
- return anyUpdated;
- }
-
- void AudioProcessorValueTreeState::timerCallback()
- {
- auto anythingUpdated = flushParameterValuesToValueTree();
-
- startTimer (anythingUpdated ? 1000 / 50
- : jlimit (50, 500, getTimerInterval() + 20));
- }
-
- //==============================================================================
- struct AttachedControlBase : public AudioProcessorValueTreeState::Listener,
- public AsyncUpdater
- {
- AttachedControlBase (AudioProcessorValueTreeState& s, const String& p)
- : state (s), paramID (p), lastValue (0)
- {
- state.addParameterListener (paramID, this);
- }
-
- void removeListener()
- {
- state.removeParameterListener (paramID, this);
- }
-
- void setNewDenormalisedValue (float newDenormalisedValue)
- {
- if (auto* p = state.getParameter (paramID))
- {
- const float newValue = state.getParameterRange (paramID)
- .convertTo0to1 (newDenormalisedValue);
-
- if (p->getValue() != newValue)
- p->setValueNotifyingHost (newValue);
- }
- }
-
- void sendInitialUpdate()
- {
- if (auto* v = state.getRawParameterValue (paramID))
- parameterChanged (paramID, *v);
- }
-
- void parameterChanged (const String&, float newValue) override
- {
- lastValue = newValue;
-
- if (MessageManager::getInstance()->isThisTheMessageThread())
- {
- cancelPendingUpdate();
- setValue (newValue);
- }
- else
- {
- triggerAsyncUpdate();
- }
- }
-
- void beginParameterChange()
- {
- if (auto* p = state.getParameter (paramID))
- {
- if (state.undoManager != nullptr)
- state.undoManager->beginNewTransaction();
-
- p->beginChangeGesture();
- }
- }
-
- void endParameterChange()
- {
- if (AudioProcessorParameter* p = state.getParameter (paramID))
- p->endChangeGesture();
- }
-
- void handleAsyncUpdate() override
- {
- setValue (lastValue);
- }
-
- virtual void setValue (float) = 0;
-
- AudioProcessorValueTreeState& state;
- String paramID;
- float lastValue;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttachedControlBase)
- };
-
- //==============================================================================
- struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private AttachedControlBase,
- private Slider::Listener
- {
- Pimpl (AudioProcessorValueTreeState& s, const String& p, Slider& sl)
- : AttachedControlBase (s, p), slider (sl), ignoreCallbacks (false)
- {
- NormalisableRange<float> range (state.getParameterRange (paramID));
-
- if (auto* param = state.getParameterAdapter (paramID))
- {
- slider.valueFromTextFunction = [param](const String& text) { return (double) param->getDenormalisedValueForText (text); };
- slider.textFromValueFunction = [param](double value) { return param->getTextForDenormalisedValue ((float) value); };
- slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getParameter().getDefaultValue()));
- }
-
- if (range.interval != 0.0f || range.skew != 1.0f)
- {
- slider.setRange (range.start, range.end, range.interval);
- slider.setSkewFactor (range.skew, range.symmetricSkew);
- }
- else
- {
- auto convertFrom0To1Function = [range](double currentRangeStart,
- double currentRangeEnd,
- double normalisedValue) mutable
- {
- range.start = (float) currentRangeStart;
- range.end = (float) currentRangeEnd;
- return (double) range.convertFrom0to1 ((float) normalisedValue);
- };
-
- auto convertTo0To1Function = [range](double currentRangeStart,
- double currentRangeEnd,
- double mappedValue) mutable
- {
- range.start = (float) currentRangeStart;
- range.end = (float) currentRangeEnd;
- return (double) range.convertTo0to1 ((float) mappedValue);
- };
-
- auto snapToLegalValueFunction = [range](double currentRangeStart,
- double currentRangeEnd,
- double valueToSnap) mutable
- {
- range.start = (float) currentRangeStart;
- range.end = (float) currentRangeEnd;
- return (double) range.snapToLegalValue ((float) valueToSnap);
- };
-
- slider.setNormalisableRange ({ (double) range.start,
- (double) range.end,
- convertFrom0To1Function,
- convertTo0To1Function,
- snapToLegalValueFunction });
- }
-
- sendInitialUpdate();
- slider.addListener (this);
- }
-
- ~Pimpl() override
- {
- slider.removeListener (this);
- removeListener();
- }
-
- void setValue (float newValue) override
- {
- const ScopedLock selfCallbackLock (selfCallbackMutex);
-
- {
- ScopedValueSetter<bool> svs (ignoreCallbacks, true);
- slider.setValue (newValue, sendNotificationSync);
- }
- }
-
- void sliderValueChanged (Slider* s) override
- {
- const ScopedLock selfCallbackLock (selfCallbackMutex);
-
- if ((! ignoreCallbacks) && (! ModifierKeys::currentModifiers.isRightButtonDown()))
- setNewDenormalisedValue ((float) s->getValue());
- }
-
- void sliderDragStarted (Slider*) override { beginParameterChange(); }
- void sliderDragEnded (Slider*) override { endParameterChange(); }
-
- Slider& slider;
- bool ignoreCallbacks;
- CriticalSection selfCallbackMutex;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
- };
-
- AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& s, const String& p, Slider& sl)
- : pimpl (new Pimpl (s, p, sl))
- {
- }
-
- AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() {}
-
- //==============================================================================
- struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl : private AttachedControlBase,
- private ComboBox::Listener
- {
- Pimpl (AudioProcessorValueTreeState& s, const String& p, ComboBox& c)
- : AttachedControlBase (s, p), combo (c), ignoreCallbacks (false)
- {
- sendInitialUpdate();
- combo.addListener (this);
- }
-
- ~Pimpl() override
- {
- combo.removeListener (this);
- removeListener();
- }
-
- void setValue (float newValue) override
- {
- const ScopedLock selfCallbackLock (selfCallbackMutex);
-
- if (state.getParameter (paramID) != nullptr)
- {
- auto normValue = state.getParameterRange (paramID)
- .convertTo0to1 (newValue);
- auto index = roundToInt (normValue * (combo.getNumItems() - 1));
-
- if (index != combo.getSelectedItemIndex())
- {
- ScopedValueSetter<bool> svs (ignoreCallbacks, true);
- combo.setSelectedItemIndex (index, sendNotificationSync);
- }
- }
- }
-
- void comboBoxChanged (ComboBox*) override
- {
- const ScopedLock selfCallbackLock (selfCallbackMutex);
-
- if (! ignoreCallbacks)
- {
- if (auto* p = state.getParameter (paramID))
- {
- auto newValue = (float) combo.getSelectedItemIndex() / (combo.getNumItems() - 1);
-
- if (p->getValue() != newValue)
- {
- beginParameterChange();
- p->setValueNotifyingHost (newValue);
- endParameterChange();
- }
- }
- }
- }
-
- ComboBox& combo;
- bool ignoreCallbacks;
- CriticalSection selfCallbackMutex;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
- };
-
- AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& s, const String& p, ComboBox& c)
- : pimpl (new Pimpl (s, p, c))
- {
- }
-
- AudioProcessorValueTreeState::ComboBoxAttachment::~ComboBoxAttachment() {}
-
- //==============================================================================
- struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl : private AttachedControlBase,
- private Button::Listener
- {
- Pimpl (AudioProcessorValueTreeState& s, const String& p, Button& b)
- : AttachedControlBase (s, p), button (b), ignoreCallbacks (false)
- {
- sendInitialUpdate();
- button.addListener (this);
- }
-
- ~Pimpl() override
- {
- button.removeListener (this);
- removeListener();
- }
-
- void setValue (float newValue) override
- {
- const ScopedLock selfCallbackLock (selfCallbackMutex);
-
- {
- ScopedValueSetter<bool> svs (ignoreCallbacks, true);
- button.setToggleState (newValue >= 0.5f, sendNotificationSync);
- }
- }
-
- void buttonClicked (Button* b) override
- {
- const ScopedLock selfCallbackLock (selfCallbackMutex);
-
- if (! ignoreCallbacks)
- {
- beginParameterChange();
- setNewDenormalisedValue (b->getToggleState() ? 1.0f : 0.0f);
- endParameterChange();
- }
- }
-
- Button& button;
- bool ignoreCallbacks;
- CriticalSection selfCallbackMutex;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
- };
-
- AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& s, const String& p, Button& b)
- : pimpl (new Pimpl (s, p, b))
- {
- }
-
- AudioProcessorValueTreeState::ButtonAttachment::~ButtonAttachment() {}
-
- #if JUCE_UNIT_TESTS
-
- static struct ParameterAdapterTests final : public UnitTest
- {
- ParameterAdapterTests() : UnitTest ("Parameter Adapter") {}
-
- void runTest() override
- {
- beginTest ("The default value is returned correctly");
- {
- const auto test = [&] (NormalisableRange<float> range, float value)
- {
- AudioParameterFloat param ({}, {}, range, value, {});
-
- AudioProcessorValueTreeState::ParameterAdapter adapter (param);
-
- expectEquals (adapter.getDenormalisedDefaultValue(), value);
- };
-
- test ({ -100, 100 }, 0);
- test ({ -2.5, 12.5 }, 10);
- }
-
- beginTest ("Denormalised parameter values can be retrieved");
- {
- const auto test = [&](NormalisableRange<float> range, float value)
- {
- AudioParameterFloat param ({}, {}, range, {}, {});
- AudioProcessorValueTreeState::ParameterAdapter adapter (param);
-
- adapter.setDenormalisedValue (value);
-
- expectEquals (adapter.getDenormalisedValue(), value);
- expectEquals (adapter.getRawDenormalisedValue(), value);
- };
-
- test ({ -20, -10 }, -15);
- test ({ 0, 7.5 }, 2.5);
- }
-
- beginTest ("Floats can be converted to text");
- {
- const auto test = [&](NormalisableRange<float> range, float value, String expected)
- {
- AudioParameterFloat param ({}, {}, range, {}, {});
- AudioProcessorValueTreeState::ParameterAdapter adapter (param);
-
- expectEquals (adapter.getTextForDenormalisedValue (value), expected);
- };
-
- test ({ -100, 100 }, 0, "0.0000000");
- test ({ -2.5, 12.5 }, 10, "10.0000000");
- test ({ -20, -10 }, -15, "-15.0000000");
- test ({ 0, 7.5 }, 2.5, "2.5000000");
- }
-
- beginTest ("Text can be converted to floats");
- {
- const auto test = [&](NormalisableRange<float> range, String text, float expected)
- {
- AudioParameterFloat param ({}, {}, range, {}, {});
- AudioProcessorValueTreeState::ParameterAdapter adapter (param);
-
- expectEquals (adapter.getDenormalisedValueForText (text), expected);
- };
-
- test ({ -100, 100 }, "0.0", 0);
- test ({ -2.5, 12.5 }, "10.0", 10);
- test ({ -20, -10 }, "-15.0", -15);
- test ({ 0, 7.5 }, "2.5", 2.5);
- }
- }
- } parameterAdapterTests;
-
- namespace
- {
- template <typename ValueType>
- inline bool operator== (const NormalisableRange<ValueType>& a,
- const NormalisableRange<ValueType>& b)
- {
- return std::tie (a.start, a.end, a.interval, a.skew, a.symmetricSkew)
- == std::tie (b.start, b.end, b.interval, b.skew, b.symmetricSkew);
- }
-
- template <typename ValueType>
- inline bool operator!= (const NormalisableRange<ValueType>& a,
- const NormalisableRange<ValueType>& b)
- {
- return ! (a == b);
- }
- } // namespace
-
- static class AudioProcessorValueTreeStateTests final : public UnitTest
- {
- private:
- using Parameter = AudioProcessorValueTreeState::Parameter;
- using ParameterGroup = AudioProcessorParameterGroup;
- using ParameterLayout = AudioProcessorValueTreeState::ParameterLayout;
-
- class TestAudioProcessor : public AudioProcessor
- {
- public:
- TestAudioProcessor() = default;
-
- explicit TestAudioProcessor (ParameterLayout layout)
- : state (*this, nullptr, "state", std::move (layout)) {}
-
- const String getName() const override { return {}; }
- void prepareToPlay (double, int) override {}
- void releaseResources() override {}
- void processBlock (AudioBuffer<float>&, MidiBuffer&) override {}
- double getTailLengthSeconds() const override { return {}; }
- bool acceptsMidi() const override { return {}; }
- bool producesMidi() const override { return {}; }
- AudioProcessorEditor* createEditor() override { return {}; }
- bool hasEditor() const override { return {}; }
- int getNumPrograms() override { return 1; }
- int getCurrentProgram() override { return {}; }
- void setCurrentProgram (int) override {}
- const String getProgramName (int) override { return {}; }
- void changeProgramName (int, const String&) override {}
- void getStateInformation (MemoryBlock&) override {}
- void setStateInformation (const void*, int) override {}
-
- AudioProcessorValueTreeState state { *this, nullptr };
- };
-
- struct Listener final : public AudioProcessorValueTreeState::Listener
- {
- void parameterChanged (const String& idIn, float valueIn) override
- {
- id = idIn;
- value = valueIn;
- }
-
- String id;
- float value{};
- };
-
- public:
- AudioProcessorValueTreeStateTests() : UnitTest ("Audio Processor Value Tree State", "AudioProcessor parameters") {}
-
- void runTest() override
- {
- ScopedJuceInitialiser_GUI scopedJuceInitialiser_gui;
-
- beginTest ("After calling createAndAddParameter, the number of parameters increases by one");
- {
- TestAudioProcessor proc;
-
- proc.state.createAndAddParameter (std::make_unique<Parameter> (String(), String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
-
- expectEquals (proc.getParameters().size(), 1);
- }
-
- beginTest ("After creating a normal named parameter, we can later retrieve that parameter");
- {
- TestAudioProcessor proc;
-
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
-
- expect (proc.state.getParameter (key) == param);
- }
-
- beginTest ("After construction, the value tree has the expected format");
- {
- TestAudioProcessor proc ({
- std::make_unique<AudioProcessorParameterGroup> ("", "", "",
- std::make_unique<AudioParameterBool> ("a", "", false),
- std::make_unique<AudioParameterFloat> ("b", "", NormalisableRange<float>{}, 0.0f)),
- std::make_unique<AudioProcessorParameterGroup> ("", "", "",
- std::make_unique<AudioParameterInt> ("c", "", 0, 1, 0),
- std::make_unique<AudioParameterChoice> ("d", "", StringArray { "foo", "bar" }, 0)) });
-
- const auto valueTree = proc.state.copyState();
-
- expectEquals (valueTree.getNumChildren(), 4);
-
- for (auto child : valueTree)
- {
- expect (child.hasType ("PARAM"));
- expect (child.hasProperty ("id"));
- expect (child.hasProperty ("value"));
- }
- }
-
- beginTest ("Meta parameters can be created");
- {
- TestAudioProcessor proc;
-
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr, true));
-
- expect (param->isMetaParameter());
- }
-
- beginTest ("Automatable parameters can be created");
- {
- TestAudioProcessor proc;
-
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr, false, true));
-
- expect (param->isAutomatable());
- }
-
- beginTest ("Discrete parameters can be created");
- {
- TestAudioProcessor proc;
-
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr, false, false, true));
-
- expect (param->isDiscrete());
- }
-
- beginTest ("Custom category parameters can be created");
- {
- TestAudioProcessor proc;
-
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr, false, false, false,
- AudioProcessorParameter::Category::inputMeter));
-
- expect (param->category == AudioProcessorParameter::Category::inputMeter);
- }
-
- beginTest ("Boolean parameters can be created");
- {
- TestAudioProcessor proc;
-
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr, false, false, false,
- AudioProcessorParameter::Category::genericParameter, true));
-
- expect (param->isBoolean());
- }
-
- beginTest ("After creating a custom named parameter, we can later retrieve that parameter");
- {
- const auto key = "id";
- auto param = std::make_unique<AudioParameterBool> (key, "", false);
- const auto paramPtr = param.get();
-
- TestAudioProcessor proc (std::move (param));
-
- expect (proc.state.getParameter (key) == paramPtr);
- }
-
- beginTest ("After adding a normal parameter that already exists, the AudioProcessor parameters are unchanged");
- {
- TestAudioProcessor proc;
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
-
- proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
-
- expectEquals (proc.getParameters().size(), 1);
- expect (proc.getParameters().getFirst() == param);
- }
-
- beginTest ("After setting a parameter value, that value is reflected in the state");
- {
- TestAudioProcessor proc;
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
-
- const auto value = 0.5f;
- param->setValueNotifyingHost (value);
-
- expectEquals (*proc.state.getRawParameterValue (key), value);
- }
-
- beginTest ("After adding an APVTS::Parameter, its value is the default value");
- {
- TestAudioProcessor proc;
- const auto key = "id";
- const auto value = 5.0f;
-
- proc.state.createAndAddParameter (std::make_unique<Parameter> (
- key,
- String(),
- String(),
- NormalisableRange<float> (0.0f, 100.0f, 10.0f),
- value,
- nullptr,
- nullptr));
-
- expectEquals (*proc.state.getRawParameterValue (key), value);
- }
-
- beginTest ("Listeners receive notifications when parameters change");
- {
- Listener listener;
- TestAudioProcessor proc;
- const auto key = "id";
- const auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
- proc.state.addParameterListener (key, &listener);
-
- const auto value = 0.5f;
- param->setValueNotifyingHost (value);
-
- expectEquals (listener.id, String { key });
- expectEquals (listener.value, value);
- }
-
- beginTest ("Bool parameters have a range of 0-1");
- {
- const auto key = "id";
-
- TestAudioProcessor proc (std::make_unique<AudioParameterBool> (key, "", false));
-
- expect (proc.state.getParameterRange (key) == NormalisableRange<float> (0.0f, 1.0f, 1.0f));
- }
-
- beginTest ("Float parameters retain their specified range");
- {
- const auto key = "id";
- const auto range = NormalisableRange<float> { -100, 100, 0.7f, 0.2f, true };
-
- TestAudioProcessor proc (std::make_unique<AudioParameterFloat> (key, "", range, 0.0f));
-
- expect (proc.state.getParameterRange (key) == range);
- }
-
- beginTest ("Int parameters retain their specified range");
- {
- const auto key = "id";
- const auto min = -27;
- const auto max = 53;
-
- TestAudioProcessor proc (std::make_unique<AudioParameterInt> (key, "", min, max, 0));
-
- expect (proc.state.getParameterRange (key) == NormalisableRange<float> (float (min), float (max)));
- }
-
- beginTest ("Choice parameters retain their specified range");
- {
- const auto key = "id";
- const auto choices = StringArray { "", "", "" };
-
- TestAudioProcessor proc (std::make_unique<AudioParameterChoice> (key, "", choices, 0));
-
- expect (proc.state.getParameterRange (key) == NormalisableRange<float> (0.0f, (float) (choices.size() - 1)));
- expect (proc.state.getParameter (key)->getNumSteps() == choices.size());
- }
-
- beginTest ("When the parameter value is changed, normal parameter values are updated");
- {
- TestAudioProcessor proc;
- const auto key = "id";
- const auto initialValue = 0.2f;
- auto param = proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- initialValue, nullptr, nullptr));
- proc.state.state = ValueTree { "state" };
-
- auto value = proc.state.getParameterAsValue (key);
- expectEquals (float (value.getValue()), initialValue);
-
- const auto newValue = 0.75f;
- value = newValue;
-
- expectEquals (param->getValue(), newValue);
- expectEquals (*proc.state.getRawParameterValue (key), newValue);
- }
-
- beginTest ("When the parameter value is changed, custom parameter values are updated");
- {
- const auto key = "id";
- const auto choices = StringArray ("foo", "bar", "baz");
- auto param = std::make_unique<AudioParameterChoice> (key, "", choices, 0);
- const auto paramPtr = param.get();
- TestAudioProcessor proc (std::move (param));
-
- const auto newValue = 2.0f;
- auto value = proc.state.getParameterAsValue (key);
- value = newValue;
-
- expectEquals (paramPtr->getCurrentChoiceName(), choices[int (newValue)]);
- expectEquals (*proc.state.getRawParameterValue (key), newValue);
- }
-
- beginTest ("When the parameter value is changed, listeners are notified");
- {
- Listener listener;
- TestAudioProcessor proc;
- const auto key = "id";
- proc.state.createAndAddParameter (std::make_unique<Parameter> (key, String(), String(), NormalisableRange<float>(),
- 0.0f, nullptr, nullptr));
- proc.state.addParameterListener (key, &listener);
- proc.state.state = ValueTree { "state" };
-
- const auto newValue = 0.75f;
- proc.state.getParameterAsValue (key) = newValue;
-
- expectEquals (listener.value, newValue);
- expectEquals (listener.id, String { key });
- }
-
- beginTest ("When the parameter value is changed, listeners are notified");
- {
- const auto key = "id";
- const auto choices = StringArray { "foo", "bar", "baz" };
- Listener listener;
- TestAudioProcessor proc (std::make_unique<AudioParameterChoice> (key, "", choices, 0));
- proc.state.addParameterListener (key, &listener);
-
- const auto newValue = 2.0f;
- proc.state.getParameterAsValue (key) = newValue;
-
- expectEquals (listener.value, newValue);
- expectEquals (listener.id, String (key));
- }
- }
- } audioProcessorValueTreeStateTests;
-
- #endif
-
- } // namespace juce
|