Skip to content

Instantly share code, notes, and snippets.

@x42
Last active August 25, 2022 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save x42/eeb2aa9f9cc4a9083fb2cf2d86645c9a to your computer and use it in GitHub Desktop.
Save x42/eeb2aa9f9cc4a9083fb2cf2d86645c9a to your computer and use it in GitHub Desktop.
*.swp
*.lv2/
*-svg/
mcomp.lv2/mcomp.so : mcomp.dsp
faust2lv2 mcomp.dsp
mcomp-svg: mcomp.dsp
faust -svg mcomp.dsp
show: mcomp-svg
x-www-browser mcomp-svg/process.svg
clean:
-rm -rf mcomp-svg
-rm -rf mcomp.lv2
.PHONY: show clean
declare name "mComp";
declare author "Robin Gareus";
declare license "GPLv2+";
import ("stdfaust.lib");
/* Calculate compressor gain redux.
*
* This is similar to `(co).peak_compression_gain_mono_db`, except
* signal power is used, and there is a fixed exponential knee.
*
* `strength` : [0,1]. The amount of gain or attenuation to be applied
* 0: no compression; 0.25: ratio 1:2 ; 1.0: hard limit.
* The compression ratio (dB/dB above threshold) is given:
* ratio = 1/(1-sqrt(st))
* `threshold`: Level (in dB/RMS) above which compression ratio is applied,
* Note, since there is an exponentially curved gain (knee), the redux is
* -3dB at threshold. If `strength` is 1 (limiter), the threshold is also
* the max output level when the input exceeds the threshold value.
* Note: attack time is still relevant, this is not a brickwall limiter.
* `att`: Attack time (in seconds) - Time it takes for the signal to become fully compressed
* after exceeding the threshold.
* `rel`: Release time (in seconds) - Minimum recovery time to uncompressed signal-level
* after falling below threshold.
* `hld`: Hold (boolean) - Retain current attenuation when the signal subceeds the threshold.
* This prevents modulation of the noise-floor and can counter-act 'pumping'.
*
* return value is in gain to be applied in dB
*
* Example:
* process= _ <: par(i,2,_) : (_, (compcalc (st, th, at, rl, 0) : ba.db2linear)) : * ;
*/
compcalc (strength, thresh, att, rel, hld, x) = loop ~ (_ , _) : select3 (2) *-strength
with {
loop (zr1, zr2) = r1, r2, ba.linear2db (max (ma.EPSILON, sqrt (2 * r2))) - thresh
with {
/* calculate signal power relative to threshold, LPF using attack time constant */
x2 = lvl ~ _
with {
lvl (y) = y + w_att * (p_thr + (x * x) - y)
with {
p_thr = .5 * pow (10.0, 0.1 * thresh);
w_att = 0.5 / (att * ma.SR);
};
};
p_hld = ba.if (hld != 1, 0, pow (10.0, 0.1 * thresh)); // 2 * p_thr
/* apply fall off using release-time */
w_rel = 3.5 / (rel * ma.SR);
r1 = ba.if (zr1 < x2, x2, ba.if (x2 < p_hld, zr1, zr1 - w_rel * zr1));
r2 = ba.if (zr2 < x2, x2, ba.if (x2 < p_hld, zr2, zr2 + w_rel * (zr1 - zr2)));
};
};
MultiBandComp (NBand, NChn) =
si.bus (NChn) <: si.bus (2 * NChn) // split: key, signal
: calc_gain
: apply_gain
: makeup_gain
with {
/* key/sidechain. Analyze input signal, and calculate gain to be applied
* to the signal. The live signal is passed though after the analysis data.
*
* (key, signal) -> (gains, signal)
*/
calc_gain = ((par (i, NChn, analyze) : calc_redux), si.bus (NChn));
/* Spectrum analyzer. Note: an.analyzer returns the data in reverse
* order. So the wires are reversed here.
*
* (1 channel key) -> (levels per freq band)
*/
analyze = an.analyzer (6, (ctls_freq)) : ro.cross (NBand);
/* Run the compressor on the key signal for each band,
* and send the compression redux level to meters.
*
* Compressor settings are identical for all channels of each band.
* Yet each band has its own threshold, and stenght/ratio,
* while attack/release are common settings for all.
*
* This processes all channels. This allows the gain for
* all channels of a given band to be linked.
*
* (levels per freq band, ..) -> (gain per freq band, ..)
*/
calc_redux =
(ctls_strength, ctls_thresh, si.bus (NBand * NChn))
: ro.interleave (NBand, 2 + NChn)
: par (i, NBand, compressor (NChn)) : si.bus (NChn * NBand)
: par (b, NBand, par (c, NChn, meter (b + 1, c + 1)))
;
/* Apply the gain, using a shelf filter cascade (linear phase)
*
* (gains, signal) -> (signal)
*/
apply_gain =
ro.interleave (NChn, NBand + 1) // separate channels
: par (i, NChn, ro.cross (NBand), _) // reverse gains
: par (i, NChn, shelfcascade ((ctls_freq)))
;
makeup_gain = par (i, NChn, _ * ctl_mg);
compressor (N, st, th) = calc_comp_db_NChn (si.smoo (st), si.smoo (ctl_th + th), ctl_at, ctl_rl, ctl_hd, N);
calc_comp_db_NChn (s,t,a,r,h,1) = compcalc (s,t,a,r,h);
calc_comp_db_NChn (s,t,a,r,h,N) = par (i, N, compcalc (s,t,a,r,h))
<: (si.bus (N), (ba.parallelMin (N) <: si.bus (N)))
: ro.interleave (N, 2)
: par (i, N, select2 (ctl_lk))
;
/* higher order low, band and hi shelf filter primitives */
ls3 (f, g) = fi.svf.ls (f, .5, g3) : fi.svf.ls (f, .707, g3) : fi.svf.ls (f, 2, g3) with {g3 = g/3;};
bs3 (f1, f2, g) = ls3 (f1, -g) : ls3 (f2, g);
hs3 (f, g) = fi.svf.hs (f, .5, g3) : fi.svf.hs (f, .707, g3) : fi.svf.hs (f, 2, g3) with {g3 = g/3;};
/* Cascade of shelving filters to apply gain per band.
*
* `lf` : list of frequencies
* followed by (count(lf) + 1) gain parameters in reverse order
* (the first input is the gain of the last stage).
*/
shelfcascade (lf) = fbus (lf), ls3 (first (lf)) : sc (lf)
with {
sc ((f1, f2, lf)) = fbus ((f2, lf)), bs3 (f1, f2) : sc ((f2, lf)); // recursive pattern
sc ((f1, f2)) = _, bs3 (f1, f2) : hs3 (f2); // halting pattern
fbus (l) = par (i, outputs (l), _); // a bus of the size of a list
first ((x, xs)) = x; // first element of a list
};
meter (b, c) = _<: attach (_, (max (-12):min (0):vbargraph ("Redux [unit:dB][symbol:redux%b%c]redux band %b chn %c", -12, 0)));
ratio2stength (r) = (1 - r)^2 / r^2;
//TODO - ensure freq are ordered, and maintain a minimum distance -> default/min/max
dft_freq (i, b, t) = b * pow ((t / b), i / (NBand - 1));
ctls_freq = par (i, NBand - 1, vslider ("[unit:Hz][scale:log]Frequency %i", dft_freq (i, 500, 17500), 20, 20000, 1));
ctls_thresh = par (i, NBand, vslider ("[%i][unit:dB][scale:log]Band %i Threshold", 0, -24, 0, 0.5));
ctls_strength = par (i, NBand, vslider ("[%i][unit:%]Band %i Ratio", 2, 1, 16, 0.1) : ratio2stength);
ctl_th = vslider ("Threshold[unit:dB][scale:log]", 0, -60, 0, 1); // master thresh
ctl_at = vslider ("Attack[unit:s][scale:log]", 0.01, 0.001, 0.1, .01);
ctl_rl = vslider ("Release[unit:s][scale:log]", 0.3, 0.03, 3.0, .1);
ctl_mg = vslider ("Makeup Gain[unit:dB][symbol:makup_gain]", 0, -6, 6, 0.5) : ba.db2linear;
ctl_hd = checkbox ("Hold");
ctl_lk = checkbox ("Stereo link");
};
/* 4 band, stereo multiband compressor */
process= MultiBandComp (4, 2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment