Why AudioProcessorValueTreeState crash this way?

Trying AudioProcessorValueTreeState.

If I wrote this:

AudioPluginAudioProcessor::AudioPluginAudioProcessor()
    : AudioProcessor(BusesProperties()
#if !JucePlugin_IsMidiEffect
#if !JucePlugin_IsSynth
                         .withInput("Input", juce::AudioChannelSet::stereo(), true)
#endif
                         .withOutput("Output", juce::AudioChannelSet::stereo(), true)
#endif
                         ),
      parameters(*this, nullptr, juce::Identifier("MyJuceParams"),
                 {
                     std::make_unique<juce::AudioParameterFloat>("gainParam", "Gain", -70.0f, 6.0f, -16.26f),
                 })
{

all seems ok.
If I use in combination with NormalisableRange:

AudioPluginAudioProcessor::AudioPluginAudioProcessor()
    : AudioProcessor(BusesProperties()
#if !JucePlugin_IsMidiEffect
#if !JucePlugin_IsSynth
                         .withInput("Input", juce::AudioChannelSet::stereo(), true)
#endif
                         .withOutput("Output", juce::AudioChannelSet::stereo(), true)
#endif
                         ),
      rangeGain{-70.0f, 6.0f}, // its a juce::NormalisableRange<float>
      parameters(*this, nullptr, juce::Identifier("MyJuceParams"),
                 {
                     std::make_unique<juce::AudioParameterFloat>("gainParam", "Gain", rangeGain, -16.26f),
                 })
{

it crash when I load on DAW.

Where am I wrong?
And also: how would you set rangeGain.setSkewForCentre(-16.26f); using this CTOR init?

Thanks

I assume you declared the rangeGain after the AudioProcessorValueTreeState parameters, which means it will be initialised in that order. When creating the APVTS, it calls/copies the uninitialised NormalisableRange.

Ideally you create a free static function (doesn’t need to be declared in the class).

static juce::AudioProcessorValueTreeState::ParameterLayout createParameters()
{
    juce::NormalisableRange<float> range { -70.0f, 6.0f };
    range.setSkewForCentre (-16.26f);
    return { std::make_unique<juce::AudioParameterFloat>("gainParam", "Gain", rangeGain, -16.26f) };
}

// and initialise like this:
parameters (*this, nullptr, juce::Identifier("NWKJuceParams"), createParameters())
1 Like

Perfect @daniel, as usual :slight_smile: Thanks

@daniel : I’ve a problem with the Editor now. It doesn’t see all the three params I’ve on the parameters variable, linking them on the AudioPluginAudioProcessorEditor class:

    // params
    gainSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
    gainSlider.setTextBoxStyle(juce::Slider::NoTextBox, false, 90, 0);
    gainSlider.setPopupDisplayEnabled(true, false, this);
    gainSlider.setTextValueSuffix(" Gain");
    gainSlider.setBounds(20, 20, 60, 60);
    addAndMakeVisible(gainSlider);
    gainAttachment.reset(new SliderAttachment(valueTreeState, "gainParam", gainSlider));

    freqSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
    freqSlider.setTextBoxStyle(juce::Slider::NoTextBox, false, 90, 0);
    freqSlider.setPopupDisplayEnabled(true, false, this);
    freqSlider.setTextValueSuffix(" Freq");
    freqSlider.setBounds(100, 20, 60, 60);
    addAndMakeVisible(freqSlider);
    freqAttachment.reset(new SliderAttachment(valueTreeState, "freqSlider", freqSlider));

    resSlider.setSliderStyle(juce::Slider::RotaryVerticalDrag);
    resSlider.setTextBoxStyle(juce::Slider::NoTextBox, false, 90, 0);
    resSlider.setPopupDisplayEnabled(true, false, this);
    resSlider.setTextValueSuffix(" Res");
    resSlider.setBounds(180, 20, 60, 60);
    addAndMakeVisible(resSlider);
    resAttachment.reset(new SliderAttachment(valueTreeState, "resSlider", resSlider));

it seems to link always the gainSlider. The other two (freqSlider, resSlider) seems to derivate “default” values, not the ones set for those params (which I see correct if I browse parameters on the DAW).

This is the createParameters() function:

juce::AudioProcessorValueTreeState::ParameterLayout AudioPluginAudioProcessor::createParameters()
{
    rangeGain = {-70.0f, 6.0f};
    rangeCutoff = {20.0f, 22000.0f};
    rangeRes = {0.0f, 40.0f};

    rangeGain.setSkewForCentre(-16.26f);
    rangeCutoff.setSkewForCentre(1000.0f);
    rangeRes.setSkewForCentre(0.0f);

    return {
        std::make_unique<juce::AudioParameterFloat>("gainParam", "Gain", rangeGain, -16.26f),
        std::make_unique<juce::AudioParameterFloat>("freqParam", "Cutoff", rangeCutoff, 1000.0f),
        std::make_unique<juce::AudioParameterFloat>("resParam", "Res", rangeRes, 0.0f)};
}

Where am I wrong?

Your parameters use a different ID than the attachments. The parameter is called freqParam, but the attachment tries to connect “freqSlider” :wink:

Some suggestions to minimise code:
Add the Slider parameters to the constructor in the header:

juce::Slider freqSlider { juce::Slider::RotaryVerticalDrag, juce::Slider::NoTextBox };

This saves the first two calls in the editor constructor.

And a nice way to create those ranges:

juce::AudioProcessorValueTreeState::ParameterLayout AudioPluginAudioProcessor::createParameters()
{
    auto createRange = [](float min, float max, float midPoint)
    {
        juce::NormalisableRange<float> range (min, max);
        range.setSkewForCentre (midPoint);
        return range;
    };

    return {
        std::make_unique<juce::AudioParameterFloat>("gainParam", "Gain", createRange (-70.0f, 6.0f, -16.26f), -16.26f),
        std::make_unique<juce::AudioParameterFloat>("freqParam", "Cutoff", createRange (20.0f, 22000.0f, 1000.0f), 1000.0f),
        std::make_unique<juce::AudioParameterFloat>("resParam", "Res", createRange (0.0f, 40.0f, 10.0f), 0.0f)};
    };
}

btw. I assume the 0.0 for centre was a type, it cannot possibly be min and centre at the same time…

1 Like

@daniel : awesome, very clean code, and works nice!
Only a thing: what if I need the juce::NormalisableRange reference later?

Such as rangeFreq.convertTo0to1(freq)?

I think converting yourself is a mistake, rather let the parameter do the conversion and forget that it was ever normalised. I would treat that as implementation detail.

But if you really need it, the commonly used parameters inherit RangedAudioParameter, which allows to access the NormalisableRange using getNormalisableRange() or you can let the parameter convert for you as well.

TL;DR: avoid storing the ranges separated from the parameters, imagine the disaster if you accidentally use the wrong range to convert.

N.B. I added a feature request to make RangedAudioParameter the base class, which would as a side product allow any parameter to normalise/unnormalise:

1 Like

Thanks :slight_smile: