Help with dynamic range compression function (audio)

Posted by MusiGenesis on Stack Overflow See other posts from Stack Overflow or by MusiGenesis
Published on 2010-05-25T20:03:14Z Indexed on 2010/05/26 3:21 UTC
Read the original article Hit count: 471

Filed under:
|
|

I am writing a C# function for doing dynamic range compression (an audio effect that basically squashes transient peaks and amplifies everything else to produce an overall louder sound). I have written a function that does this (I think):

alt text

public static void Compress(ref short[] input, double thresholdDb, double ratio)
{
    double maxDb = thresholdDb - (thresholdDb / ratio);
    double maxGain = Math.Pow(10, -maxDb / 20.0);

    for (int i = 0; i < input.Length; i += 2)
    {
        // convert sample values to ABS gain and store original signs
        int signL = input[i] < 0 ? -1 : 1;
        double valL = (double)input[i] / 32768.0;
        if (valL < 0.0)
        {
            valL = -valL;
        }
        int signR = input[i + 1] < 0 ? -1 : 1;
        double valR = (double)input[i + 1] / 32768.0;
        if (valR < 0.0)
        {
            valR = -valR;
        }

        // calculate mono value and compress
        double val = (valL + valR) * 0.5;
        double posDb = -Math.Log10(val) * 20.0;
        if (posDb < thresholdDb)
        {
            posDb = thresholdDb - ((thresholdDb - posDb) / ratio);
        }

        // measure L and R sample values relative to mono value
        double multL = valL / val;
        double multR = valR / val;

        // convert compressed db value to gain and amplify
        val = Math.Pow(10, -posDb / 20.0);
        val = val / maxGain;

        // re-calculate L and R gain values relative to compressed/amplified
        // mono value
        valL = val * multL;
        valR = val * multR;

        double lim = 1.5; // determined by experimentation, with the goal
            // being that the lines below should never (or rarely) be hit
        if (valL > lim)
        {
            valL = lim;
        }
        if (valR > lim)
        {
            valR = lim;
        }

        double maxval = 32000.0 / lim; 

        // convert gain values back to sample values
        input[i] = (short)(valL * maxval); 
        input[i] *= (short)signL;
        input[i + 1] = (short)(valR * maxval); 
        input[i + 1] *= (short)signR;
    }
}

and I am calling it with threshold values between 10.0 db and 30.0 db and ratios between 1.5 and 4.0. This function definitely produces a louder overall sound, but with an unacceptable level of distortion, even at low threshold values and low ratios.

Can anybody see anything wrong with this function? Am I handling the stereo aspect correctly (the function assumes stereo input)? As I (dimly) understand things, I don't want to compress the two channels separately, so my code is attempting to compress a "virtual" mono sample value and then apply the same degree of compression to the L and R sample value separately. Not sure I'm doing it right, however.

I think part of the problem may the "hard knee" of my function, which kicks in the compression abruptly when the threshold is crossed. I think I may need to use a "soft knee" like this:

alt text

Can anybody suggest a modification to my function to produce the soft knee curve?

© Stack Overflow or respective owner

Related posts about c#

Related posts about function