Smoothing the Gain knob

Hi!
I’m trying to create a click-free gain knob, like the one found in Pro-Q 3, but I’m having some issues.
I’ve tried several things but all have their downsides:

  1. No smoothing - The Gain value is directly attached to the Slider value, and the Gain value is simply read at the start of each processBlock, then applied. This leads to a lot of popping and crackling when moving the Slider no matter how small or big the gain change is, so it’s a no-go.

  2. Smoothing at a fixed approach velocity - The Gain value is not directly attached to the Slider value, but each sample a small step (e.g 0.005/sampleRate for a 5ms ramp from 0 to 1 or a 2.5ms ramp from 0.5 to 1) is added/subtracted from the current actual value until it reaches the value of the Slider, effectively making the actual Gain “follow” the Slider value. This worked fantastically but had the downside that it introduced some crackles in the low frequencies while moving the Slider. Changing the ramp rate did affect the sound of the crackle, but it never disappeared completely.

  3. Starting a new fade on every Slider update - Every time the Slider value is changed, a new fade is started from the current actual Gain to Slider value. This worked fantastically when the user is changing the Slider, as it stops often enough that the fade had time to approach the target value. No artifacts were heard. The only problem is that the responsiveness is heavily affected by block size, with lower block sizes being more prone to “stalling”, so that if the Slider gets automated or moved constantly, the actual Gain will take lots of time before reaching the target Gain.

To be clear, some lost responsiveness is not an issue and the smoothing gives a nice feel, so the 3rd option has been pretty good. I just haven’t found a way to make it work consistently across block sizes, which is a no-go as it affects the sound if there is any automation of the parameter.

These are the methods I’ve tried so far. Am I missing some other solution or are there always downsides? Would anyone happen to know how high-end plugins like Pro-Q 3 handles this?

I’d be really thankful for any help with this one.

I’m guessing you’re currently using buffer.applyGain(…), right?
You should use applyGainRamp instead, which uses 2 separate variables.
Here’s the recipe in pseudo-code.

In your header:

class MyAudioProcessor
{
public:

void setGain(float value);
...

private:

float gain { 0.f );
float lastGain { 0.f };
};

In your .cpp:

void MyAudioProcessor::setGain(float value)
{
    gain = value;
}

void MyAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    buffer.applyGainRamp(0, buffer.getNumSamples(), lastGain, gain);
    lastGain = gain;
}

The idea is that you need to apply a ramp, because you’re working on a buffer (i.e. an array of values) at each processBlock call.

better look at LinearSmoothedValue

Thanks for the answer.

I did consider this approach but I’d like the sound of my plugin to be completely independent of buffer size if possible. This method would make the ramp shorter/longer at lower/higher buffer sizes, right?

In cases 2. and 3. I’m ramping and applying the gain manually by looping over each sample. This is fine in my case performance wise.

The problem here I think is to find a good way to define a smoothing process that can adapt to a constantly changing end value.

I’m quite new to DSP so thank you for your patience.

I see. Then yes, you’ll probably need to find a way to split the buffer content into fixed sized blocks. The tricky part is then managing the remaining samples in case the buffer size isn’t a perfect multiple of the block size.

this is exactly what LinearSmoothedValue does with a reset in seconds given a sample rate and a applyGain method

Nice :slight_smile:

Thanks a lot, I’ll try this!

pro-q has really slow parameter smoothing on most controls. that’s why it never audibly distorts