I’m trying to wrap my head around using AudioProcessorValueTreeState in a plugin using child processors. Ideally, I’d like to get a parent processor with an AudioProcessorValueTreeState reflecting parameters present in any child processors, with the child processors providing the parent constructor their parameters themselves.
I can have child processors with their own AudioProcessorValueTreeStates, but those aren’t seen by the plugin host as they don’t show up in the parent’s parameters.
I can have the child processors provide parameters to the parent’s APVTS in the parent’s constructor, as in the example below, but there’s some cumbersome acrobatics involved in getting the parameter linked to the appropriate child variables – the parameter listener will only provide the [0 1] range, so I appear to need to keep each parameter’s NormalisableRange somewhere in the child…
It would be great to just pass a pointer to the parent APVTS to the child, but the child needs to be initialized in order to provide its parameters to the parent constructor, so the APVTS doesn’t exist when the child is being initialized.
Can an APVTS add a child’s entire APVTS to it? Or extract and/or move the child APVTS as a ParameterLayout in the parent APVTS constructor?
If I’m missing something simple or obvious, what is it? Anybody else facing the problem of surfacing child parameters to the parent processor? It seems like this isn’t an esoteric use case – providing host-visible parameters linked to child processors in a Graph or ProcessorChain or whatever. Any thoughts greatly appreciated.
class BaseProcessor : public AudioProcessor
{
// ... default shared AudioProcessor setup methods ...
};
class GainProcessor : public BaseProcessor, public AudioProcessorParameter::Listener
{
public:
GainProcessor() {}
std::vector<std::unique_ptr<RangedAudioParameter>> createParameters() {
std::vector<std::unique_ptr<RangedAudioParameter>> params;
auto gainParam = std::make_unique<AudioParameterFloat> ("gain", "Gain", 0.0f, 2.0f, 1.0f);
gainParam->addListener(this);
gainRange = gainParam.get()->getNormalisableRange();
params.push_back(std::move(gainParam));
return params;
}
void parameterValueChanged (int parameterIndex, float newValue) override {
gainParamValue = gainRange.convertFrom0to1(newValue);
}
void parameterGestureChanged (int, bool) override {}
void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override
{
buffer.applyGain(gainParamValue);
}
private:
float gainParamValue = 1.0f;
NormalisableRange<float> gainRange;
};
class ParentProcessor : public BaseProcessor {
public:
//==============================================================================
ParentProcessor()
: gainProcessor()
, avts (*this, nullptr, "PARAMETERS", createParameters() )
{}
AudioProcessorValueTreeState::ParameterLayout createParameters()
{
std::vector<std::unique_ptr<RangedAudioParameter>> params;
auto gainParams = gainProcessor.createParameters();
for (auto& p : gainParams)
params.push_back(std::move(p));
return { params.begin(), params.end() };
}
virtual void prepareToPlay (double sampleRate, int samplesPerBlock) override
{
gainProcessor.prepareToPlay(sampleRate, samplesPerBlock);
}
virtual void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override
{
for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
buffer.clear (i, 0, buffer.getNumSamples());
gainProcessor.processBlock(buffer, midiMessages);
}
GainProcessor gainProcessor;
AudioProcessorValueTreeState avts;
};