Ok, so after spending some time figuring out how to fix a segfault due to my MidiChannel parameter trying to modify some not-existing-yet attachments, I can at last confirm that the technique is working fine.
Thanks @mathieudemange for pointing me in the right direction, and thanks @Rincewind for the extra hint, setting the std::unique_ptr<SomeGuiAttachment> to nullptr before creating the new attachment is indeed required for the technique to work.
For the record, I ended up using something like this (inspired by this post from @daniel) :
// abstract base attachment wrapper class
class DynamicGuiAttachment {
protected:
juce::AudioProcessorValueTreeState& apvts;
juce::String parameterId;
juce::String getFullParameterId(juce::String groupId) {
return groupId + parameterGroupSeparator + parameterId;
}
public:
DynamicGuiAttachment(
juce::AudioProcessorValueTreeState& vts,
juce::String paramId
) : apvts(vts), parameterId(paramId) {}
virtual void attachToGroup(juce::String groupId) = 0;
};
// implementation for slider (one must exist for each type of GUI control)
class DynamicSliderAttachment : public DynamicGuiAttachment {
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> attachment;
juce::Slider& slider;
public:
DynamicSliderAttachment(
juce::AudioProcessorValueTreeState& vts,
juce::String paramId,
juce::Slider& sldr
) : DynamicGuiAttachment(vts, paramId), slider(sldr) {}
void attachToGroup(juce::String groupId) override {
attachment = nullptr; // MANDATORY !!!
attachment.reset(new SliderAttachment(
apvts, getFullParameterId(groupId), slider
));
}
};
class MyWrapperComponent {
std::map<std::string, std::unique_ptr<DynamicGuiAttachment>> attachments;
juce::Slider mySlider;
public:
MyWrapperComponent(PluginProcessor& processor) {
addAndMakeVisible(mySlider);
attachments["myParameter"] = std::make_unique<DynamicSliderAttachment>(
processor.getValueTreeState(), "myParameter", mySlider
);
// etc ...
}
// groupId is inferred from the currently selected channel
void setActiveParameterGroup(juce::String groupId) {
for (const auto& [key, attachment] : attachments) {
attachment->attachToGroup(groupId);
}
}
};
I should precise that I’m using an AudioProcessorParameterGroup containing the same set of AudioParameterFloat’s for each channel in the AudioProcessorValueTreeState, and a "global" group with a "MidiChannel" parameter to control which channel group the GUI controls are attached to.
Hope this might help someone …
Cheers !
