How to implement radio buttons with ParameterAttachment right?

Hey there,

I’m struggling to implement radio buttons with the new(?) ParameterAttachment class and connect them to a AudioProcessorValueTreeState. From my understanding this should be as simple as a ComboBoxAttachment beside that one has to organize some buttons in a radio group. So what would be the right way to do it?
My implementation works when clicking the buttons but automation does not work.
What am I missing? Any thought are welcome. Thanks a lot!

RadioButtonAttachment::RadioButtonAttachment(RangedAudioParameter& param, juce::Array<juce::Button*>& _buttons,
                                         String componentID, int radioGroupID, UndoManager* um) :
storedParameter(param),
attachment(param, [this] (float newValue) { setValue(newValue); }, um)
{
    for (int i=0; i < _buttons.size(); ++i) {
        juce::Button* b = _buttons.getUnchecked (i);
        if (! buttons.contains (b)) {
            if (radioGroupID > 0) {
                b->setRadioGroupId(radioGroupID);
            }
            b->setComponentID(componentID);
            b->setClickingTogglesState(true);
            buttons.add (b);
            b->addListener (this);
        }
    }
    attachment.sendInitialUpdate();
}

RadioButtonAttachment::~RadioButtonAttachment()
{
    for (int i=0; i < buttons.size(); ++i) {
        juce::Button* b = buttons.getUnchecked (i);
        b->removeListener(this);
    }
}

// place all buttons in a row
void RadioButtonAttachment::setBounds(int x, int y, int width, int height, int margin)
{
    for (int i=0; i<buttons.size(); i++) {
        buttons.getUnchecked(i)->setBounds(x+margin*i, y, width, height);
    }
}

// FIXME: automation no worky
void RadioButtonAttachment::buttonClicked (juce::Button* b)
{
    if (ignoreCallbacks)
        return;
    
    for (int i=0; i<buttons.size(); i++) {
        if (b == buttons.getUnchecked(i) && b->getToggleState()) {
            attachment.setValueAsCompleteGesture(i);
        }
    }
}

void RadioButtonAttachment::buttonStateChanged(juce::Button* b)
{
    if (ignoreCallbacks) {
        return;
    }
    
    for (int i=0; i<buttons.size(); i++) {
        if (b == buttons.getUnchecked(i) && b->getToggleState()) {
            attachment.setValueAsCompleteGesture(i);
        }
    }
}

void RadioButtonAttachment::setValue (float newValue)
{
    value = newValue;
    const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
    buttons[value]->setToggleState(true, sendNotification);
}

Ok, looks like this is related to this Logic X 6 Bug.
My radio buttons behave the same. Reading automation values work, writing just the first time until reinstantiation.

Hi, I’m trying to do the same, can you please post the full class? Great reference for a newbie :slight_smile:

Hi, this is the header file:

#pragma once
#include <JuceHeader.h>

/*To implement a new attachment type, create a new class which includes an instance of this class as a data member. Your class should pass a function to the constructor of the ParameterAttachment, which will then be called on the message thread when the parameter changes. You can use this function to update the state of the UI control. Your class should also register as a listener of the UI control and respond to changes in the UI element by calling either setValueAsCompleteGesture or beginGesture, setValueAsPartOfGesture and endGesture.

Make sure to call sendInitialUpdate at the end of your new attachment's constructor, so that the UI immediately reflects the state of the parameter.*/

class RadioButtonAttachment : private Button::Listener
{
public:
//     Creates a connection between a plug-in parameter and some radio buttons.
    RadioButtonAttachment (RangedAudioParameter& parameter, juce::Array<juce::Button*>& _buttons, String componentID, int radioGroupID = 0, UndoManager* um = nullptr);
    ~RadioButtonAttachment();
        
    Button* getButton(int index) { return buttons.getUnchecked(index); }
    
    int numButtons() { return buttons.size(); }
    
    void setBounds(int x, int y, int width, int height, int margin);

private:
    void setValue (float newValue);
    float value;

    void buttonClicked (juce::Button* b) override;
    void buttonStateChanged(juce::Button* b) override;

    juce::RangedAudioParameter& storedParameter;
    ParameterAttachment attachment;
    
    juce::Array<juce::Component::SafePointer<juce::Button> > buttons;
    bool ignoreCallbacks = false;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RadioButtonAttachment)
};

.cpp is complete, just missing the include for the header. I also commented out most of the code in buttonStateChanged. Will check again when Logic gets an update.

usage:
std::unique_ptr<RadioButtonAttachment> myAttachment;

myAttachment = std::make_unique<RadioButtonAttachment>(*paramTree.getParameter("myID"), myButtons, "myID", myGroup);

myButtons is an array of buttons created before:

juce::Array<juce::Button*> myButtons;
myButtons.add(&a);
myButtons.add(&b);
myButtons.add(&c);

hope this helps. I’m also new to JUCE so this solution is probably far from perfect. Let me know if you have a more elegant solution.