/* ============================================================================== 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 { struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParameterWithID, private ValueTree::Listener { Parameter (AudioProcessorValueTreeState& s, const String& parameterID, const String& paramName, const String& labelText, NormalisableRange r, float defaultVal, std::function valueToText, std::function textToValue, bool meta, bool automatable, bool discrete, AudioProcessorParameter::Category category, bool boolean) : AudioProcessorParameterWithID (parameterID, paramName, labelText, category), owner (s), valueToTextFunction (valueToText), textToValueFunction (textToValue), range (r), value (defaultVal), defaultValue (defaultVal), listenersNeedCalling (true), isMetaParam (meta), isAutomatableParam (automatable), isDiscreteParam (discrete), isBooleanParam (boolean) { value = defaultValue; state.addListener (this); } ~Parameter() { // should have detached all callbacks before destroying the parameters! jassert (listeners.size() <= 1); } float getValue() const override { return range.convertTo0to1 (value); } float getDefaultValue() const override { return range.convertTo0to1 (defaultValue); } float getValueForText (const String& text) const override { return range.convertTo0to1 (textToValueFunction != nullptr ? textToValueFunction (text) : text.getFloatValue()); } String getText (float v, int length) const override { return valueToTextFunction != nullptr ? valueToTextFunction (range.convertFrom0to1 (v)) : AudioProcessorParameter::getText (v, length); } int getNumSteps() const override { if (range.interval > 0) return (static_cast ((range.end - range.start) / range.interval) + 1); return AudioProcessor::getDefaultNumParameterSteps(); } void setValue (float newValue) override { newValue = range.snapToLegalValue (range.convertFrom0to1 (newValue)); if (value != newValue || listenersNeedCalling) { value = newValue; listeners.call ([=] (AudioProcessorValueTreeState::Listener& l) { l.parameterChanged (paramID, value); }); listenersNeedCalling = false; needsUpdate = true; } } void setNewState (const ValueTree& v) { state = v; updateFromValueTree(); } void setUnnormalisedValue (float newUnnormalisedValue) { if (value != newUnnormalisedValue) { const float newValue = range.convertTo0to1 (newUnnormalisedValue); setValueNotifyingHost (newValue); } } void updateFromValueTree() { setUnnormalisedValue (state.getProperty (owner.valuePropertyID, defaultValue)); } void copyValueToValueTree() { if (auto* valueProperty = state.getPropertyPointer (owner.valuePropertyID)) { if ((float) *valueProperty != value) { ScopedValueSetter svs (ignoreParameterChangedCallbacks, true); state.setProperty (owner.valuePropertyID, value, owner.undoManager); } } else { state.setProperty (owner.valuePropertyID, value, nullptr); } } void valueTreePropertyChanged (ValueTree&, const Identifier& property) override { if (ignoreParameterChangedCallbacks) return; if (property == owner.valuePropertyID) updateFromValueTree(); } void valueTreeChildAdded (ValueTree&, ValueTree&) override {} void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} void valueTreeChildOrderChanged (ValueTree&, int, int) override {} void valueTreeParentChanged (ValueTree&) override {} static Parameter* getParameterForID (AudioProcessor& processor, StringRef paramID) noexcept { for (auto* ap : processor.getParameters()) { // When using this class, you must allow it to manage all the parameters in your AudioProcessor, and // not add any parameter objects of other types! jassert (dynamic_cast (ap) != nullptr); auto* p = static_cast (ap); if (paramID == p->paramID) return p; } return nullptr; } bool isMetaParameter() const override { return isMetaParam; } bool isAutomatable() const override { return isAutomatableParam; } bool isDiscrete() const override { return isDiscreteParam; } bool isBoolean() const override { return isBooleanParam; } AudioProcessorValueTreeState& owner; ValueTree state; ListenerList listeners; std::function valueToTextFunction; std::function textToValueFunction; NormalisableRange range; float value, defaultValue; std::atomic needsUpdate { true }; bool listenersNeedCalling; const bool isMetaParam, isAutomatableParam, isDiscreteParam, isBooleanParam; bool ignoreParameterChangedCallbacks = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter) }; //============================================================================== AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um) : processor (p), undoManager (um) { startTimerHz (10); state.addListener (this); } AudioProcessorValueTreeState::~AudioProcessorValueTreeState() {} AudioProcessorParameterWithID* AudioProcessorValueTreeState::createAndAddParameter (const String& paramID, const String& paramName, const String& labelText, NormalisableRange r, float defaultVal, std::function valueToTextFunction, std::function textToValueFunction, bool isMetaParameter, bool isAutomatableParameter, bool isDiscreteParameter, AudioProcessorParameter::Category category, bool isBooleanParameter) { // All parameters must be created before giving this manager a ValueTree state! jassert (! state.isValid()); Parameter* p = new Parameter (*this, paramID, paramName, labelText, r, defaultVal, valueToTextFunction, textToValueFunction, isMetaParameter, isAutomatableParameter, isDiscreteParameter, category, isBooleanParameter); processor.addParameter (p); return p; } void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener) { if (Parameter* p = Parameter::getParameterForID (processor, paramID)) p->listeners.add (listener); } void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener) { if (Parameter* p = Parameter::getParameterForID (processor, paramID)) p->listeners.remove (listener); } Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const { if (Parameter* p = Parameter::getParameterForID (processor, paramID)) return p->state.getPropertyAsValue (valuePropertyID, undoManager); return {}; } NormalisableRange AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept { if (Parameter* p = Parameter::getParameterForID (processor, paramID)) return p->range; return NormalisableRange(); } AudioProcessorParameterWithID* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept { return Parameter::getParameterForID (processor, paramID); } float* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept { if (Parameter* p = Parameter::getParameterForID (processor, paramID)) return &(p->value); 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(); } ValueTree AudioProcessorValueTreeState::getOrCreateChildValueTree (const String& paramID) { ValueTree v (state.getChildWithProperty (idPropertyID, paramID)); if (! v.isValid()) { v = ValueTree (valueType); v.setProperty (idPropertyID, paramID, nullptr); state.appendChild (v, nullptr); } return v; } void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees() { ScopedLock lock (valueTreeChanging); for (auto* param : processor.getParameters()) { jassert (dynamic_cast (param) != nullptr); auto* p = static_cast (param); p->setNewState (getOrCreateChildValueTree (p->paramID)); } } void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier& property) { if (property == idPropertyID && tree.hasType (valueType) && tree.getParent() == state) updateParameterConnectionsToChildTrees(); } void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree) { if (parent == state && tree.hasType (valueType)) if (auto* param = Parameter::getParameterForID (processor, tree.getProperty (idPropertyID).toString())) param->setNewState (getOrCreateChildValueTree (param->paramID)); } void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree& tree, int) { if (parent == state && tree.hasType (valueType)) if (auto* param = Parameter::getParameterForID (processor, tree.getProperty (idPropertyID).toString())) param->setNewState (getOrCreateChildValueTree (param->paramID)); } 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 anythingUpdated = false; for (auto* ap : processor.getParameters()) { jassert (dynamic_cast (ap) != nullptr); auto* p = static_cast (ap); bool needsUpdateTestValue = true; if (p->needsUpdate.compare_exchange_strong (needsUpdateTestValue, false)) { p->copyValueToValueTree(); anythingUpdated = true; } } return anythingUpdated; } void AudioProcessorValueTreeState::timerCallback() { auto anythingUpdated = flushParameterValuesToValueTree(); startTimer (anythingUpdated ? 1000 / 50 : jlimit (50, 500, getTimerInterval() + 20)); } AudioProcessorValueTreeState::Listener::Listener() {} AudioProcessorValueTreeState::Listener::~Listener() {} //============================================================================== 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 setNewUnnormalisedValue (float newUnnormalisedValue) { if (AudioProcessorParameter* p = state.getParameter (paramID)) { const float newValue = state.getParameterRange (paramID) .convertTo0to1 (newUnnormalisedValue); if (p->getValue() != newValue) p->setValueNotifyingHost (newValue); } } void sendInitialUpdate() { if (float* 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 (AudioProcessorParameter* 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 range (state.getParameterRange (paramID)); if (range.interval != 0 || range.skew != 0) { 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 }); } if (auto* param = dynamic_cast (state.getParameter (paramID))) { if (param->textToValueFunction != nullptr) slider.valueFromTextFunction = [param] (const String& text) { return (double) param->textToValueFunction (text); }; if (param->valueToTextFunction != nullptr) slider.textFromValueFunction = [param] (double value) { return param->valueToTextFunction ((float) value); }; slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getDefaultValue())); } sendInitialUpdate(); slider.addListener (this); } ~Pimpl() { slider.removeListener (this); removeListener(); } void setValue (float newValue) override { const ScopedLock selfCallbackLock (selfCallbackMutex); { ScopedValueSetter svs (ignoreCallbacks, true); slider.setValue (newValue, sendNotificationSync); } } void sliderValueChanged (Slider* s) override { const ScopedLock selfCallbackLock (selfCallbackMutex); if ((! ignoreCallbacks) && (! ModifierKeys::currentModifiers.isRightButtonDown())) setNewUnnormalisedValue ((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() { combo.removeListener (this); removeListener(); } void setValue (float newValue) override { const ScopedLock selfCallbackLock (selfCallbackMutex); { ScopedValueSetter svs (ignoreCallbacks, true); combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync); } } void comboBoxChanged (ComboBox* comboBox) override { const ScopedLock selfCallbackLock (selfCallbackMutex); if (! ignoreCallbacks) { beginParameterChange(); setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f); 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() { button.removeListener (this); removeListener(); } void setValue (float newValue) override { const ScopedLock selfCallbackLock (selfCallbackMutex); { ScopedValueSetter svs (ignoreCallbacks, true); button.setToggleState (newValue >= 0.5f, sendNotificationSync); } } void buttonClicked (Button* b) override { const ScopedLock selfCallbackLock (selfCallbackMutex); if (! ignoreCallbacks) { beginParameterChange(); setNewUnnormalisedValue (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() {} } // namespace juce