-
Notifications
You must be signed in to change notification settings - Fork 1
/
compressors.lib
327 lines (260 loc) · 14.2 KB
/
compressors.lib
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
declare author "Bart Brouns";
declare version "1.2";
declare license "GPLv3";
import("stdfaust.lib");
import("slidingReduce.lib");
// A library of compressor building blocks, compressors and some general utilities.
// The four most interesting examples:
// If wanted I can make demo versions, with tooltips that are more appropriate for each model.
// process =
// FFcompressor_N_chan(strength,threshold,attack,release,knee,prePost,link,meter,2);
// FBFFcompressor_N_chan(strength,threshold,attack,release,knee,prePost,link,FBFF,meter,2);
// RMS_FBFFcompressor_N_chan(strength,threshold,attack,release,knee,prePost,link,FBFF,meter,2);
// RMS_FBcompressor_peak_limiter_N_chan(strength,threshold,thresholdLim,attack,release,knee,link,meter,2);
// all benchmarks with a stereo RMS_FBcompressor_peak_limiter_N_chan,
// rmsMaxSize = 2:pow(16) and compiled with:
// time faust2jaqt -t 99999999 -time -double -vec test.dsp
// blockSize = 64; // takes forever to compile
// blockSize = 128; // compile time 296s, 34% cpu in rt thread
blockSize = 256; // 161s, 26%
// blockSize = 512; //239s, 30%
// blockSize = 1024; // 829s, 50%
// binaryBlockDelaySum blows them all out of the water: 7.4%
// It also compiles quick.
// Same performance with all compilation options,
// but needs -double as well, because of si.lag_ud bug below
// rmsMaxSize = 1024; // for block diagram of blockDelaySum
// rmsMaxSize = 8; // for block diagram of binaryBlockDelaySum
// rmsMaxSize = 2:pow(15);
// rmsMaxSize = 2:pow(16); // highest usable for blockDelaySum, used for benchmarking.
rmsMaxSize = 2:pow(17); // highest usable for faust2lv2
// rmsMaxSize = 2:pow(19); // Nice and long: 11 seconds.
// binaryBlockDelaySum is practically equally cheap at any size, up to:
// rmsMaxSize = 2:pow(25);
// rmsMaxSize = 2:pow(27);
// Crashes when ypu go higher, cause 2^25 eats more than half my RAM.
// To be expected, at 760 seconds of RMS-time!
maxRelTime = rmsMaxSize/sr;
sr = 44100;
// note: si.lag_ud has a bug where ba.if you compile with standard precision,
// down is 0 and prePost is 1, you go into infinite GR and stay there
peak_compression_gain_mono(strength,thresh,att,rel,knee,prePost) =
abs:bypass(prePost,si.lag_ud(att,rel)) : ba.linear2db : gain_computer(strength,thresh,knee):bypass((prePost*-1)+1,si.lag_ud(rel,att)) : ba.db2linear;
// peak_compression_gain_mono(strength,thresh,att,rel,knee) has a more traditional knee parameter than
// compression_gain_mono(ratio,thresh,att,rel), which also has an internal parameter called knee,
// but that is a time-smoothing of the gain-reduction.
// This knee is a gradual increase in gain reduction around the threshold:
// Below thresh-(knee/2) there is no gain reduction,
// above thresh+(knee/2) there is the same gain reduction as without a knee,
// and in between there is a gradual increase in gain reduction.
// prePost places the level detector either at the input or after the gain computer
// this turns it from a linear return-to-zero detector into a log domain return-to-threshold detector
// source:
// Digital Dynamic Range Compressor Design
// A Tutorial and Analysis
// DIMITRIOS GIANNOULIS (Dimitrios.Giannoulis@eecs.qmul.ac.uk)
// MICHAEL MASSBERG (michael@massberg.org)
// AND JOSHUA D. REISS (josh.reiss@eecs.qmul.ac.uk)
// It uses a strength parameter instead of the traditional ratio, in order to be able to
// function as a hard limiter.
// For that you'd need a ratio of infinity:1, and you cannot express that in faust
// Sometimes even bigger ratios are usefull:
// For example a group recording where one instrument is recorded with both a close microphone and a room microphone,
// and the instrument is loud enough in the room mic when playing loud, but you want to boost it when it is playing soft.
gain_computer(strength,thresh,knee,level) =
select3((level>(thresh-(knee/2)))+(level>(thresh+(knee/2))),
0,
((level-thresh+(knee/2)):pow(2)/(2*knee)) ,
(level-thresh)
) : max(0)*-strength;
RMS_compression_gain_mono(strength,thresh,att,rel,knee,prePost) =
RMS(rel): bypass(prePost,si.lag_ud(att,0)) : ba.linear2db : gain_computer(strength,thresh,knee) : bypass((prePost*-1)+1,si.lag_ud(0,att)) : ba.db2linear;
// Slow:
// RMS(time) = pow(_,2):(blockDelaysum(s,blockSize,rmsMaxSize)/s):sqrt with {
// s = int(time*sr):max(1);
// };
// Fast:
RMS(time) = slidingRMSn(s,rmsMaxSize) with {
s = int(time*sr):max(1);
};
// generalise compression gains for N channels.
// first we define a mono version:
compression_gain_N_chan(strength,thresh,att,rel,knee,prePost,link,1) =
peak_compression_gain_mono(strength,thresh,att,rel,knee,prePost);
// The actual N-channel version:
// Calculate the maximum gain reduction of N channels,
// and then crossfade between that and each channel's own gain reduction,
// to link/unlink channels
compression_gain_N_chan(strength,thresh,att,rel,knee,prePost,link,N) =
par(i, N, peak_compression_gain_mono(strength,thresh,att,rel,knee,prePost))
<:(si.bus(N),(minimum(N)<:si.bus(N))):ro.interleave(N,2):par(i,N,(crossfade(link)));
// an RMS versions of the above
RMS_compression_gain_N_chan(strength,thresh,att,rel,knee,prePost,link,1) =
RMS_compression_gain_mono(strength,thresh,att,rel,knee,prePost);
RMS_compression_gain_N_chan(strength,thresh,att,rel,knee,prePost,link,N) =
par(i, N, RMS_compression_gain_mono(strength,thresh,att,rel,knee,prePost))
<:(si.bus(N),(minimum(N)<:si.bus(N))):ro.interleave(N,2):par(i,N,(crossfade(link)));
// feed forward compressor
FFcompressor_N_chan(strength,thresh,att,rel,knee,prePost,link,meter,N) =
(si.bus(N) <:
(compression_gain_N_chan(strength,thresh,att,rel,knee,prePost,link,N),si.bus(N))
)
:(ro.interleave(N,2):par(i,N,meter*_));
// feed back compressor
FBcompressor_N_chan(strength,thresh,att,rel,knee,prePost,link,meter,N) =
(
(compression_gain_N_chan(strength,thresh,att,rel,knee,prePost,link,N),si.bus(N))
:(ro.interleave(N,2):par(i,N,meter*_))
)~si.bus(N);
// feed back and/or forward compressor
// the feedback part has a much higher strength, so they end up sounding similar
FBFFcompressor_N_chan(strength,thresh,att,rel,knee,prePost,link,FBFF,meter,N) =
si.bus(N) <: si.bus(N*2):
(
((
(par(i, 2, compression_gain_N_chan(strength*(1+((i==0)*2)),thresh,att,rel,knee,prePost,link,N)):ro.interleave(N,2):par(i, N, crossfade(FBFF)))
,si.bus(N))
:(ro.interleave(N,2):par(i,N,meter*_))
)~si.bus(N)
);
// RMS feed back and/or forward compressor
// to save CPU we cheat a bit, in a similar way as in the original libs:
// instead of crosfading between two sets of gain calculators as above,
// we take the abs of the audio from both the FF and FB, and crossfade between those,
// and feed that into one set of gain calculators
// again the strength is much higher when in FB mode, but implemented differently
RMS_FBFFcompressor_N_chan(strength,thresh,att,rel,knee,prePost,link,FBFF,meter,N) =
si.bus(N) <: si.bus(N*2):
(
(
(
(ro.interleave(N,2):par(i, N*2, abs) :par(i, N, crossfade(FBFF)) : RMS_compression_gain_N_chan(strength*(1+(((FBFF*-1)+1)*1)),thresh,att,rel,knee,prePost,link,N))
,si.bus(N)
)
:(ro.interleave(N,2):par(i,N,meter*_))
)~si.bus(N)
);
// RMS feed back compressor into peak limiter feeding back into the FB comp.
// By combining them this way, they complement each other optimally:
// The RMS compressor doesn't have to deal with the peaks,
// and the peak limiter get's spared from the steady state signal.
RMS_FBcompressor_peak_limiter_N_chan(strength,thresh,threshLim,att,rel,knee,link,meter,N) =
(
(
(
(RMS_compression_gain_N_chan(strength,thresh,att,rel,knee,0,link,N))
,si.bus(N)
):(ro.interleave(N,2):par(i,N,meter*_))
):FFcompressor_N_chan(1,threshLim,0,att:min(rel),knee*0.5,0,link,meter,N)
)~si.bus(N);
crossfade(x,a,b) = a*(1-x),b*x : +;
// bypass switch for any number of channels
// bp -> the switch
// e -> the expression you want to bypass
// NOTE: bypass only makes sense when inputs(e) equals outputs(e)
bypass(bp,e) = si.bus(N) <: ((inswitch:e),si.bus(N)) : outswitch with {
N = inputs(e);
inswitch =par(i, N, select2(bp,_,0));
outswitch = ro.interleave(N,2) : par(i, N, select2(bp) );
};
// here bp can be a float between 0 and 1
crossfade_bypass(bp,e) = si.bus(N) <: ((inswitch:e),si.bus(N)) : outswitch with {
N = inputs(e);
inswitch = par(i, N, crossfade(bp,_,0));
outswitch = ro.interleave(N,2) : par(i, N, crossfade(bp) );
};
// get the minimum of N inputs:
minimum(1) = _;
minimum(2) = min;
minimum(N) = (minimum(N-1),_):min;
compressor_N_chan_demo(N) =
bypass(cbp,FFcompressor_N_chan(strength,threshold,attack,release,knee,prePost,link,meter,N):par(i, N, *(makeupgain)));
comp_group(x) = vgroup("COMPRESSOR [tooltip: Reference: http://en.wikipedia.org/wiki/Dynamic_range_compression]", x);
meter_group(x) = comp_group(vgroup("[0]", x));
knob_group(x) = comp_group(hgroup("[1]", x));
checkbox_group(x) = meter_group(hgroup("[0]", x));
cbp = checkbox_group(checkbox("[0] Bypass [tooltip: When this is checked, the compressor has no effect]"));
maxGR = -100;
meter = _<:(_, (ba.linear2db:max(maxGR):meter_group((hbargraph("[1][unit:dB][tooltip: gain reduction in dB]", maxGR, 0))))):attach;
ctl_group(x) = knob_group(hgroup("[3] Compression Control", x));
strength = ctl_group(hslider("[0] Strength [style:knob]
[tooltip: A compression Strength of 0 means no gain reduction and 1 means full gain reduction]",
1, 0, 8, 0.01));
duck_strength =
ctl_group(hslider("[-1] Duck Strength [style:knob]
[tooltip: A compression Strength of 0 means no gain reduction and 1 means full gain reduction]",
1, 0, 8, 0.01));
expand_strength =
ctl_group(hslider("[0] Expand Strength [style:knob]
[tooltip: A compression Strength of 0 means no gain reduction and 1 means full gain reduction]",
1, 0, 8, 0.01));
threshold = ctl_group(hslider("[1] Threshold [unit:dB] [style:knob]
[tooltip: When the signal level exceeds the Threshold (in dB), its level is compressed according to the Strength]",
0, maxGR, 10, 0.1));
knee = ctl_group(hslider("[2] Knee [unit:dB] [style:knob]
[tooltip: soft knee amount in dB]",
6, 0, 30, 0.1));
HPfreq =
ctl_group(hslider("[3] HP freq [scale:log] [style:knob]
[tooltip: cutoff frequency of the sidechain fi.highpass filter]",
20, 20, 10000, 1));
LPfreq =
ctl_group(hslider("[4] LP freq [scale:log] [style:knob]
[tooltip: cutoff frequency of the sidechain fi.lowpass filter]",
10000, 20, 10000, 1));
SClisten = knob_group(checkbox("SC"));
env_group(x) = knob_group(hgroup("[4] Compression Response", x));
attack = env_group(hslider("[1] Attack [unit:ms] [style:knob] [scale:log]
[tooltip: Time constant in ms (1/e smoothing time) for the compression gain to approach (exponentially) a new lower target level (the compression `kicking in')]",
0.1, 0.1, 1000, 0.01)-0.1) : *(0.001) ;
// The actual attack value is 0.1 smaller than the one displayed.
// This is done for hard limiting:
// You need 0 attack for that, but a log scale starting at 0 is useless
release = env_group(hslider("[2] Release [unit:ms] [style: knob] [scale:log]
[tooltip: Time constant in ms (1/e smoothing time) for the compression gain to approach (exponentially) a new higher target level (the compression 'releasing')]",
100, 1, maxRelTime*1000, 0.1)) : *(0.001) : max(1/ma.SR);
prePost = env_group(checkbox("[3] slow/fast [tooltip: Unchecked: log domain return-to-threshold detector
Checked: linear return-to-fi.zero detector]")*-1)+1;
link = env_group(hslider("[4] link [style:knob]
[tooltip: 0 means all channels get individual gain reduction, 1 means they all get the same gain reduction]",
1, 0, 1, 0.01));
FBFF = env_group(hslider("[5] feed-back/forward [style:knob]
[tooltip: fade between a feedback and a feed forward compressor design]",
1, 0, 1, 0.01));
lim_group(x) = knob_group(hgroup("[5] Limiter [tooltip: It's release time is the minimum of the attack and release of the compressor,
and it's knee is half that of the compressor]", x));
thresholdLim = lim_group(hslider("[9] Threshold [unit:dB] [style:knob]
[tooltip: The signal level never exceeds this threshold]",
0, -30, 10, 0.1));
makeupgain = comp_group(hslider("[6] Makeup Gain [unit:dB]
[tooltip: The compressed-signal input level is increased by this amount (in dB) to make up for the level lost due to compression]",
0, 0, maxGR*-1, 0.1)) : ba.db2linear;
// };
// ******************************************************************************************
// expander:
// ******************************************************************************************
expander_gain_computer(strength,thresh,knee,level) =
select3((level<(thresh-(knee/2)))+(level<(thresh+(knee/2))),
0,
// 0,
((level-thresh-(knee/2))*-1:pow(2)/(-2*knee)) ,
// 0,
// -24,
(level-thresh)
) :min(0)*strength;
expander(strength,thresh,knee,x) = (expander_gain_computer(strength,thresh,knee,level(att,rel,x)):ba.db2linear)*x;
SCexpander(strength,thresh,knee,att,rel,SC,x) = (expander_gain_computer(strength,thresh,knee,level(att,rel,SC)):ba.db2linear):meter*x;
level(att,rel,x) = x:abs:si.lag_ud(att,rel):ba.linear2db;
// ******************************************************************************************
// drumDuxpander:
// ******************************************************************************************
nrChan = 6;
N = 8;
drumDuxpander = si.bus(nrChan)<: (drumDuck, si.bus(nrChan)):drumExpand;
drumDuck = SCfilters <: ( si.bus(nrChan), ( duckGainReductions <: par(i, nrChan, meDuckGR(i))) : ro.interleave(nrChan,2)): par(i, nrChan, *);
duckGainReductions = par(i, nrChan, duckGainReduction(i));
duckGainReduction(i) = chanGroup(i+1, peak_compression_gain_mono(duck_strength,threshold,attack,release,knee,prePost));
meDuckGR(i) = chanGroup(i+1, par(j, nrChan, (ba.linear2db*(i!=j))):>ba.db2linear:meter);
drumExpand = ro.interleave(nrChan,2) : par(i, nrChan, chanGroup(i+1, SCexpander(expand_strength,threshold,knee,attack,release)));
SCfilters = par(i, nrChan, chanGroup(i+1, (fi.highpass(N,HPfreq): fi.lowpass(N,LPfreq))));
chanGroup(i,x) = vgroup("channel %i", x);