Skip to main content

Negative Compression

One blog post I’ve been meaning to write for a while is a comprehensive review of the design of dynamic range compressors and limiters, both digital and analog. Textbook compressor designs can be easily found, but like reverbs there are lots of weird little tricks from both hardware and software designs that supposedly define the distinctive musical character of different compressors. It may be a while before I finish that post because, while I’ve read a lot about the DSP of compressors, I don’t feel yet qualified to write on design. I haven’t yet designed a compressor plugin that I’m happy with, nor done a lot of compressor wine tasting, and the musical and psychoacoustic aspects of compressors are to me at least as important as the signal math.

Nevertheless, there’s a weird corner of compressor design that I feel inspired to talk about, and it’s called negative compression. It’s a feature of a few commercial compressors; I’m not sure which was the first, but I first learned about the concept from Klanghelm DC1A. Negative comp is the source of considerable confusion – just watch the Gearspace pundits go at it.

The brief description is that a standard compressor, upon receiving a signal with increasing amplitude, will reach a point where the output amplitude will increase at a slower rate. If the compressor is a perfect limiter, the output amplitude will hit a hard limit and refuse to increase. A negative compressor takes it further – the output signal will eventually get quieter over time as the amplitude increases. If you feed a percussive signal into a negative compressor and drive it hard enough, it will punch a hole in the signal’s amplitude, and can split a transient in two. It can be a pretty bizarre effect, and seems underutilized.

This explanation should be enough for most, but you know this blog. We do the math here. In this post, I will explain the basic mathematics of compressors to demystify negative compression, propose variants of negative compressors, and demonstrate how to do negative compression in SuperCollider.

Compressor basics

Let’s first briefly define standard feedforward compression. A compressor requires two audio signals – an input signal to be attenuated, and a sidechain to control the attenuation. The sidechain is usually identical to the input signal, or is a filtered version of it to control response to different frequency ranges; if they are entirely different signals then it’s sidechain compression. The compressor operates by feeding the sidechain through an envelope follower to track its amplitude, and a function called the gain curve maps the amplitude to a gain control signal. The gain control signal is simply multiplied by the input signal to produce the output of the compressor. There are many variations, but pretty much all feedforward compressors are based on this foundation.

The gain curve is critical to the sound, and for proper compressor action the gain curve should decrease as the sidechain gets louder. Let \(x \geq 0\) be the amplitude detected by the envelope follower (peak, RMS, whatever – as long as it increases roughly linearly with the amplitude of the sidechain), and let \(g(x)\) be the gain curve.

If the compressor isn’t in sidechain mode, then it’s meaningful to talk about a closely related function that doesn’t have a standard name but I call the “response curve.” The response curve is defined as \(f(x) = x g(x)\), and approximately represents the output amplitude of an input signal with stable amplitude such as a sine wave. (In practice, the envelope follower usually makes the output signal an imperfect sine wave, making “amplitude” ambiguous and frequency-dependent, but this is a rough model.) The response curve is usually what is depicted in pictures of compressors, but importantly, the response curve only applies if we aren’t sidechaining.

A common way to describe the response curve is that \(f(x) = x\) if \(x\) is below a threshold amplitude \(T\) (in linear amplitude units), and above that threshold \(f(x)\) increases by 1 dB for every \(R\ \text{dB}\) that \(x\) increases. \(R > 0\) is the compression ratio, usually described as a ratio such as 4:1. Mathematically:

\begin{equation*} f(x) = \begin{cases} x & \text{if } x < T \\ T (x / T)^{1 / R} & \text{if } x \geq T \\ \end{cases} \end{equation*}

\(f(x)\) is often drawn using a log-log graph, and such graphs appear as piecewise linear because \(\log_{10} f(10^x)\) is. Although I introduced the ratio in terms of dB, there’s no specific dependency on the decibel scale in this formula.

As \(R \rightarrow \infty\) the response curve approaches \(f(x) = \text{min}(x, T)\), which is a limiter with a ratio of ∞:1. Higher ratios correspond to heavier compression.

The gain curve is computed as \(g(x) = f(x) / x\), which gives \(g(x) = 1\) for \(x < T\) and \(g(x) = (x / T)^{1 / R - 1}\) otherwise. While the response curve conventionally increases, the gain curve decreases past the threshold.

This textbook “log-log piecewise linear” model produces a perfectly acceptable compressor, but it’s not universal. It’s merely a visual simplification of how compressors work. Practical compressors – especially analog designs – can have arbitrary curves for \(f(x)\), and while they might offer controls labeled “threshold” and “ratio,” these are more romantically defined and based on fitting the piecewise model to the curve.

Negative compression

Negative compression happens when \(f(x)\) is nonmonotonic, such as decreasing above the threshold. In the textbook model, this is the natural extension of flattening the compressor’s curve so it bends back on itself. If we define the unitless “log-log slope” above the threshold as \(S = 1 / R\), normal compressor operation is \(0 < S < 1\), \(S = 0\) is a limiter, and \(S < 0\) is negative compression.

In terms of ratio, normal compressors are \(R > 1\), \(R \rightarrow \infty\) approaches a limiter, and for negative compression, \(R\) wraps around back to negative values, \(R < 0\). Counterintuitively, negative ratios are “beyond” a positive infinite ratio, and -4:1 is “more compressed” than ∞:1; this is a consequence of ratio being the reciprocal of the slope. (The region \(0 < R < 1\), corresponding to compression ratios like 0.5:1, is not negative compression but rather expansion or noise gating.) This trips people up when discussing negative compression because they’re used to thinking in ratios, when slope more naturally extends to negative numbers.

Like I said, a negative compressor can result in an output decrease in amplitude for an input increase in amplitude, creating a “vacuum” effect on transients. But this is only true if we aren’t doing sidechain compression. Remember when I said that the response curve only makes sense if the sidechain signal is the same as the input signal? For such cases, it’s more useful to look at \(g\) than \(f\). Recalling that \(g(x) = (x / T)^{1 / R - 1}\) above the threshold, we can see that regardless of whether \(R < 0\) or \(R > 1\), the gain curve is always nonincreasing because the exponent \(1 / R - 1\) is negative. It turns out that sidechain compression with a negative ratio is just extra-dramatic sidechain compression, and isn’t all that special compared to positive ratios.

Variants

Soft knee: Negative compression can be done with either a hard knee or a soft knee. The soft knee becomes less and less audible as other settings become more dramatic. Aside from softening the response curve, the gain control signal can also be lowpass filtered prior to multiplication with the input signal. This is a non-textbook trick found in some commercial designs (I first saw it in an analog schematic).

Zigzag compression: One natural extension is to have a zigzagging response curve with multiple critical points. Ordinary compression is to zigzag compression as saturation is to wavefolding. Reducing the attack and decay times of a compressor makes it behave more and more like a waveshaper, so zigzag compression is in a sense a generalization of wavefolding.

Multiband negative compression: self-explanatory.

Implementation in SuperCollider

I will take a moment to rant: don’t use Compander. It downsamples the gain control signal to control rate to save CPU usage, and although it does linear interpolation, the aliasing artifacts are pretty audible to me at a standard block size of 64, and very audible if I try to set fast attack and release.

Luckily, building a feedforward compressor is easy with Amplitude, as long as we remember to use Amplitude.ar and not Amplitude.kr. Here’s my implementation:

// Public Domain, Creative Commons Zero

(
var compGain;
compGain = { |in, threshold, slope, attack = 0.05, release = 0.3|
    var inArray, amplitude;
    inArray = if(in.isArray) { in } { [in] };
    amplitude = (Amplitude.ar(inArray, attack, release).sum / inArray.size.sqrt).max(-100.dbamp).ampdb;
    ((amplitude - threshold).max(0) * (slope - 1)).lag(attack).dbamp;
};

{
    var snd;
    snd = SinOsc.ar(440) * Env.perc(0.01, 1.0).ar;
    snd = snd * compGain.(snd, threshold: -10, slope: -2, attack: 0.1, release: 0.3);
    (snd * -6.dbamp) ! 2;
}.play(fadeTime: 0);
)

threshold is in dBFS, and slope is \(S\). Again, slope is the reciprocal of ratio, so the setting slope: 1 / 4 corresponds to a 4:1 compressor, slope: 0 is a limiter, and slope: -1 / 5 is a -5:1 ratio.

You may specify either a single channel or an array of signals, in which case individual envelope followers are applied to each channel and the amplitudes are combined under the assumption of equal power panning. This ensures that the different channels are ducked by equal amounts, preserving the spatial image. This is often called “stereo link” in commercial units. If you’d rather compress each channel individually (as in “dual mono”), invoke compGain individually for each channel: snd.collect { |channel| channel * compGain.(channel, ...) }.

The .lag(attack) method call does post-smoothing of the gain control as mentioned before. I like it because it maintains a more organic sound even for more dramatic negative comp settings. Feel free to remove it.

Conclusion

Thank you for reading my conclusion.