Fast access to parameters?

I am updating from JUCE 5.3.2 to 5.4.7, and am looking at restructuring a fair amount of code, specifically the AudioProcessorValueTreeState.

One practice that I’ve used previously was to always create the parameters as member variables in my PluginProcessor class using CreateAndAddParameter. Then, whenever I needed to access the parameter, I directly used that member variable (a raw pointer). The idea is that this would greatly speed up access to the parameters without having to look them up. (A couple of my plugins have over 100 parameters, so it seemed like a good optimization to use.)

Is this still a viable/reasonable idea, using the ParameterLayout scheme instead of the deprecated CreateAndAddParameter? If so, I’ve been having trouble figuring out how to keep a pointer in my processor class. Here is the only thing I’ve come up with that doesn’t generate a compile error:

AudioProcessorValueTreeState::ParameterLayout TestPlugin1AudioProcessor::createParameterLayout()
{
  std::vector<std::unique_ptr<RangedAudioParameter>> params;
  params.push_back( std::make_unique<AudioParameterFloat>( "gain", "Gain", -36.0f, 0.0f, 0.0f ) );
  pGainParam = dynamic_cast <AudioParameterFloat*>(&(*params.back()));
  return { params.begin(), params.end() };
}

I’ve tried using a std::unique_ptr as the member variable, but I cannot find a way to first create it using std::make_unique, and then call push_back on it. (I intend to have a variety of parameter types, so I wanted (I assume) a vector of RangedAudioParameter objects, correct?)

Is there any reason for additional data for your realtime callbacks more than getRawParameterValue() ? (it’s a float atomic float * with the value described by your range, in your example it’ll be (-36.0f, 0.0f)

Well, some of my parameters interact with each other, and I need access to them from my parameterChanged() callback, not just the parameter being changed at the moment. So I need the actual current value at that moment, which getRawParameterValue does not guarantee in the listener callbacks.

Also, I need access to the range itself sometimes, which is not provided via that call.

You could consider an approach something like this:

class MyProcessor  : public juce::AudioProcessor
{
    ...

private:
    template <typename Param, typename... Args>
    static Param& addToLayout (juce::AudioProcessorValueTreeState::ParameterLayout& layout,
                               Args&&... args)
    {
        auto param = std::make_unique<Param> (std::forward<Args> (args)...);
        auto& result = *param;
        layout.add (std::move (param));
        return result;
    }

    juce::AudioProcessorValueTreeState::ParameterLayout createParameters()
    {
        juce::AudioProcessorValueTreeState::ParameterLayout layout;

        floatParam = &addToLayout<juce::AudioParameterFloat> (layout, "float", "float", juce::NormalisableRange<float>(), 0.5);
        intParam = &addToLayout<juce::AudioParameterInt> (layout, "int", "int", 0, 10, 5);
        boolParam = &addToLayout<juce::AudioParameterBool> (layout, "bool", "bool", false);

        return layout;
    }

    juce::AudioParameterFloat* floatParam;
    juce::AudioParameterInt* intParam;
    juce::AudioParameterBool* boolParam;

    juce::AudioProcessorValueTreeState apvts { *this, nullptr, "tree", createParameters() };
};
3 Likes