Hi @kunz,
Oh, I thought @JeffMcClintock was suggesting that all those parameter attachments were adding individual timers running simultaneously, maybe I misunderstood. My idea was that the “one timer to rule them all” pattern could be already provided by the APVTS.
About the rest, I agree with you and I don’t think I’ll end up reiventing the wheel (mostly because I’m not confident enough to do something that no one seems to be doing). About parameter ranges though, I had a go at implementing a generic component that binds a slider to a juce::Value
but will still set up the correct behavior in case the user specifies that the value is actually a processor parameter:
class SliderAttachedToValue : public juce::Component
{
public:
SliderAttachedToValue (juce::ValueTree v, isParam = false, juce::AudioProcessorValueTreeState * apvts = nullptr)
{
if (isParam && apvts != nullptr) // check if the user explicitly specifies that we're about to refer to an audioprocessor param
{
auto paramID = v.getProperty("id").toString(); // if it's a param, the id is the parameter ID (that's how the APVTS sets it)
auto& correspondingParam = *apvts->getParameter(paramID); // so we use it get the actual param from the apvts
configureSliderForParam(correspondingParam, slider); // we set all ranges, etc, by querying them from the parameter
label.setText(correspondingParam.name, juce::dontSendNotification);
}
auto& sliderValue = slider.getValueObject(); // underlying value of the slider to bind to
sliderValue.referTo(v.getPropertyAsValue("value", nullptr)); // bind to the actual value stored by this tree, whether it's a value corresponding to a param or a value for something else
addAllAndMakeVisible (*this, slider, label);
label.attachToComponent (&slider, false);
label.setJustificationType (juce::Justification::centred);
}
void resized() override { slider.setBounds (getLocalBounds().reduced (0, 40)); }
private:
juce::Slider slider { juce::Slider::RotaryVerticalDrag, juce::Slider::TextBoxBelow };
juce::Label label;
};
The code for configureAttachmentForParam
is basically stolen from the SliderAttachment
class of JUCE, I just copy-pasted it and there was nothing to do. The only thing missing are the begin/end gesture stuff. I guess that could be added easily.
This slider configures everything according to the parameter but is not directly listening to it, everything will always be on the main thread based on the ValueTree. I’ve tested making a simple UI with two sliders, one with a normal parameter attachment and another with my custom slider, both referring to the same underlying parameter. I get the expected behavior. Namely, if I start modifying the parameter, my custom slider will react with an initial lag if I wasn’t previously moving the parameter. This is expected since the APVTS is slowing down its own timer as soon as parameters stop changing. However, for the duration of a gesture, the two sliders are pretty much in sync (the responsiveness is actually 20ms if we believe the APVTS code, fine for UI I guess). And anyway this lag will only happen in the case of “user changes parameter from the DAW”, there shouldn’t be any lag if something’s changed from within the plugin UI. That will all be happening directly on the ValueTree, like it would for a non-plugin app. Only the one-way connection “parameters → tree” relies on the timer.
To me, processor parameters are simply a portion of the state that the DAW can see. In the Model-View-Controller design pattern the APVTS ValueTree seems to be the perfect “Controller”, but we’re missing out on this. The plugin UI is one view and the DAW is just another view on a restricted part of the state. In that sense, what bothers me is that the normal way is setting up weird paths “view (DAW) ↔ view (plugin UI)” all over the place, with both views possibly running on different threads.That’s why the ParameterAttachment
class must implement thread-safe mechanisms under the hood. The normal way seems to be missing out on the classic “view (DAW) ↔ controller (APVTS tree) ↔ view (plugin UI)” option.
Anyway I won’t be using this because as you said it’s doesn’t seem to be really the intended design of the APVTS and I might be overlooking some performance thing. That was interesting to try out anyway and I’m curious of people’s thoughts on all this 