Compressor: shorter attack/release times?

If you’d like some help with that, just start a new topic in the Compiling Audacity part of the forum and post details of your operating system as well as the actual errors that are shown.

I’m not a programmer, but I’ve had a go at making a patch to allow shorter Attack/Release times in the built-in compressor.
The patch needs to be applied to the source code prior to building Audacity.
compressor.patch (3.01 KB)

Hi Steve!

I’ll jump in here but will also answer the PM…

The patch would not apply via Tortoise SVN’s Apply Patch. Gale has just recently had a problem with my patches so it might be a Tortoise problem.

I applied manually and seem to have one minor problem. If one increments the Attack Time slider with the arrow keys (if you hadn’t clued me in to a potential problem a while back…) it increments as I expect 1.23 → 1.24; unfortunately, the newly renamed Release Time slider misbehaves–it jumps 1.20->1.30

I think just changing these two lines might work but it might not:

   decay = (double)(mDecaySlider->GetValue() / 100.0);

making the 10 into 100. I must go cook dinner but after dinner & hot tub I will come back and look more closely.

That changes the slider range to 0.1 t 3.00 seconds, but I want it to be 0.1 to 30.0 seconds.

Here’s a revised patch that keeps the decay slider as 1 decimal place.
compressor.patch (2.94 KB)
One thing I’ve noticed is that the Attack / Decay values in both the patched and un-patched version appear to be wrong.
An Attack setting of 2.0 seconds seems to actually produce an attack of about 1.3 seconds :confused:

Just dropping by between dinner & hot tub, grabbed the new patch…will be back in 2 hours to see why 2 sec equals 1.3 seconds…

First, your last patch applied just fine. Second, its GUI functioned as you designed (personally, I would have the Release in 1/100’s – two decimal places, see pic at bottom*).

Third, as to why 2.0 seems to end up at ~1.3…this gets real ugly and there is no “answer” just some conclusions and questions!
The dialog stores the Attack Time value into the mAttackTime variable of the EffectCompressor class:

class EffectCompressor: public EffectTwoPassSimpleMono {
   double    mAttackTime;

Searching all Audacity code on mAttackTime yields:

Find all "mAttackTime", Match case, Whole word, Subfolders, Find Results 1, "Entire Solution", "*.c; *.cpp; *.h"
  D:audioAudacitySVNsrceffectsCompressor.cpp(56):   mAttackTime = 0.2;      // seconds
  D:audioAudacitySVNsrceffectsCompressor.cpp(75):   gPrefs->Read(wxT("/Effects/Compressor/AttackTime"), &mAttackTime, 0.2f );
  D:audioAudacitySVNsrceffectsCompressor.cpp(104):   shuttle.TransferDouble( wxT("AttackTime"), mAttackTime, 0.2f );
  D:audioAudacitySVNsrceffectsCompressor.cpp(117):   dlog.attack = mAttackTime;
  D:audioAudacitySVNsrceffectsCompressor.cpp(132):   mAttackTime = dlog.attack;
  D:audioAudacitySVNsrceffectsCompressor.cpp(141):   gPrefs->Write(wxT("/Effects/Compressor/AttackTime"), mAttackTime);
  D:audioAudacitySVNsrceffectsCompressor.cpp(155):   mAttackInverseFactor = exp(log(mThreshold) / (mCurRate * mAttackTime + 0.5));
  D:audioAudacitySVNsrceffectsCompressor.cpp(757):   double    oldAttackTime = mEffect->mAttackTime;
  D:audioAudacitySVNsrceffectsCompressor.cpp(765):   mEffect->mAttackTime = attack;
  D:audioAudacitySVNsrceffectsCompressor.cpp(775):   mEffect->mAttackTime = oldAttackTime;
  D:audioAudacitySVNsrceffectsCompressor.h(78):   double    mAttackTime;
  Matching lines: 11    Matching files: 2    Total files searched: 1146

Examining each of the above statements where it is used, I see it is only “employed” in one place:

Compressor.cpp(155):   mAttackInverseFactor = exp(log(mThreshold) / (mCurRate * mAttackTime + 0.5));

everywhere else is just initializing, getting it into & out of the dialog or using it in the preview. From this we can determine that it is only used to set the value of mAttackInverseFactor. Searching all Audacity code on mAttackInverseFactor yields:

Find all "mAttackInverseFactor", Match case, Whole word, Subfolders, Find Results 1, "Entire Solution", "*.c; *.cpp; *.h"
  D:audioAudacitySVNsrceffectsCompressor.cpp(155):   mAttackInverseFactor = exp(log(mThreshold) / (mCurRate * mAttackTime + 0.5));
  D:audioAudacitySVNsrceffectsCompressor.cpp(156):   mAttackFactor = 1.0 / mAttackInverseFactor;
  D:audioAudacitySVNsrceffectsCompressor.cpp(374):		last *= mAttackInverseFactor;
  D:audioAudacitySVNsrceffectsCompressor.cpp(386):			last *= mAttackInverseFactor;
  D:audioAudacitySVNsrceffectsCompressor.h(87):   double    mAttackInverseFactor;
  Matching lines: 5    Matching files: 2    Total files searched: 1146

Looking at the three lines where mAttackInverseFactor is “employed” we see line 156 where it is used to calculate mAttackFactor and two places where it is used to determine a resulting peak value where, AFAICT it has no bearing on timeline.

Looking at line 156, mAttackFactor seems more promising, I search on mAttackFactor:

Find all "mAttackFactor", Match case, Whole word, Subfolders, Find Results 1, "Entire Solution", "*.c; *.cpp; *.h"
  D:audioAudacitySVNsrceffectsCompressor.cpp(156):   mAttackFactor = 1.0 / mAttackInverseFactor;
  D:audioAudacitySVNsrceffectsCompressor.cpp(398):			last *= mAttackFactor;
  D:audioAudacitySVNsrceffectsCompressor.cpp(406):			last *= mAttackFactor;
  D:audioAudacitySVNsrceffectsCompressor.h(86):   double    mAttackFactor;
  Matching lines: 4    Matching files: 2    Total files searched: 1146

Here we see that it is “employed” twice as
last *= mAttackFactor;
and, AFAICT this is timeline related. Reading the comment at the top of
void EffectCompressor::Follow
(srceffectsCompressor.cpp lines 303-338) by Roger B. Dannenberg might help our understanding:

"Follow"ing algorithm by Roger B. Dannenberg, taken from
   Nyquist.  His description follows.  -DMM

   Description: this is a sophisticated envelope follower.
    The input is an envelope, e.g. something produced with
    the AVG function. The purpose of this function is to
    generate a smooth envelope that is generally not less
    than the input signal. In other words, we want to "ride"
    the peaks of the signal with a smooth function. The
    algorithm is as follows: keep a current output value
    (called the "value"). The value is allowed to increase
    by at most rise_factor and decrease by at most fall_factor.
    Therefore, the next value should be between
    value * rise_factor and value * fall_factor. If the input
    is in this range, then the next value is simply the input.
    If the input is less than value * fall_factor, then the
    next value is just value * fall_factor, which will be greater
    than the input signal. If the input is greater than value *
    rise_factor, then we compute a rising envelope that meets
    the input value by working bacwards in time, changing the
    previous values to input / rise_factor, input / rise_factor^2,
    input / rise_factor^3, etc. until this new envelope intersects
    the previously computed values. There is only a limited buffer
    in which we can work backwards, so if the new envelope does not
    intersect the old one, then make yet another pass, this time
    from the oldest buffered value forward, increasing on each
    sample by rise_factor to produce a maximal envelope. This will
    still be less than the input.

    The value has a lower limit of floor to make sure value has a
    reasonable positive value from which to begin an attack.

Looking at the values of all the pertinent variables right after they are calculated from the dialog we see:

this	EffectCompressor * const
mAttackTime	2.0000000000000000	double
mThresholdDB	-18.000000000000000	double
mNoiseFloorDB	-40.000000000000000	double
mRatio	4.0000000000000000	double
mDecayTime	1.5200000000000000	double
mAttackFactor	1.0000234959090843	double
mAttackInverseFactor	0.99997650464296040	double
mDecayFactor	0.99996908522655759	double
mThreshold	0.12589254117941673	double
mCompression	0.75000000000000000	double
mNoiseFloor	0.010000000000000000	double
mGain	-6.2774385622041925e+066	double

So we know the proper values are arriving to be used in the calculations. I cannot “prove” the math as I know nothing about the science involved.

I leave a similar treatment of the mDecayTime (now Release Time) as an exercise for the reader !

my patch:
CompressorEFM5.patch (3.09 KB)
*my version:

OMG, what did I start?? :laughing:
I’ll try to build with the patch as soon as I can, Steve. Thanks for the time and work to both!

How should the patch be applied, btw?

My ugly reply was more directed at Steve ! Since you are on Linux, Steve should be the one to step you through the process. Since it is only a couple of minor changes you could do it by hand.

Have you gotten Audacity to build from scratch yet? If so, just to be safe, checkout (acquire by download) the current SVN head; in a Terminal, paste and execute this:

svn checkout Audacity

(the last separate word “Audacity” is the name of a directory into which the new files will go–you may use any legal folder name. If it already exists, make sure it is empty before checkout).

To apply manually, in that “Audacity” (or whatever you named it) folder, find the file:


Make sure to install a text editor which shows line numbers, using that editor open Compressor.cpp and scroll down so the first affected lines are visible. On Windows we have a GUI patching tool, it has a window which shows the changes it looks vaguely like this:

(I edited it down to get everything on a single screen and added the purple bars as edit separators). You can look at the line numbers of affected lines, and make all the changes (changed items are highlighted (background color) in red on the left (original values) and bright yellow on the right (patched version).

Exchanging patches between Linux and Windows can be challenging because of the different line endings. This makes manual patching a better solution unless both the creator and and applier really know what they are doing (I don’t).

That’s good to know, and now after giving myself a headache I’ve worked out what’s going on with the Attack/Release times. They appear to be the time for the envelope to fall/rise between “threshold” and “floor” levels, which means that the actual rise/fall times depend on amplitude and most of the time the attack/release times will be less than that set. It’s not a bug, it’s by design.

The first thing you should do is to build a straight (not patched) version to ensure that it builds correctly.

After that, put the patch file into the folder where the source code is (for example, if you are following these instructions the folder will be “audacity-read-only”.
Then open a terminal window and navigate to that folder.
Then use the command:

patch -p0 < name-of-patch-file

There may be minor variations on this method depending on how the patch has been made, but this should work for patches released by the Audacity developers and will work with the last patch that I posted. If you would prefer 2 decimal places for both Attack and Release I’ll post a new patch (basically the same as Edgar’s patch but I’m on Linux so there should be no problems with line endings).

Just patched Audacity 1.3.12 (until I find a way to compile the latest SVN version successfully I’m stuck with it) and it worked like a charm.
This is what I was starving for! Thanks a lot, Steve! :smiley:

You’re welcome. :wink:

BTW, strangely enough, it works best if I set it to a really short attack time while keeping the release/decay at 1 second. I thought shortening the release time around 50% would give better results, but oh surprise, it doesn’t. I suppose it has something to do with the “Hold” thing you mentioned before. I think its absence can be somewhat compensated by a slow release, just like you said. Anyway, it works just great. I can’t hear any perceivable pumping and the results I get are generally loud but also very natural. I am still looking forward to your nyquist compressor if you still have in mind to develop it. I am always open to new things, and your proposal looked really promising.

That’s why I made my patch to only have 1 decimal place for the Release time. It’s not often that a really short Release time is advantageous.

I don’t recall if you said what kind of music you are working on, but with dance music, a really short Attack and a Release time just a little shorter than the time between beats can get the sound pumpin’ in a good way (this technique actually works better without lookahead, using a limiter to control the attack peaks instead, but we don’t have that option).

It could be a while as I also have some other projects on the go, but I’ve made good progress with the Limiter stage already. I’ll probably release the limiter as a standalone plug-in before incorporating it into a compressor.

I don’t recall if you said what kind of music you are working on, but with dance music, a really short Attack and a Release time just a little shorter than the time between beats can get the sound pumpin’ in a good way.

Dance music usually has a very limited dynamic range, so when dealing with it I often apply a limiter to maximize loudness and that’s it. And that’s only with older pieces, because modern dance music very often comes already maximized or even clipped. Not exactly dance music, but I extracted the PCM audio track from Kraftwerk’s 2005 “Minimum-Maximum” live DVD and when I opened it in Audacity I saw that it was already brickwalled. But it sounded awesome anyway. Electronica doesn’t get as many side-effects from compression/limiting as other kinds of music I guess.
I usually apply compression on rock music, mainly hard rock and progressive rock (Pink Floyd and Mike Oldfield are good examples of music unsuitable for portable listening without using compression, but so is Led Zeppelin in many cases). And on those sources pumping can be really annoying. It can make a nice effect on fast tempos though.

What is the difference between the soft clipping limiter you posted here and the one you’re working on?

The soft clipping limiter (like all versions of the “Broadcast Limiter”) is a “wave shaping” effect. It looks at the instantaneous level of the waveform and if it exceeds a threshold then its level is reduced.

Here you can see a sine wave before and after applying the soft clipping limiter (without make-up gain):
You can see how the waveform peaks are gently distorted to reduce the peak level.

In contrast, the Limiter that I am currently working on is a very fast compressor with lookahead.
Here you can see that the gain is reduced just before the peak occurs so reducing the level of the peak. The gain then returns to normal after the peak has passed. This produces mush less harmonic distortion than the soft clipping limiter. The upper track is before processing and the lower track is after processing.

OK, that’s what I thought. So what’s the difference between “soft clipping” and “hard limiting”? Only the “round-shaping” of the wave?
I’ve used the Hard Limiter LADSPA plugin in Audacity before and it pretty much sounds like amplifying far beyond the 0dB limit and then turning the volume down again so the peaks simply disappear and the clipping is disguised/not shown. With the same amount of ugly distortion. It might be good for brickwalling, but what does make it better than actual clipping? Because both your plugin and the broadcast limiter sound noticeably better.
Maybe if used in real-time hard limiting may save your equipment from damage, but apart from that…

The Hard Limiter LADSPA effect chops off the top / bottom of the waveforms (ugly hard clipping) but the option “Residual level” puts the peaks back on after negatively amplifying them. If you set “Residual Level” to maximum then the output is the same as the unprocessed sound. At 0.5 the peaks above the threshold level are amplified by -6 dB (half the original hight of the portion that was above the threshold). At 0.0 the waveform is hard clipped at the threshold level.

If only a couple of dB is being clipped off and the Residual level set at around 0.3 the sound quality is not too bad, but the soft clipping limiter is better (less harsh overtones).
The LADSPA effect should be much faster than the Nyquist effect so may be a good option where only a few peaks need to be clipped in a very long track.

Then I was right, hard limiting is not my thing.
What about a multiband compressor? I know what it is and roughly how it works: it divides the wave into 3 frequency ranges so they can be compressed separately, right? High frequencies don’t need to be amplified as much as the low ones, so you can apply more compression on the low range without any or much perceivable change.
My question is: what is the actual advantage over a conventional compressor? Does it minimize side effects or give more natural results?