Auto Makeup Gain

Hi everyone,

I am trying to make an EQ with auto makup gain. The idea is that as a band’s gain is increased, an output knob turns the master for that channel gain down (left or right). I had the idea of trying to store the magnitude of the samples before and after the processing using

    auto inputLeftOutputGain = buffer.getMagnitude(0, 0, buffer.getNumSamples());

but everytime I do this, I end up with chatter or silence. I believe this is because I am not getting individual stores of the magnitude of the buffer before and after processing, even if I put the previous line above the following line in the chain.

    leftShelf8k.process(dsp::ProcessContextReplacing<float> (leftBlock));

How do I store the level of something before and after processing it?

Thanks,
Andy Bainton

That’s a nice idea, I implemented it once as prototype, and you can get it to work.
Two remarks:

  • don’t use the magnitude but the RMS value
  • you need to average along a longer period to get a good estimate

The displayed value depends on the signal you are sending through, so the probe has to be of a certain time.

2 Likes

I don’t know if this would be a horrible idea but I would approach this by:

  1. using getMagnitudeForFrequencyArray on your coefficients
  2. Finding the average level of the frequencies
  3. Calculate the gain you need to bring it to the correct level
  4. Apply gain

This is of course you’re wanting a theoretical autogain rather than one that is responding to the actual incoming frequencies of your processes. It does mean that you wouldn’t be reliant on monitoring levels on input and output but the accuracy would be restricted to the number of frequencies in your array.

1 Like

The problem is that the attenuation of the eq depends on the signal routed through. Imagine a HighPass set to 1kHz on a piccolo flute. Attenuation = 0dB because no frequency below 1kHz was there in the first place. Put it on a bass drum and almost nothing is left.

1 Like

@daniel very true, good point!

Hey! So here’s what I have so far:

auto outputLeftOutputGain = buffer.getRMSLevel(0, 0, buffer.getNumSamples());
auto outputRightOutputGain = buffer.getRMSLevel(1, 0, buffer.getNumSamples());

auto currentLeftOutputGain = 1.f - outputLeftOutputGain; //1.f being nominal gain
auto currentRightOutputGain = 1.f - outputRightOutputGain;

if (currentLeftOutputGain == previousLeftOutputGain)
{
    buffer.applyGain(0, 0, buffer.getNumSamples(), currentLeftOutputGain);
} else {
    buffer.applyGainRamp(0, 0, buffer.getNumSamples(), previousLeftOutputGain, currentLeftOutputGain);
    previousLeftOutputGain = currentLeftOutputGain;
}

if (currentRightOutputGain == previousRightOutputGain)
{
    buffer.applyGain(1, 0, buffer.getNumSamples(), currentRightOutputGain);
} else {
    buffer.applyGainRamp(1, 0, buffer.getNumSamples(), previousRightOutputGain, currentRightOutputGain);
    previousRightOutputGain = currentRightOutputGain;
}

There isn’t any distortion which is great, and the RMS level does move up when I move a band’s gain. I feel like I’m doing something wrong in this part. Maybe I just don’t know how to calculate the gain offset.

auto currentLeftOutputGain = 1.f - outputLeftOutputGain; //1.f being nominal gain
auto currentRightOutputGain = 1.f - outputRightOutputGain;

Thanks for your help!
Andy Bainton

Two notes:

  • applyGainRamp does the same check, if startGain and endGain are identical, so you can simply always call applyGainRamp
  • you want to calculate the factor to attenuate:

output = input * makeup
makeup = output / input

auto makeup = (inputGain > 0.0) ? outputGain / inputGain : 1.0;
for (int i=0; i < buffer.getNumChannels(); ++i)
    buffer.applyGainRamp (i, 0, buffer.getNumSamples(), previousMakeup, makeup);

previousMakeup = makeup;

I don’t think the user would appreciate different makeup factors per channel…

1 Like

I see what you’re saying about the independent channels.

Where would I source inputGain and outputGain from? Can I do that in stereo, given that using RMS gain happens in mono channels?

Yes, good question. I think I would average the channels.
Shame that there is not a convenience method, but easy enough to form the average yourself:

double inputGain = 0.0;
for (int i=0; i<buffer.getNumChannels(); ++i)
    inputGain += buffer.getRMSLevel (i, 0, buffer.getNumSamples());

filter.process(...);

double outputGain = 0.0;
for (int i=0; i<buffer.getNumChannels(); ++i)
    outputGain += buffer.getRMSLevel (i, 0, buffer.getNumSamples());

auto makeup = (inputGain > 0.0) ? outputGain / inputGain : 1.0;
for (int i=0; i < buffer.getNumChannels(); ++i)
    buffer.applyGainRamp (i, 0, buffer.getNumSamples(), previousMakeup, makeup);

previousMakeup = makeup;

No need to divide by numChannels, that cancels out…

2 Likes

I like your code! It makes sense. For some reason, when pushing up the gain on a slider, I still get a change in RMS gain overall. Maybe I’m misunderstanding something. Here’s my process block, if that helps to see where I’m going wrong. Is there something I need to put in to prepare to play?

ScopedNoDenormals noDenormals;
auto totalNumInputChannels  = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();

dsp::AudioBlock<float> block (buffer);

dsp::AudioBlock<float> stereoBlock (buffer);

auto leftBlock = stereoBlock.getSingleChannelBlock (0);
auto rightBlock = stereoBlock.getSingleChannelBlock (1);

for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    buffer.clear (i, 0, buffer.getNumSamples());

updateLeftShelf8k();
updateRightShelf8k();
updateLeftBell4k();
updateRightBell4k();
updateLeftBell2k();
updateRightBell2k();
updateLeftBell1k();
updateRightBell1k();
updateLeftBell500();
updateRightBell500();
updateLeftShelf250();
updateRightShelf250();
    
double inputGain = 0.0;
for (int i=0; i<buffer.getNumChannels(); ++i)
    inputGain += buffer.getRMSLevel (i, 0, buffer.getNumSamples());

leftShelf8k.process(dsp::ProcessContextReplacing<float> (leftBlock));
rightShelf8k.process(dsp::ProcessContextReplacing<float> (rightBlock));
leftBell4k.process(dsp::ProcessContextReplacing<float> (leftBlock));
rightBell4k.process(dsp::ProcessContextReplacing<float> (rightBlock));
leftBell2k.process(dsp::ProcessContextReplacing<float> (leftBlock));
rightBell2k.process(dsp::ProcessContextReplacing<float> (rightBlock));
leftBell1k.process(dsp::ProcessContextReplacing<float> (leftBlock));
rightBell1k.process(dsp::ProcessContextReplacing<float> (rightBlock));
leftBell500.process(dsp::ProcessContextReplacing<float> (leftBlock));
rightBell500.process(dsp::ProcessContextReplacing<float> (rightBlock));
leftShelf250.process(dsp::ProcessContextReplacing<float> (leftBlock));
rightShelf250.process(dsp::ProcessContextReplacing<float> (rightBlock));

double outputGain = 0.0;
for (int i=0; i<buffer.getNumChannels(); ++i)
    outputGain += buffer.getRMSLevel (i, 0, buffer.getNumSamples());

auto makeup = (inputGain > 0.0) ? outputGain / inputGain : 1.0;
for (int i=0; i < buffer.getNumChannels(); ++i)
    buffer.applyGainRamp (i, 0, buffer.getNumSamples(), previousMakeup, makeup);

previousMakeup = makeup;

Thanks,
Andy

How did you test your code? Are you measuring RMS before and after the plugin?

Maybe add a display for the numbers, if they make sense…

What you also can do is calculate the RMS after the applyGainRamp and compare with the inputRMS…

Good luck

1 Like

i switched…

to…

and it works?

1 Like

Where are you defining your previousMakeup?

In the PluginProcessor.h

double previousMakeup;

I’ve been utilizing this thread to achieve an auto-makeup after convolution with an IR. I did have to switch it to input/output to calculate makeup like baintonaj did.

However, now my gain is actually too powerful, and I appear to be increasing beyond the input gain. Any ideas why this might be?

1 Like

My guess is that if your output is lower than your input, the ratio between the two might exceed the change needed to bring the output back up.