Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1181 lines (944 sloc) 44.2 KB
/*
==============================================================================
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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.