Mapping a AudioParameterChoice to a Radio Button Group sensecheck

Hi,
As per the title I’m trying to find a neat / sensible way to map an AudioParameterChoice to a group of radio buttons.

Inspired by the ParameterSlider code in the JuceDemoPlugin project I came up with the code below.
It creates a GroupComponent with ToggleButtoms as required as subcomponents to the group.

I have a couple of questions:

  1. Can I get away without hashing the paramID to make a RadioGroupId? It kinda looked like it was only scoped to the parent Component (e.g. the GroupComponent in this case) but I’m not 100% sure.

  2. C++ isn’t my first language can you spot any lurking howlers? Put another way, is there a better way to do this?

Thanks in advance,
Dave


class MainUI::ParameterButtonGroup : public GroupComponent,
    private Timer, private juce::Button::Listener
{
public:
    ParameterButtonGroup (AudioParameterChoice& apc)
        : GroupComponent (((AudioProcessorParameter&)apc).getName(256), ((AudioProcessorParameter&)apc).getName(16)), param(apc)
    {
        ToggleButton* tb;
        DefaultHashFunctions dhf;

        for (int i = 0; i < apc.choices.size(); i++) 
        {
            toggleButtons.add(tb = new ToggleButton (apc.choices[i]));
            tb->setRadioGroupId(dhf.generateHash(apc.paramID, 65535));
            addAndMakeVisible(tb);
            tb->addListener(this);
        }
        startTimerHz(30);
        updateButtonDisplay();
    }

    void buttonClicked (Button*) override 
    {
        //from the listener, handle updating the host param from the buttons
        for (int i = 0; i < toggleButtons.size(); i++) {
            if (toggleButtons[i]->getToggleState()) {
                param.setValueNotifyingHost((float)i/(toggleButtons.size()));
                lastValue = i;
                return;
            }
        }
    }

    void timerCallback() override { updateButtonDisplay();}

    void updateButtonDisplay()
    {
        const int newValue = param.getIndex();
        if (newValue != lastValue && !isMouseButtonDown())
        {
            toggleButtons[newValue]->setToggleState(true, NotificationType::dontSendNotification);
        }
        lastValue = newValue;
    }


    void resized() override
    {
        // set toggle button positions within group
        Rectangle<int> ctrls = getLocalBounds().reduced(5,5);
        ctrls.removeFromTop(5);
        float heightPerButton = (float)ctrls.getHeight() / toggleButtons.size();
        for (int i = 0; i < toggleButtons.size(); i++) {
            toggleButtons[i]->setBounds(ctrls.removeFromBottom(heightPerButton));
        }
    }

    AudioParameterChoice& param;
    OwnedArray<ToggleButton> toggleButtons;
private:
    int lastValue;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ParameterButtonGroup)
};

Hi, I am currently struggling with the same issue here. I have one AudioParameterChoice that’s supposed to be managed by 4 mutually exclusive textButtons whose setClickingTogglesState mode i turned on (ticks don’t look good with what i’m trying to achieve and this way to do it matches exactly what I was going for). I am using setRadioGroupId to make them mutually exclusive.

However no matter how I set it up there are issues. Either the buttons don’t display the correct value or the AudioProcessorValueTreeState parameter doesn’t follow up, or the buttons don’t stay down after I clicked, etc.

More than fixing my code, i’d like to see a complete demonstration or example of how to set up radio buttons for a single parameter so I can decline and adapt it to the situations I could get myself into.

If anyone knows how to set that up, please show me. Otherwise I might be back in a few days if I figure it out myself.

In the meantime here’s the workaround setup that I found worked the best.

  1. create a slider that will never be added to the UI
juce::Slider radioSlider; // hidden
  1. attach it to the AudioParameterChoice of your AudioProcessorValueTreeState
juce::AudioProcessorValueTreeState::SliderAttachment radioSliderAttachment{
    audioProcessor.apvts, radioParamId.getParamID(), radioSlider
};
  1. create a function to set the slider to a specific value and update the buttons (here in my example I use 4 buttons but do as many as you need)
void radioUpdate(int selected) {
    radioSlider.setValue(selected, juce::SendNotification);
    radioButton0.setToggleState(selected == 0, juce::dontSendNotification);
    radioButton1.setToggleState(selected == 1, juce::dontSendNotification);
    radioButton2.setToggleState(selected == 2, juce::dontSendNotification);
    radioButton3.setToggleState(selected == 3, juce::dontSendNotification);
}
  1. set those buttons to trigger the update function on click
radioButton0.onClick = [this] { radioUpdate(0); };
radioButton1.onClick = [this] { radioUpdate(1); };
radioButton2.onClick = [this] { radioUpdate(2); };
radioButton3.onClick = [this] { radioUpdate(3); };

Alternatively if you have a lot of buttons you might want to store them in an array. I haven’t tested it but steps 3 and 4 should look something like this:

void radioUpdate(int selected) {
    radioSlider.setValue(selected, juce::SendNotification);
    for (int i=0; i<numRadioButtons; i++){
        radioButtons[i].setToggleState(selected == i, juce::dontSendNotification);
    }
}
for (int i=0; i<numRadioButtons; i++){
    radioButtons[i].onClick = [this, i] { radioUpdate(i); };
}

with radioButtons being your array of buttons and numRadioButtons the number of buttons of course.

The lambda should capture the loop counter variable as a value like this :

thanks, i’ll edit the post!