Parameters snap weirdly when being sent to Processor

this post is about a slider called makeupSlider:

makeupSlider.setRange(0, 12, .01f);

here’s the method where i try to convert the values from db to gain and then send them to the processor:

void _2020_01_27_slewAudioProcessorEditor::sliderValueChanged(Slider* slider){
    float value = slider->getValue();
    
    if (slider == &makeupSlider) {
        value = Decibels::decibelsToGain(value);
        processor.setParameter(0, value);
	}
}

i debugged inside of this method. at this point it still works.
PluginProcessor has this:
AudioParameterFloat* makeupParam;
Then in the constructor there is:

addParameter(makeupParam = new AudioParameterFloat("makeup", "MakeupParam",	NormalisableRange<float>(0.0f, 1.0f), 0.f));

Now when i DBG in the processBlock-method right before the loop that goes through the samples i’d expect the values to be either ranging from 0 to 1 smoothly or from 1 to 3.something, or from 0 to 12. but instead it just shows “1” constantly. when i changed the range to 1,3 in the line with NormalisableRange the behaviour went to starting on “1” but after first moving the knob staying on “3”. it seems it only knows the maximum value or something.

DBG(String(*makeupParam));

First, the AudioProcessor::setParameter() method is deprecated and shouldn’t be used any more.

Ideally you use the AudioProcessorValueTreeState to manage your parameters. That has connections from parameter to slider, that work both ways and have all the edge cases implemented:

  • sending beginChangeGesture() and endChangeGesture() when a knob is touched to work well with parameter automation from the host (otherwise the values you send and the value the host sends are competing leading to undefined behaviour)
  • takes care to use the same NormalisableRange from the parameter, so the snap value is applied correctly
  • uses the same textToValue and textFromValue lambdas, so the textbox and the host automation lane display the numbers in the same format
  • takes care of initial setup and loading in setStateInformation()

The tutorial to follow is here

After that have a look at the AudioParameterFloat constructor, specifically the NormalisableRange you can supply.

By defining the normalisation and unnormalisation in one place you can be sure to have always the same conversion. Attaching a Slider to an AudioParameterFloat via SliderAttachment will automatically set the range and the text conversion from the parameter to the slider, hands off!

To create a logarithmic scale (I used it for frequencies), I created myself a helper, that returns me a logarithmic NormalisableRange, just as an example:

A decibel one would be even easier, just replace the formulae with Decibels::decibelsToGain and gainToDecibels

Now to answer your question:

  • it could be the host automation struggling with the value you are sending unannounced
  • you could have set different ranges for the parameter and the slider
  • the deprecated function doesn’t work as you expect it
  • something else I cannot guess
2 Likes

first of all thanks for the detailed answer… but i still gotta say this is really complicated. i mean valuetree is supposed to make things easier and it probably does once i’ve understood it but the learning curve seems steep. i don’t even know how to start. after initializing the object i wrote this:

_2020_01_27_slewAudioProcessor::_2020_01_27_slewAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       )
    : parameters(*this, nullptr, Identifier("params"),
        {
            std::make_unique<AudioParameterFloat>("slew",
                                                   "Slew Factor",
                                                   0.0f,
                                                   1.0f,
                                                   0.75f),
            std::make_unique<AudioParameterFloat>("makeup",
                                                  "Makeup Gain",
                                                  0.0f,
                                                  12.0f,
                                                  0.f),
        })
#endif
{
    
}

but it red-underlines the “:” before parameters and the “)” at its end.

when i hover over them they say
“no default constructor exists for class: juce::AudioProcessorValueTreeState”
and
“expected a declaration”.

i also tried to put this into the { }-part of the method, which also doesn’t work. in the tutorial the constructor is much easier structured. it’s not representative for what the projucer actually makes when you create a new plugin

I see. This is actually not the fault of the AudioProcessorValueTreeState, but the macro mess around JucePlugin_PreferredChannelConfigurations, which should have been killed a long time ago IMHO.

If you take the crap out, it is easier to understand:

_2020_01_27_slewAudioProcessor::_2020_01_27_slewAudioProcessor()
     : AudioProcessor (BusesProperties()
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                       )
    : parameters(*this, nullptr, Identifier("params"),
        {
            std::make_unique<AudioParameterFloat>("slew",
                                                   "Slew Factor",
                                                   0.0f,
                                                   1.0f,
                                                   0.75f),
            std::make_unique<AudioParameterFloat>("makeup",
                                                  "Makeup Gain",
                                                  0.0f,
                                                  12.0f,
                                                  0.f),
        })
{ /* ... */ }

But the format is a colon, and then a comma separated list of base class constructors and member initialisation.

So just changing this line should get you further:

// instead
   : parameters(*this, nullptr, Identifier("params"),
// write
   , parameters(*this, nullptr, Identifier("params"),

Good luck

1 Like

awesome, now i even know what this syntax means!

btw as far as i can see now everything works perfectly now and yes, it really looks better than before. then i’d suggest to get rid of the parameters-tutorial with the slider-listener since all of the stuff is pretty much not supported anymore anyway, it was kinda pointless to learn this way in the first place. now i just have to build the plugin and check if it actually works as intended in the daw

1 Like

i have another question about the parameters that you helped me to set up (or anyone else who reads this!).

my gain knobs seem to heavily clip the sample whenever they were on 0 when the plugin has just loaded (without even having the gui opened yet) and the clipping only ends when both parameters are not 0 anymore. turning them back to 0 creates the expected behaviour (which is a sound without additional gain). it seems like only when 0 was set some other time it would create weird values for this parameter when reopened.

Just a shot in the dark, are you dividing by the number of the parameter by any chance? Or are you using a log() function? Both are undefined at zero, so the numbers could be more or less random…

the parameters both range from 0 to 12. in the processBlock-method i iterate through all samples. for each sample a method is started that does this:

void _2020_01_27_slewAudioProcessor::checkGainParams() {
    if (lastMakeup != *makeupParam) {
        lastMakeup = *makeupParam;
        makeup = Decibels::decibelsToGain(lastMakeup);
    }
    if (lastOutGain != *outGainParam) {
        lastOutGain = *outGainParam;
        outGain = Decibels::decibelsToGain(lastOutGain);
    }
}

i always check if lastMakeup and lastOutGain were different so it won’t recalculate the decibels for each sample, but only when a parameter has changed, and then as you can see i use the normal decibels-function

nvm. it was because i said lastMakeup = lastOutGain = 0; in the constructor in order to give them a default value, but that was stupid because it makes this function skip the first call, so makeup and outGain still contain garbage then.