Dynamics Processing, Oversampling, and an HPF on the Side-Chain

Hi, I am trying to create a simple compressor that oversamples by a fixed 2x, and has a HPF in the side-chain. I haven’t been able to create a HPF, because I haven’t figured out an effective way to implement it. I found that the filterBlock.copyFrom(returnBlock) statement, doesn’t perform a duplicate of returnBlock in the way I was expecting. I was hoping for it to be a 1:1 copy of the block so that I can have two separate, equally-oversampled audio paths, one of which I will use as the side-chain with the HPF. Any insights into where I could go from here? Thank you.

The setup I have right now looks like this:

//**Process Block**
juce::dsp::AudioBlock< float> inputBlock (buffer);
auto returnBlock = oversampler.processSamplesUp(inputBlock);

//Ballistics Filter set up

juce::dsp::AudioBlock<float> filterBlock;
filterBlock.copyFrom(returnBlock); // <- right here is where I am having trouble

//Filter Process
scHPF.process(juce::dsp::ProcessContextReplacing<float> (filterBlock));

//Gain Calculator + DCA
auto returnBlockNumSamples {returnBlock.getNumSamples()};
for (size_t channel {0}; channel < totalNumInputChannels; ++channel)
    auto *channelData = returnBlock.getChannelPointer(channel);
    auto *filterBlockChannelData = filterBlock.getChannelPointer(channel);
    auto avgGain = 0.0f;
    //Gain Calc
    for (size_t sample {0}; sample < returnBlockNumSamples; ++sample) {
        if (sample % 5 == 0) {
        float envDB = 20.0f * std::log10f(ballisticsFilter.processSample(channel, filterBlockChannelData[sample])); 
        float gain = 0.0f;

        if ((2.0f * (envDB - threshold)) < -knee){
            gain = 1.0f;
        } else if ((2.0f * std::abs(envDB - threshold)) <= knee){
            gain = std::powf(10.0f, ((envDB + (1.0f / ratio - 1.0f) * std::powf(envDB - threshold + (knee / 2.0f), 2.0f) / (2.0f * knee)) - envDB) / 20.0f);
        } else if ((2.0f * (envDB - threshold)) > knee) {
            gain = std::powf(10.0f, (threshold + ((envDB - threshold) / ratio) - envDB) / 20.0f);
        avgGain += gain;
    avgGain /= returnBlockNumSamples;
    //DCA w/ juce::SmoothedValue<float, juce::ValueSmoothingTypes::Linear> 
    smoothedGain.applyGain(channelData, returnBlockNumSamples);


AudioBlock isn’t something that holds data, it’s more of a wrapper to an underlying AudioBuffer.

Not at the computer today, but you will need to create an AudioBuffer, then when you make your filter block, you pass this AudioBuffer as the argument to the constructor. Now you have 2 buffers, each with a block referencing them.

The block copy function didn’t work because there was no underlying buffer to copy in to.

Hope this helps set you on the right path!

1 Like

Although if I understand correctly, this second buffer shouldn’t be needed. You could make a custom ballistics filter class and just implement the side chain there on a sample by sample basis.

1 Like

Thanks for your help! I made a duplicate of the buffer, processed it, oversampled the buffers independently, and it worked. I couldn’t figure out how to implement the side chain on a sample-by-sample basis in a custom class, so if you have any ideas for how to do so I would love to hear them! For now, I have something that works.

The only issue is in Pro Tools Developer Version 2020.11, and Pro Tools Release Version 2021.7 (via VST3 on DDMF’s Metaplugin), and that I get comb filtering between two instances of my compressor when I try to use them in parallel. I have the latency set up correctly, and when I have only one active, non-bypassed instance the tracks sound fine. I tried removing the second oversampler and its HPF from the side-chain, and that got rid of the comb filtering, but this limits some of the functionality of what I am trying to accomplish. Is there something else I can do to affect this comb filtering? Or should I figure out how to make a custom ballistics filter class? Thank you.