Parallel Gain Knob

Hi everyone,

I’m working on a plugin that I intend to have parallel gain processing. I want one clean channel, and another channel that has the gain affecting, from the same source. I believe this is a multi-threading process, or maybe I am missing something simple. I have been emulating the function in Pro Tools, using two sets of faders, to my satisfaction How would you all accomplish this task? Here’s my code for my gain knob:

for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());
    
auto currentKnobGain = (pow (10, *apvts.getRawParameterValue(KNOB_GAIN_ID) / 20));

if (currentKnobGain == previousKnobGain)
{
    buffer.applyGain(currentKnobGain);
} else {
    buffer.applyGainRamp(0, buffer.getNumSamples(), previousKnobGain, currentKnobGain);
    previousKnobGain = currentKnobGain;
}

Thanks for your help!

Best,
Andy Bainton

So do you want to do something like a dry/wet configuration? I guess a simple linear gain (–> a louder but no other way altered version of the signal) combined with a non-altered version of the signal does not make so much sense, but I guess you want to add some saturation or compression after the gain?

In either case

No definetively no multiple threads needed here! There are very few use cases where multithreaded processing inside a plugin might make sense at all, this is only a good solution of you hit some serious processing power limits and an expert task as there are multiple ways to do things wrong here. So, although you might have a parallel signal flow in mind, you want to compute it in a serial fashion. The ususal way to do this is to create a copy of your input buffer that you keep around as your dry signal and then apply processing to the main buffer and mix them together in a last step. If your processing introduces lantency, you want to make sure to apply the same latency to your dry signal. And make sure that the dry buffer is a member of your processor class which you pre-allocate in the prepare callback.

Another few comments to your code snippet: Of course you should not clear your buffer at the beginning – this way you will erase all your input data before you even processed it and you will hear nothing but silene :wink: Then regarding the way you fetch the gain from the apvts inside your processing loop: The std::atomic<float>* returned by that method is intended to be fetched once and then be kept as member instead of doing it again and again in each callback. In any case, operations that invoke strings used inside process block should look suspicious to you :smiley:
I personally use to store the raw parameter atomics const std::atomic<float>& references and initialize them in my processors constructor. Example:

(Note that I use my own processor base class here which simplifies things a bit, so it might look a bit different to a plain JUCE project)

1 Like