Compressor produces ringing / distorted sound


#1

Hi there!

I want to build an audio plugin which needs some sort of bandpass filtering. So with some research I found the DSP Framework from Vinnie Falco and tried to implement that into my project.

First of all: This is my really first audio plugin. It should become a multiband compressor, lets see if it can be published one day.

A short description of what I want to do:
I started using the audio plugin template from the projucer and implemented a litte GUI which offers me 3 sliders. Each one controls the cut off frequency of a highpass and lowpass filter (these are Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::XXXXpass<4>,2> (1024), where XXXX is ‘High’ or ‘Low’).

In the end there should be 4 Frequencybands: Sub, Low, Mid, High. To get each Band I don’t want to use bandpass filters, because later the filters will be Linkwitz-Riley ones.

For each band there should be a compressor applied (I found one on the net, if you are interessted I will copy the link for you).

In the end all bands are summed up.

That’s the short description of my project, now the issues:
My audio is kind of ringing
Each time I want to evaluate my compressor functions (regardless of any prior filterings) there is some kind of ringing or distortion in my audio. It does not matter where I apply the compressor (within some filtered bands or on the whole buffer). So my suggestion is some pointer / copy issues.

To explain my problem I will start with the PluginProcessor code and then going into detail of my compressor class.

So to initialise my buffers and filters I use the prepareToPlay function and apply these filters onto some bandpass buffers which are copies from the buffer in processBlock:

My whole code is available at: this link

void MultibandCompressorAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
    internal_sampleRate = sampleRate;
    
    // initialise the buffers we need for the later on filterings
    subBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock);
    lowBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock);
    
    lowerBands = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock);
    higherBands = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock);
    
    midBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock);
    highBand = new AudioSampleBuffer(INIT_FILTER_CHANNELS,samplesPerBlock);
    
    // Now initialise the filters I need for the bandpass filtering using butterworth designs
    subpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH);
    lowpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH);
    setSubpass((double)INIT_LOW_FREQ);
    setLowpass((double)INIT_LOW_FREQ);
    
    lowerpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH);
    higherpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH);
    setLowerpass((double)INIT_MID_FREQ);
    setHigherpass((double)INIT_MID_FREQ);
    
    midpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::LowPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH);
    highpass = new Dsp::SmoothedFilterDesign<Dsp::Butterworth::Design::HighPass, INIT_FILTER_CHANNELS> (INIT_FILTER_SMOOTH);
    setMidpass((double)INIT_HIGH_FREQ);
    setHighpass((double)INIT_HIGH_FREQ);
    
    SubMute = false;
    LowMute = false;
    MidMute = false;
    HighMute = false;
    Active = true;
    
    midCompressor = new Compressor();
    midCompressor->setBuffer(midBand);
}

void MultibandCompressorAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    if(Active)
    {
        
        // divide the buffer into sub, low, mid and high bands by copying the buffer
        // and then apply some highpass and lowpass filters to the data
        
        // start with the lower frequencies
        
        lowerBands->copyFrom(0, 0, buffer, 0, 0, buffer.getNumSamples());
        lowerBands->copyFrom(1, 0, buffer, 1, 0, buffer.getNumSamples());
        lowerpass->process(lowerBands->getNumSamples(), lowerBands->getArrayOfWritePointers());
    
        subBand->copyFrom(0, 0, *lowerBands, 0, 0, lowerBands->getNumSamples());
        subBand->copyFrom(1, 0, *lowerBands, 1, 0, lowerBands->getNumSamples());
        subpass->process(subBand->getNumSamples(), subBand->getArrayOfWritePointers());
        
        lowBand->copyFrom(0, 0, *lowerBands, 0, 0, lowerBands->getNumSamples());
        lowBand->copyFrom(1, 0, *lowerBands, 1, 0, lowerBands->getNumSamples());
        lowpass->process(lowBand->getNumSamples(), lowBand->getArrayOfWritePointers());
        
        // now the higher frequencies
        
        higherBands->copyFrom(0, 0, buffer, 0, 0, buffer.getNumSamples());
        higherBands->copyFrom(1, 0, buffer, 1, 0, buffer.getNumSamples());
        higherpass->process(higherBands->getNumSamples(), higherBands->getArrayOfWritePointers());
        
        midBand->copyFrom(0, 0, *higherBands, 0, 0, higherBands->getNumSamples());
        midBand->copyFrom(1, 0, *higherBands, 1, 0, higherBands->getNumSamples());
        midpass->process(midBand->getNumSamples(), midBand->getArrayOfWritePointers());
        
        highBand->copyFrom(0, 0, *higherBands, 0, 0, higherBands->getNumSamples());
        highBand->copyFrom(1, 0, *higherBands, 1, 0, higherBands->getNumSamples());
        highpass->process(highBand->getNumSamples(), highBand->getArrayOfWritePointers());
        
        // now the bandpassed data is within the buffers subBand, lowBand, midBand, highBand
        // so clear the original buffer and then sum it up from the individual bands
        
        for (int i = 0; i < 2; ++i)
            buffer.clear (i, 0, buffer.getNumSamples());

        if (!SubMute)
        {
            buffer.addFrom(0, 0, *subBand, 0, 0, subBand->getNumSamples());
            buffer.addFrom(1, 0, *subBand, 1, 0, subBand->getNumSamples());
        }
        if (!LowMute)
        {
            buffer.addFrom(0, 0, *lowBand, 0, 0, lowBand->getNumSamples());
            buffer.addFrom(1, 0, *lowBand, 1, 0, lowBand->getNumSamples());
        }
        if (!MidMute)
        {
            // for debugging reasons I started using only one compressor. If you
            // comment the next line out, the ringing is gone
            midCompressor->process();
            buffer.addFrom(0, 0, *midBand, 0, 0, midBand->getNumSamples());
            buffer.addFrom(1, 0, *midBand, 1, 0, midBand->getNumSamples());
        }
        if (!HighMute)
        {
            buffer.addFrom(0, 0, *highBand, 0, 0, highBand->getNumSamples());
            buffer.addFrom(1, 0, *highBand, 1, 0, highBand->getNumSamples());
        }
        
    }
}

So I give the pointer of my midBand to the compressor class and then later on I compress my audio data using the method midCompressor->process(); Now here’s the code of my compressor class (neglecting most getter/setter)
Compressor.h

#include "../JuceLibraryCode/JuceHeader.h"

class Compressor
{
public:
    //==============================================================================
    Compressor();
    ~Compressor();
    
    //==============================================================================
    
    void setBuffer(AudioSampleBuffer *newbuffer);
    
    void setAttack(double newTauAttack);
    void setAttack(double newTauAttack, double sampleRate);
    void setRelease(double newTauRelease);
    void setRelease(double newTauRelease,double sampleRate);
    
    void setThreshold(double newThreshold);
    void setRatio(double newRatio);
    void setWeighting(double newWeighting);
    void setMakeup(double newMakeup);
    
    void setActive(bool newActive);
    void toggleActive();
    
    //==============================================================================
    
    AudioSampleBuffer* getBufferPointer();
    
    float getAttackSEC();
    float getAttackMSEC();
    float getReleaseSEC();
    float getReleaseMSEC();
    
    float getThreshold();
    float getRatio();
    float getWeighting();
    float getMakeup();
    
    bool isActive();
    
    //==============================================================================
    
    void process();
    void process(AudioSampleBuffer &buffer, int m);
    void resetAll();
    
    //==============================================================================
    
private:
    
    // AudioBuffer for Compressor
    AudioSampleBuffer *buffer, data;
    
    // time constraints
    float   alphaAttack,
            tauAttack,
            alphaRelease,
            tauRelease;
    
    double    sampleRate;
    
    // compressor settings
    float   thresh,
            ratio,
            weighting, halfWeighting,
            makeup;
    
    bool active;
    
    // compressor internal calculations
    float *xL, *xG, *yG, *yL, yLprev, dbMin, linMin, *appliedGain;
    int numChannels, numSamples;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Compressor);
};

Compressor.cpp (some methods are not written here. They’re obvious)

#include "Compressor.h"

Compressor::Compressor()
{
    linMin = 0.000001;
    dbMin = -120;
    
    //
    makeup = 0.0;
    thresh = 0.0;
    ratio = 1.0;
    
    active = false;
    
    setAttack(0.0);
    setRelease(0.0);
    
    sampleRate = 48000;
}

void Compressor::setBuffer(AudioSampleBuffer *newbuffer)
{
    buffer = newbuffer;
    
    data.setSize(buffer->getNumChannels(),buffer->getNumSamples());
    data.clear();
    
    xL = new float[buffer->getNumSamples() * buffer->getNumChannels()];
    xG = new float[buffer->getNumSamples() * buffer->getNumChannels()];
    yG = new float[buffer->getNumSamples() * buffer->getNumChannels()];
    yL = new float[buffer->getNumSamples() * buffer->getNumChannels()];
    
    for(int i = 0; i < buffer->getNumSamples()*buffer->getNumChannels(); i++)
    {
        xL[i] = 0;
        xG[i] = 0;
        yG[i] = 0;
        yL[i] = 0;
    }
    
    numChannels = buffer->getNumChannels();
    numSamples = buffer->getNumSamples();
}

void Compressor::setAttack(double newTauAttack)
{
    tauAttack = newTauAttack;
    alphaAttack = exp(-1000 / (sampleRate * tauAttack));
}

void Compressor::setRelease(double newTauRelease)
{
    tauRelease = newTauRelease;
    alphaRelease = exp(-1000 / (sampleRate * tauRelease));
}

void Compressor::toggleActive()
{
    active = !active;
    
    if (active)
    {
        for(int i = 0; i < numSamples*numChannels; i++)
        {
            xL[i] = 0;
            xG[i] = 0;
            yG[i] = 0;
            yL[i] = 0;
        }
    }
}

void Compressor::process()
{
    if(!active)
        return;
    if (fabs(thresh) < 0.00001)
        return;
    
    yLprev = 0.0;
    for (int m = 0; m < numChannels; ++m)
    {
        data.clear();
        data.addFrom(m, 0, *buffer, m*2,   0, data.getNumSamples(), 0.5);
        data.addFrom(m, 0, *buffer, m*2+1, 0, data.getNumSamples(), 0.5);
        
        for(int i = 0; i < numSamples; ++i)
        {
            if(fabs(data.getWritePointer(m)[i]) < linMin)
            {
                xG[i + m*numChannels] = -120;
            }
            else
            {
                xG[i + m*numChannels] = 20*log10(fabs(data.getWritePointer(m)[i]));
            }
            
            // compressor curve
            if(xG[i + m*numChannels] >= thresh)
            {
                yG[i + m*numChannels] = thresh + (xG[i + m*numChannels] - thresh) / ratio;
            }
            else
            {
                yG[i + m*numChannels] = xG[i + m*numChannels];
            }
            
            // attack and release
            
            xL[i + m*numChannels] = xG[i + m*numChannels] - yG[i + m*numChannels];
            
            if(xL[i + m*numChannels] > yLprev)
            {
                yL[i + m*numChannels] = alphaAttack * yLprev + (1 - alphaAttack) * xL[i + m*numChannels];
            }
            else
            {
                yL[i + m*numChannels] = alphaRelease * yLprev + (1 - alphaRelease) * xL[i + m*numChannels];
            }
            
            // apply gain to the buffer
            (*data.getWritePointer(m,i)) *= pow(10,(makeup - yL[i + m*numChannels])/20);
             yLprev = yL[i + m*numChannels];
        }
    }
}

Do you have a clue why my data is ringing? Thanks for your help :wink: