Resources on SliderAttachment and ParameterAttachment

Hello,

I am trying to have a slider which clicks between 0 and 7 make visible one of eight groups of six sliders. These sliders will all control parameters for the comb filters in the built-in reverb module. 0 selects group 0, 1 selects 1, and so on… these sliders are all added to a flexbox (two actually). At the moment it seems the flexbox will properly display the banks just by toggling their visibility, but I haven’t fully tested that.

My main problem right now is understanding how to attach a GUI listener to the parameter changing. I created a SliderAttachment like so:

    AudioProcessorValueTreeState::SliderAttachment combIDAttachment;
    // And these are my other variables...
    AudioProcessorValueTreeState& valueTreeState;
    LabeledSlider combID;

I called it in my editor’s initialization list like so:

      combIDAttachment(valueTreeState, "combID", combID.getModSlider()),

All well and good. However I then go to create the callback:

    combIDAttachment.onParameterChanged = [this]() {
        for (int i = Constants::Parameter::combIDStart;
            i <= Constants::Parameter::combIDEnd;
            i += Constants::Parameter::combIDInterval)
        {
            DBG("combID parameter changed callback");
            // I need the new value of combID, but I don't know how it's passed in
            // Call it "newValue"
            float newValue = 0;
            bool visible = (i == static_cast<int>(newValue) ? true : false);
            combTap[i].setVisible(visible);;
            combDampingL[i].setVisible(visible);;
            combDampingR[i].setVisible(visible);;
            combFeedbackL[i].setVisible(visible);;
            combFeedbackR[i].setVisible(visible);;
        }
    }

And I’m warned that onParameterChanged does not exist; of course looking at the SliderAttachment documentation, it’s true.

This leads me to the ParameterAttachment class reference which leads me to think I may be totally on the wrong path.

The AVPTS tutorial shows you how to add attachments - of the Slider and Button type, not Parameter - but it doesn’t show you how to add a callback.

I see of course the function callback is simply a parameter in the ParameterAttachment constructor, but is that the right attachment to use? I have read a few forum threads on issues with both types, but frequently people don’t even include the callback code snippets…! Is there something obvious I’m missing?

Thank you :slight_smile:

I would suggest inheriting from juce::AudioProcessorValueTreeState::Listener instead of modifying the attachment. After that, you can put the same callback code into parameterChanged().

replace slider and attachment with each other. the slider must exist before the attachment because the attachment’s constructor wants a reference to the slider.

idk if it can work the way you approach it with the callback of the attachment. it would make sense to me. but you could also just use a timerCallback to update your components’ visibility

Thanks for the suggestion! It’s not ideal in my case because I have lots of LabeledSliders but only one that requires a callback. Of course, I could create a LabeledSliderWithCallback class - but that gets harder to maintain.

The slider’s constructor is called before the attachment’s constructor. Here’s both in the order they appear:

      combID(this),
      combIDAttachment(valueTreeState, "combID", combID.getModSlider()),

comID is the slider, and combIDAttachment is the attachment.

I see how a regularly polling timer function could handle updating the GUI visibility with no impact on the user experience. This is another good option if nothing else pans out.


I am going to look further into the ParameterAttachment class because I really think I just started down the wrong path. I would like an elegant solution that is easy to maintain, including in my mental map of the code, and doesn’t impact performance.

So it turned out to be pretty easy using ParameterAttachment. I struggled just a little with declaring and referencing the lambda function, so I’ll share how I did that for anyone else. But first, the result:

2023_10_23-comb_selector

Both in the header and source, the callback is written like any other function:

// Header
    void onCombIDChanged(float newValue);

The sliders are just stored as an array and the callback function sets the visibility of all sliders in each array with a ternary bool:

// Source
void GlobeLovelerEditor::onCombIDChanged(float newValue) {
    //DBG("onCombIDChanged callback, new value " + std::to_string(newValue));
    for (int i = Constants::Parameter::combIDStart;
        i <= Constants::Parameter::combIDEnd;
        i += Constants::Parameter::combIDInterval)
    {
        bool visible = (i == static_cast<int>(newValue) ? true : false);
        //DBG("visibility of " + std::to_string(i) + ": " + std::to_string(visible));
        combTap[i].setVisible(visible);;
        combDampingL[i].setVisible(visible);;
        combDampingR[i].setVisible(visible);;
        combFeedbackL[i].setVisible(visible);;
        combFeedbackR[i].setVisible(visible);;
    }
}

The initializer for the ParameterAttachment is where the lambda syntax comes into play:

    combIDAttachment(*vts.getParameter("combID"), 
        [this](float newValue) { onCombIDChanged(newValue); }),

There is still much work to be done to connect the comb filter internals to these sliders, but I’m on the way! Thank you again for the suggestions @zsliu98 and @Mrugalla.

edit: One thing I thought strange was that none of the parameters updated their values in the saved xml parameter file until I had jiggled them. Any insight as to why that is, or if I’m missing a step in initializing new parameters? Their default values are set during initialization, so why wouldn’t they be saved until I had touched them?

Your methods looks wonderful. I will definitely try ParameterAttachment next time to save myself from adding/removing listeners.

One thing I thought strange was that none of the parameters updated their values in the saved xml parameter file until I had jiggled them.

Which XML do you refer to? The one saved by getStateInformation? Why would a slider updates APVTS before you touch it? If you are using APVTS, the example in tutorial would work in most cases.

1 Like

Thanks and I’m glad it could help you!!

Yes, the getStateInformation XML. As for why a slider would update it: Sorry for my poor phrasing. It’s the AudioParameterFloats I’m adding, with default values, that I expected to fill out the APVTS.

I’m not able to replicate the issue now despite deleting the AppData/app.settings file and saving the default AVPTS, so perhaps I was ahead of myself with testing. It seems to work as expected now.

Hey all I’m running into some weird behavior while accessing VTS parameter values from the editor during initialization.

I want to run my show/hide method on startup so that the correct bank of sliders displays, according to whatever combID is in the saved state.

This is the range and interval of my combID slider:

        constexpr int combIDStart = 0;
        constexpr int combIDEnd = 7;
        constexpr int combIDInterval = 1;

This is the initialization of visibility:

    float combID = valueTreeState.getParameter("combID")->getValue();
    onCombIDChanged(combID);

But combID->getValue is evaluating as:
{0, 0.142857, 0.285714, 0.428571, 0.571429, 0.714286, 0.857143, 1}
when the program exits with the combID {0, 1, … 7}.

Why is the value normalized to its range when it’s saved, and loaded on startup?

It’s an easy enough fix, just have to multiply by seven, but it’s certainly confusing to me.

getValue() returns the normalized value of a parameter. that’s why every RangedAudioParameter has a NormalisableRange object for denormalizing the value as well as checking if the denormalized value is legal. Not sure if you can access the range object from all the different parameter subclasses, but they surely always offer some method for convertingFrom0to1() or so

Jeez… thank you! I’ve seen many discussions about that but this is my first time encountering it. Much appreciate the info even if it was a silly ask!

1 Like