Compressor: distortion/artifacts introduced due to gain reduction

Hi

I have been coding a basic compression algorithm, however, my algorithm introduces a lot of artifacts which seem to be introduced due to sharp changes in the signal. I have tried numerous types of envelope followers including a basic signal squared and then low pass filtered. However, the signal still seems to result in a jagged response. I have included a plot of the response below using a bass guitar as the input (the white signal is the input, purple is the processed signal and the green is the envelope). The issue does not seem to affect drums - my suspicion is that it is because my envelope follower is not working well for signals which are more continuous like this bass line.

Not 100% sure what is going on here - any help will be appreciated. Thanks

if(envSampleLevel > *mCompressionThreshold){
    float compressedLevel;
    float in = Decibels::gainToDecibels(fabsf(channelData[i]),-96.0f);
     // Include knee compression
    float slope = 1/floor(*mRatio) - 1;
    float overshoot = in - *mCompressionThreshold;
    if (overshoot <= -*mKnee/2)
        compressedLevel = in;
    if ((overshoot > -*mKnee/2) & (overshoot < *mKnee/2))
        compressedLevel = in + slope * ((overshoot + *mKnee / 2) * (overshoot + *mKnee / 2))/ (2 * *mKnee) ;
    if (overshoot >= -*mKnee/2)
        compressedLevel = in + slope* overshoot ;
    float newGain = Decibels::decibelsToGain(compressedLevel);
    if(channelData[i] < 0)newGain = -newGain;
    buffer.setSample(channel, i, newGain);
}

It looks like you are calculating the gain reduction for individual samples (channelData[i]) but for each sample you should be updating the envelope, calculating the gain from the envelope level (when it’s above the threshold) then multiply the individual samples by the gain.

1 Like

Thanks for the response @ujam. I think I know what you mean so I changed my algorithm to use my envelope level (envSampleLevel) to work out the gain (as in code below). Then I applied this to a ratio of the sample and the envelope so that the signal reduces more for larger sample overshoots. Now when I play with the attack, release and knee I can eventually get a smoother sounding compression (see images below that illustrate a smoother processed signal (purple). However as soon as all I increase the threshold I have to manipulate the attack and release to reduce the ‘crackliness’ of the signal) and this counteracts the effect of increasing the ratio. Perhaps this is because I need to implement lookahead to detect fast peaks? Or maybe some kind of noise filtering, perhaps it is aliasing/quantization distortion… I’m not sure how to tell.

if(envSampleLevel > *mCompressionThreshold){
    float compressedLevel;
    float in = Decibels::gainToDecibels(fabsf(channelData[i]),-96.0f);
    // Include knee compression
    float slope = 1 / *mRatio - 1;
    float overshoot = envSampleLevel - *mCompressionThreshold;
    if (overshoot <= -*mKnee/2)
        compressedLevel = in;
    if ((overshoot > -*mKnee/2) & (overshoot < *mKnee/2))
        compressedLevel = in + slope * ((overshoot + *mKnee / 2) * (overshoot + *mKnee / 2))/ (2 * *mKnee) ;
    if (overshoot >= -*mKnee/2)
        compressedLevel = in + (slope * overshoot) * in/envSampleLevel ;
    float newGain = Decibels::decibelsToGain(compressedLevel);
    if(channelData[i] < 0)newGain = -newGain;
    buffer.setSample(channel, i, newGain);
}


The plots look ok, and all compressors will distort if the release is short relative to the period of the waveform. But I wonder why ‘in’ is still in the calculation of compressedLevel. That should only depend on how far the envelope is above the threshold.

1 Like

I have changed the calculation so that the gain is detected with only the envelope level and then applied to the input as you mentioned (also described in the implementation by Romain Michon in Dynamic Compressor Tutorial). I think I must have overlooked, splitting the signal, using the envelope to calculate gain and then applying this to the original signal. Now there are no crackles, thanks for your help on this @ujam

However, there is still distortion when applying a threshold to a guitar (drums respond fine). Perhaps this due to the balance between attack and response which improves the output a little bit when tweaked. I think I need to look at optimising my code to use SIMD.

I used the envelope calculation that is described here Music DSP Envelope Detector … it would also be interesting to know if there are better ways to improve the accuracy of the envelope. Perhaps a different implementation that uses higher order filtering?

I doubt that SIMD will change the behavior of your compressor, as I don’t think it’s a performance issue. In general, a compressor with short attack and release times will distort your signal, especially with a fast envelop follower. However, a couple of milliseconds should be enough to reduce the distortion.

I very much liked this resource about dynamic range compressors: http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/documents/Reiss-Tutorialondynamicrangecompression.pdf
I ended up using a peak envelope follower (no filtering) and applied ballistics (attack & release times) in the decibels domain (supposed to sound better)

1 Like

Thanks @danielrudrich, I did a few optimisations and can confirm, as you say, that the issues are not due to this. The attack and release times definitely have an impact on the clarity and there is a small range where this appears much clearer. I think an improved envelope follower like the one in the article you shared will be my next approach - looks like I need to start studying that resource - thanks… and I’ll also try the dB ballistics.