Dynamic Range Compression - Release???

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Ichad.c wrote:Not anything uber-special, but has helped me alot:

Code: Select all

float smoothABS ( float a, float smoothness )
{
 return (sqrtf((a*a)+ smoothness))- sqrtf(smoothness);
}
Once you have a decent abs, you can make your own min/max/clip, and then go from there.

Regards
Andrew
I'm using a variation of this for simulating hard/soft knee. But I don't remove the square root, as for big a, it induces a biais on the result.

Post

FabienTDR wrote:Have a closer look a diode's behaviour, it's in no way equivalent to an "if". The argumentation and conclusion in your last paragraph is confusing. Forget the analogue thing for a second.
Thanks FabienTDR. Will think on it some more.

Analog designers often were dismayed by the diode's imperfections. When they could afford the cost would use various strategies for circuitry emulating "precision full wave rectifiers" and "precision diodes". Various tricks to eliminate the variable transconductance and voltage drops. Or use transistor circuits as diodes, or whatever.

They tried to make full wave rectifiers as close to abs() as they could afford to get, and tried to make precision diodes as close as possible to ideal if/then statements.
The main issue is the requirement for bandlimiting (Nyquist). The "if"s in the example code above extend the bandwidth to infinity in at least 3 cases. This means: plenty aliasing + plenty aliasing + plenty aliasing. In short, a pile of garbage.

There's no proper way of bandlimiting a branch, it's a dead end. You can't really make an "if" less "branchy". I explained above how the AR "if" can be eliminated.

The code doesn't handle the potential bandwidth extension introduced by the central gain multiplication => it generates even more aliasing, in a highly reliable and reproducible manner.
I'm too ignorant to argue the issue, but am curious about the large aliasing? Am cognizant of the intermodulation distortion issues.

That simplest example first order single stage if/then AR envelope-- Consider a typical attack = 10ms, release = 100 ms. TC of 10 ms is Fc of 15.9 Hz and TC of 100 ms is 1.59 Hz. So at 16Khz the attack nonlinearity has been attenuated -60 dB, and the release nonlinearity has been attenuated -80 dB? A 1 second TC leveling amp would attenuate any nonlinearity of the abs and/or IfThen by 100 dB at 16KHz?

Seems IM distortion in the envelope ripple would be a worse problem than aliasing even with the simple example, using common AR values?

I've typically inherited old analog envelope habits of at least two stage smoothing. The abs and if/then in a first stage instant attack, 10ms release first order section, feeding the AR stage. Because AR values I typically use, stay in release most of the time, any aliasing of the release stage in the control envelope should be attenuated in excess of -140 dB at nyquist?

What am I missing? Been a few years since I wrote a new AR compressor, and wouldn't hold up my AR compressor as the finest example of the art, but it didn't sound obviously ugly to my ear. Maybe my ear ain't golden enough.

The envelope in some of them had extra strategies to reduce ripple on low freq sustained notes, without lengthening the release TC, but that would be more IM suppression rather than aliasing suppression.

Apologies rambling. Am not against better ways to do it if the abs method is so evil. :)

Post

JCR, the transition discontinuity between attack and release is not attenuated at all. Of course, a "nonlinear" AR filter with exactly the same A and R values is an exceptional case (it's perfectly linear). But, have a look at a more realistic example, A = 0.1ms, R = 200ms. Depending on the detector, this can easily pollute the whole area above 4-5kHz with non-harmonic garbage.

The main problem is that the exact position of the A/R "switch" is unknown in standard PCM data, it has a potentially big timing error (it switches A to R to early or too late, most probably not at the right moment). Aliases result.

Also, the multiplication of two signals with bandwidth N results in bandwidth 2N (in the worst case of course).


Just as an example, if designer chooses to use an abs() rectifier, the upper part of the spectrum will already be heavily aliased before it hits the AR filter. abs() creates a pretty strong 3rd harmonic series up to infinity, much like clipping! So, the AR filter will already see a full bandwidth signal (full of aliasing) and will try to extend the bandwidth of this signal again. The hard A/R's harmonic pattern extends to infinity as well, effectively forcing bad-ass aliasing into the audio and control signal.

Everything the designer can do at this point is to "wash out" the worst things, which in turn slows the compressor down beyond usefulness. This post filtered signal still contains the heavy aliasing error, so it doesn't really solve the problem. It vital to avoid direct branching wherever possible to keep bandwidth extension under control. "IF" messes with infinity, which in turn is a horrible advice in a band-limited environment.

Use low order polynomials and similar animals instead! For abs(), have a look at x² for a true starting point :)

The Nyquist theorem practically forbids the use of hard switching (or any bandwidth extension beyond Nyquist, but the infinity cases are particularly uncontrollable and "incurable"), if you do, don't expect the theorem to work for you!
Fabien from Tokyo Dawn Records

Check out my audio processors over at the Tokyo Dawn Labs!

Post

Hi Matt,

Thanks for your help, penny is starting to drop now. I am referring to "Digital Audio Signal Processing" by Udo Zoller, which has a section, Dynamic Range Control. I am trying to follow this section at the moment, getting there, but something has thrown me. Hopefully the image below displays so I can illustrate my point.
CompressUdoZol.jpg
Consider the compressor path here using peak level measurement. Earlier in this chapter, Zoller defines the peak signal as:

xPEAK(n)=(1−AT)·xPEAK(n−1)+AT ·|x(n)| for |x(n)| > xPEAK(n−1)
xPEAK(n)=(1−RT)·xPEAK(n−1) for |x(n)| < xPEAK(n−1)

Where AT and RT are coefficients for a 1-pole low-pass filter like you mentioned above. They are defined using exponentials including attack & release times and sample period.

From the diagram which I've attached (and hopefully displays), I can follow everything except it appears that attack and release are applied twice in the gain computation stage.
My understanding of this diagram is that xPEAK(n) is calculated using the formulae above, so attack and release times will be applied at the first stage.
Now, again at the final stage of the gain signal computation, we have AT/RT/SMOOTH which implies that attack/release/smoothing is applied here again. In reference to this stage, the book reads:

"Thesequence f(n) is smoothed with a SMOOTH filter in the limiter case, or weighted with corresponding attack and release times of the relevant operating range (compressor,expander or noise gate)."

How or why would attack and release times be applied twice? This does not seem correct to me. If the diagram is correct, could xPEAK(n) mean something different in the diagram than the definition above?

Thanks
Ger
You do not have the required permissions to view the files attached to this post.

Post

Actually, the first stage doesn't have A/R. The whole log2/2** is just the way you create the gain (reduction or amplification), but it is often implemented as a LUT (if it is a compressor/limiter, the lookup is different than an expander/noise gate). In this case, you only have discreet output gain, and you have to smooth it. As you already have a smoother, the A/R filter, it is possible to put it after the gain stage. Be aware that filters are non commutative, so the result is a little bit different.
Also, the gain is inferior to 1 for an active compressor, so if an A/R filter is quite easy to implement after the peak/power stage, you need to switch attack and release if it is after the gain stage.

Post

Hi Miles,

Thanks for your response. Just focusing on the first and final stages here, I thought that maybe the first stage does not use attack/release filtering. But if this is the case, what exactly is xPEAK(n) in this scheme and how is it calculated? Is it simply |x(n)|?
Also, if the static curve is applied to the output of this, will log2(|x(n)|) give the correct decibel level to apply the static curve/threshold to? I'm thinking it sould be 20*log10(|x(n)|)?

Thanks
Ger

Post

Yes, it can be as easy as the abs value. Sometimes, you may want to filter it a little bit.
For the log issue, you can actually use any log, as it is just an additional constant. In the general case, you may have the natural log, and just have a custom LT, CS...

Post

Miles1981 wrote:Yes, it can be as easy as the abs value. Sometimes, you may want to filter it a little bit.
Ok, the diagram makes sense with this approach, though filtering here also will affect attack and delay times I would think.
Miles1981 wrote:For the log issue, you can actually use any log, as it is just an additional constant. In the general case, you may have the natural log, and just have a custom LT, CS...
Get you.. the threshold just needs to follow the same scale as the gain calculation.

Thanks again, hopefully I can come up with something resembling a digital compressor now! :)

Post

Hi guys,

I've implemented a version of the diagram in MATLAB. y is a wav file of a drum recording I have and I normalized it to using y = y./max(abs(y)). Here's the code:

Code: Select all

tAttack = 0.1;
tRelease = 200;
Fs = 48000; 
input = y;
thresh = -10;
ratio = 0.5; 
Ts = 1/Fs;
sigLength = length(input);


AT = 1 - exp(-2.2*Ts/(tAttack/1000));
RT = 1 - exp(-2.2*Ts/(tRelease/1000));

xPeak = abs(input);
x_dB = 20*log10(xPeak);

for i=1:sigLength
    if (x_dB(i) > thresh)
        delta = x_dB(i) - thresh;
        delta = delta*ratio;
        x_dB(i) = thresh + delta;
    end
end

xLin = 10.^(x_dB/20);


gainFactor(1)=1;
for i=2:sigLength
    if xLin(i) > gainFactor(i-1)
        gainFactor(i)=(1-AT)*gainFactor(i-1)+ AT*xLin(i);
    else
        gainFactor(i)=(1-RT)*gainFactor(i-1);
    end
end
gainFactor = gainFactor';

compressedSig = y.*gainFactor;
Here is the input audio, y, in the code above:
input_drums.jpg
Here is compressedSig:
compressedInput.jpg
Here's a plot of the gainFactor signal:
gainFactor.jpg
It seems to me like it is being compressed too much. Can anyone see anything wrong here? Or maybe it is working correct according to the crude implementation?

Thanks
Ger
You do not have the required permissions to view the files attached to this post.

Post

I would also use xLin for RT, just as for the attack.

I think you have an issue with xLin. It should be one by default, when not compressed, but it does not, it is equal to the signal power. I would suggest to replace x_dB by xout_dB, equal to one by default, and if x_dB > threshold, xout_dB = 1 - delta * ratio. This is almost what I'm using for my compressor (although I don't have if/then, but a smooth function instead that behaves closely to this).

Post

Miles, you're right, thanks for pointing that out. I was letting x_dB = the original signal when under the threshold. I've added an else to let it equal 0 when under the threshold. I'm still seeing some strange results though, the gain factor minimum is ~0.99, where the xLin minimum is ~0.32. It's something to do with the AT/RT smoothing, not working correctly. I did try to apply release the same as attack as you suggested, but it didn't work.
I'll keep playing with it. Thanks for your help!

Post

I also find the AT/RT computation strange. exp(-1/(fs*time_in_s)) is what I use, not 1-x.

Post

Sorry, my mistake... I messed up the 1-x and x coeffs in our equations ;)

Post

Yes, I got those coefficient formulae from Udo Zoller's book.. something not right with them. I think it's actually the RT coefficient, for the RT time, I should have a release coefficient around the 0.9 region where the formula is giving me an RT coeff in the 0.1 region.
I tried a different set of formulae, those used in the link Matt provided for the simple envelope follower above:

Code: Select all

AT = exp(log(0.01)/( tAttack * Fs * 0.001));
RT = exp(log(0.01)/( tRelease * Fs * 0.001));
Adjusted the gain filtering equations to this:

Code: Select all

for i=2:sigLength
    if xLin(i) > gainFactor(i-1)
        gainFactor(i)=(1-AT1)*gainFactor(i-1) + AT1*xLin(i);
    else
        gainFactor(i)=(1-RT1)*gainFactor(i-1) + RT1*xLin(i);;
    end
end
And it appears to be working now.

Moving onto knee interpolation now, seems 2nd order is best.

Post

Your log(0.01) is basically -2 * log(10), but your issue is not what you think it is. Your computation was correct before, but not the test.
I don't have all your current code, but I made the mistake before. If you compare the gain factors, you have to invert AT and RT position. If your gain is less than 1, it means that you are compressing. The lesser the gain, the more you are compressing, and so when the new gain is lower than the former gain, you have to use AT (and not RT in what I see of your code).
Try with AT = RT, and you will see the issue.

Be aware that this is only an issue for compressor, but not for an expander, as the gain is monotonly increasing (and not decreasing)

Post Reply

Return to “DSP and Plugin Development”