8 #include "static_assert.h"
11 /* These structures assume BUFFERSIZE is a power of 2. */
12 static_assert((BUFFERSIZE
& (BUFFERSIZE
-1)) == 0, "BUFFERSIZE is not a power of 2");
14 typedef struct SlidingHold
{
15 ALfloat Values
[BUFFERSIZE
];
16 ALsizei Expiries
[BUFFERSIZE
];
22 /* General topology and basic automation was based on the following paper:
24 * D. Giannoulis, M. Massberg and J. D. Reiss,
25 * "Parameter Automation in a Dynamic Range Compressor,"
26 * Journal of the Audio Engineering Society, v61 (10), Oct. 2013
28 * Available (along with supplemental reading) at:
30 * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/
32 typedef struct Compressor
{
56 alignas(16) ALfloat SideChain
[2*BUFFERSIZE
];
57 alignas(16) ALfloat CrestFactor
[BUFFERSIZE
];
60 ALfloat (*Delay
)[BUFFERSIZE
];
75 /* This sliding hold follows the input level with an instant attack and a
76 * fixed duration hold before an instant release to the next highest level.
77 * It is a sliding window maximum (descending maxima) implementation based on
78 * Richard Harter's ascending minima algorithm available at:
80 * http://www.richardhartersworld.com/cri/2001/slidingmin.html
82 ALfloat
UpdateSlidingHold(SlidingHold
*Hold
, const ALsizei i
, const ALfloat in
)
84 const ALsizei mask
= BUFFERSIZE
- 1;
85 const ALsizei length
= Hold
->Length
;
86 ALfloat
*restrict values
= Hold
->Values
;
87 ALsizei
*restrict expiries
= Hold
->Expiries
;
88 ALsizei lowerIndex
= Hold
->LowerIndex
;
89 ALsizei upperIndex
= Hold
->UpperIndex
;
91 if(i
>= expiries
[upperIndex
])
92 upperIndex
= (upperIndex
+ 1) & mask
;
94 if(in
>= values
[upperIndex
])
96 values
[upperIndex
] = in
;
97 expiries
[upperIndex
] = i
+ length
;
98 lowerIndex
= upperIndex
;
104 if(!(in
>= values
[lowerIndex
]))
106 } while(lowerIndex
--);
111 lowerIndex
= (lowerIndex
+ 1) & mask
;
112 values
[lowerIndex
] = in
;
113 expiries
[lowerIndex
] = i
+ length
;
116 Hold
->LowerIndex
= lowerIndex
;
117 Hold
->UpperIndex
= upperIndex
;
119 return values
[upperIndex
];
122 static void ShiftSlidingHold(SlidingHold
*Hold
, const ALsizei n
)
124 const ALsizei lowerIndex
= Hold
->LowerIndex
;
125 ALsizei
*restrict expiries
= Hold
->Expiries
;
126 ALsizei i
= Hold
->UpperIndex
;
130 for(;i
< BUFFERSIZE
;i
++)
134 for(;i
< lowerIndex
;i
++)
140 /* Multichannel compression is linked via the absolute maximum of all
143 static void LinkChannels(Compressor
*Comp
, const ALsizei SamplesToDo
, ALfloat (*restrict OutBuffer
)[BUFFERSIZE
])
145 const ALsizei index
= Comp
->LookAhead
;
146 const ALsizei numChans
= Comp
->NumChans
;
147 ALfloat
*restrict sideChain
= Comp
->SideChain
;
150 ASSUME(SamplesToDo
> 0);
151 ASSUME(numChans
> 0);
153 for(i
= 0;i
< SamplesToDo
;i
++)
154 sideChain
[index
+ i
] = 0.0f
;
156 for(c
= 0;c
< numChans
;c
++)
158 ALsizei offset
= index
;
159 for(i
= 0;i
< SamplesToDo
;i
++)
161 sideChain
[offset
] = maxf(sideChain
[offset
], fabsf(OutBuffer
[c
][i
]));
167 /* This calculates the squared crest factor of the control signal for the
168 * basic automation of the attack/release times. As suggested by the paper,
169 * it uses an instantaneous squared peak detector and a squared RMS detector
170 * both with 200ms release times.
172 static void CrestDetector(Compressor
*Comp
, const ALsizei SamplesToDo
)
174 const ALfloat a_crest
= Comp
->CrestCoeff
;
175 const ALsizei index
= Comp
->LookAhead
;
176 const ALfloat
*restrict sideChain
= Comp
->SideChain
;
177 ALfloat
*restrict crestFactor
= Comp
->CrestFactor
;
178 ALfloat y2_peak
= Comp
->LastPeakSq
;
179 ALfloat y2_rms
= Comp
->LastRmsSq
;
182 ASSUME(SamplesToDo
> 0);
184 for(i
= 0;i
< SamplesToDo
;i
++)
186 ALfloat x_abs
= sideChain
[index
+ i
];
187 ALfloat x2
= maxf(0.000001f
, x_abs
* x_abs
);
189 y2_peak
= maxf(x2
, lerp(x2
, y2_peak
, a_crest
));
190 y2_rms
= lerp(x2
, y2_rms
, a_crest
);
191 crestFactor
[i
] = y2_peak
/ y2_rms
;
194 Comp
->LastPeakSq
= y2_peak
;
195 Comp
->LastRmsSq
= y2_rms
;
198 /* The side-chain starts with a simple peak detector (based on the absolute
199 * value of the incoming signal) and performs most of its operations in the
202 static void PeakDetector(Compressor
*Comp
, const ALsizei SamplesToDo
)
204 const ALsizei index
= Comp
->LookAhead
;
205 ALfloat
*restrict sideChain
= Comp
->SideChain
;
208 ASSUME(SamplesToDo
> 0);
210 for(i
= 0;i
< SamplesToDo
;i
++)
212 const ALuint offset
= index
+ i
;
213 const ALfloat x_abs
= sideChain
[offset
];
215 sideChain
[offset
] = logf(maxf(0.000001f
, x_abs
));
219 /* An optional hold can be used to extend the peak detector so it can more
220 * solidly detect fast transients. This is best used when operating as a
223 static void PeakHoldDetector(Compressor
*Comp
, const ALsizei SamplesToDo
)
225 const ALsizei index
= Comp
->LookAhead
;
226 ALfloat
*restrict sideChain
= Comp
->SideChain
;
227 SlidingHold
*hold
= Comp
->Hold
;
230 ASSUME(SamplesToDo
> 0);
232 for(i
= 0;i
< SamplesToDo
;i
++)
234 const ALsizei offset
= index
+ i
;
235 const ALfloat x_abs
= sideChain
[offset
];
236 const ALfloat x_G
= logf(maxf(0.000001f
, x_abs
));
238 sideChain
[offset
] = UpdateSlidingHold(hold
, i
, x_G
);
241 ShiftSlidingHold(hold
, SamplesToDo
);
244 /* This is the heart of the feed-forward compressor. It operates in the log
245 * domain (to better match human hearing) and can apply some basic automation
246 * to knee width, attack/release times, make-up/post gain, and clipping
249 static void GainCompressor(Compressor
*Comp
, const ALsizei SamplesToDo
)
251 const bool autoKnee
= Comp
->Auto
.Knee
;
252 const bool autoAttack
= Comp
->Auto
.Attack
;
253 const bool autoRelease
= Comp
->Auto
.Release
;
254 const bool autoPostGain
= Comp
->Auto
.PostGain
;
255 const bool autoDeclip
= Comp
->Auto
.Declip
;
256 const ALsizei lookAhead
= Comp
->LookAhead
;
257 const ALfloat threshold
= Comp
->Threshold
;
258 const ALfloat slope
= Comp
->Slope
;
259 const ALfloat attack
= Comp
->Attack
;
260 const ALfloat release
= Comp
->Release
;
261 const ALfloat c_est
= Comp
->GainEstimate
;
262 const ALfloat a_adp
= Comp
->AdaptCoeff
;
263 const ALfloat
*restrict crestFactor
= Comp
->CrestFactor
;
264 ALfloat
*restrict sideChain
= Comp
->SideChain
;
265 ALfloat postGain
= Comp
->PostGain
;
266 ALfloat knee
= Comp
->Knee
;
267 ALfloat t_att
= attack
;
268 ALfloat t_rel
= release
- attack
;
269 ALfloat a_att
= expf(-1.0f
/ t_att
);
270 ALfloat a_rel
= expf(-1.0f
/ t_rel
);
271 ALfloat y_1
= Comp
->LastRelease
;
272 ALfloat y_L
= Comp
->LastAttack
;
273 ALfloat c_dev
= Comp
->LastGainDev
;
276 ASSUME(SamplesToDo
> 0);
278 for(i
= 0;i
< SamplesToDo
;i
++)
280 const ALfloat y2_crest
= crestFactor
[i
];
281 const ALfloat x_G
= sideChain
[lookAhead
+ i
];
282 const ALfloat x_over
= x_G
- threshold
;
288 knee
= maxf(0.0f
, 2.5f
* (c_dev
+ c_est
));
289 knee_h
= 0.5f
* knee
;
291 /* This is the gain computer. It applies a static compression curve
292 * to the control signal.
294 if(x_over
<= -knee_h
)
296 else if(fabsf(x_over
) < knee_h
)
297 y_G
= (x_over
+ knee_h
) * (x_over
+ knee_h
) / (2.0f
* knee
);
305 t_att
= 2.0f
* attack
/ y2_crest
;
306 a_att
= expf(-1.0f
/ t_att
);
311 t_rel
= 2.0f
* release
/ y2_crest
- t_att
;
312 a_rel
= expf(-1.0f
/ t_rel
);
315 /* Gain smoothing (ballistics) is done via a smooth decoupled peak
316 * detector. The attack time is subtracted from the release time
317 * above to compensate for the chained operating mode.
319 y_1
= maxf(x_L
, lerp(x_L
, y_1
, a_rel
));
320 y_L
= lerp(y_1
, y_L
, a_att
);
322 /* Knee width and make-up gain automation make use of a smoothed
323 * measurement of deviation between the control signal and estimate.
324 * The estimate is also used to bias the measurement to hot-start its
327 c_dev
= lerp(-y_L
- c_est
, c_dev
, a_adp
);
331 /* Clipping reduction is only viable when make-up gain is being
332 * automated. It modifies the deviation to further attenuate the
333 * control signal when clipping is detected. The adaptation
334 * time is sufficiently long enough to suppress further clipping
335 * at the same output level.
338 c_dev
= maxf(c_dev
, sideChain
[i
] - y_L
- threshold
- c_est
);
340 postGain
= -(c_dev
+ c_est
);
343 sideChain
[i
] = expf(postGain
- y_L
);
346 Comp
->LastRelease
= y_1
;
347 Comp
->LastAttack
= y_L
;
348 Comp
->LastGainDev
= c_dev
;
351 /* Combined with the hold time, a look-ahead delay can improve handling of
352 * fast transients by allowing the envelope time to converge prior to
353 * reaching the offending impulse. This is best used when operating as a
356 static void SignalDelay(Compressor
*Comp
, const ALsizei SamplesToDo
, ALfloat (*restrict OutBuffer
)[BUFFERSIZE
])
358 const ALsizei mask
= BUFFERSIZE
- 1;
359 const ALsizei numChans
= Comp
->NumChans
;
360 const ALsizei indexIn
= Comp
->DelayIndex
;
361 const ALsizei indexOut
= Comp
->DelayIndex
- Comp
->LookAhead
;
362 ALfloat (*restrict delay
)[BUFFERSIZE
] = Comp
->Delay
;
365 ASSUME(SamplesToDo
> 0);
366 ASSUME(numChans
> 0);
368 for(c
= 0;c
< numChans
;c
++)
370 for(i
= 0;i
< SamplesToDo
;i
++)
372 ALfloat sig
= OutBuffer
[c
][i
];
374 OutBuffer
[c
][i
] = delay
[c
][(indexOut
+ i
) & mask
];
375 delay
[c
][(indexIn
+ i
) & mask
] = sig
;
379 Comp
->DelayIndex
= (indexIn
+ SamplesToDo
) & mask
;
382 /* The compressor is initialized with the following settings:
384 * NumChans - Number of channels to process.
385 * SampleRate - Sample rate to process.
386 * AutoKnee - Whether to automate the knee width parameter.
387 * AutoAttack - Whether to automate the attack time parameter.
388 * AutoRelease - Whether to automate the release time parameter.
389 * AutoPostGain - Whether to automate the make-up (post) gain parameter.
390 * AutoDeclip - Whether to automate clipping reduction. Ignored when
391 * not automating make-up gain.
392 * LookAheadTime - Look-ahead time (in seconds).
393 * HoldTime - Peak hold-time (in seconds).
394 * PreGainDb - Gain applied before detection (in dB).
395 * PostGainDb - Make-up gain applied after compression (in dB).
396 * ThresholdDb - Triggering threshold (in dB).
397 * Ratio - Compression ratio (x:1). Set to INFINITY for true
398 * limiting. Ignored when automating knee width.
399 * KneeDb - Knee width (in dB). Ignored when automating knee
401 * AttackTimeMin - Attack time (in seconds). Acts as a maximum when
402 * automating attack time.
403 * ReleaseTimeMin - Release time (in seconds). Acts as a maximum when
404 * automating release time.
406 Compressor
* CompressorInit(const ALsizei NumChans
, const ALuint SampleRate
,
407 const ALboolean AutoKnee
, const ALboolean AutoAttack
,
408 const ALboolean AutoRelease
, const ALboolean AutoPostGain
,
409 const ALboolean AutoDeclip
, const ALfloat LookAheadTime
,
410 const ALfloat HoldTime
, const ALfloat PreGainDb
,
411 const ALfloat PostGainDb
, const ALfloat ThresholdDb
,
412 const ALfloat Ratio
, const ALfloat KneeDb
,
413 const ALfloat AttackTime
, const ALfloat ReleaseTime
)
420 lookAhead
= (ALsizei
)clampf(roundf(LookAheadTime
*SampleRate
), 0.0f
, BUFFERSIZE
-1);
421 hold
= (ALsizei
)clampf(roundf(HoldTime
*SampleRate
), 0.0f
, BUFFERSIZE
-1);
422 /* The sliding hold implementation doesn't handle a length of 1. A 1-sample
423 * hold is useless anyway, it would only ever give back what was just given
429 size
= sizeof(*Comp
);
432 size
+= sizeof(*Comp
->Delay
) * NumChans
;
434 size
+= sizeof(*Comp
->Hold
);
437 Comp
= al_calloc(16, size
);
438 Comp
->NumChans
= NumChans
;
439 Comp
->SampleRate
= SampleRate
;
440 Comp
->Auto
.Knee
= AutoKnee
;
441 Comp
->Auto
.Attack
= AutoAttack
;
442 Comp
->Auto
.Release
= AutoRelease
;
443 Comp
->Auto
.PostGain
= AutoPostGain
;
444 Comp
->Auto
.Declip
= AutoPostGain
&& AutoDeclip
;
445 Comp
->LookAhead
= lookAhead
;
446 Comp
->PreGain
= powf(10.0f
, PreGainDb
/ 20.0f
);
447 Comp
->PostGain
= PostGainDb
* logf(10.0f
) / 20.0f
;
448 Comp
->Threshold
= ThresholdDb
* logf(10.0f
) / 20.0f
;
449 Comp
->Slope
= 1.0f
/ maxf(1.0f
, Ratio
) - 1.0f
;
450 Comp
->Knee
= maxf(0.0f
, KneeDb
* logf(10.0f
) / 20.0f
);
451 Comp
->Attack
= maxf(1.0f
, AttackTime
* SampleRate
);
452 Comp
->Release
= maxf(1.0f
, ReleaseTime
* SampleRate
);
454 /* Knee width automation actually treats the compressor as a limiter. By
455 * varying the knee width, it can effectively be seen as applying
456 * compression over a wide range of ratios.
465 Comp
->Hold
= (SlidingHold
*)(Comp
+ 1);
466 Comp
->Hold
->Values
[0] = -INFINITY
;
467 Comp
->Hold
->Expiries
[0] = hold
;
468 Comp
->Hold
->Length
= hold
;
469 Comp
->Delay
= (ALfloat(*)[])(Comp
->Hold
+ 1);
473 Comp
->Delay
= (ALfloat(*)[])(Comp
+ 1);
477 Comp
->CrestCoeff
= expf(-1.0f
/ (0.200f
* SampleRate
)); // 200ms
478 Comp
->GainEstimate
= Comp
->Threshold
* -0.5f
* Comp
->Slope
;
479 Comp
->AdaptCoeff
= expf(-1.0f
/ (2.0f
* SampleRate
)); // 2s
484 void ApplyCompression(Compressor
*Comp
, const ALsizei SamplesToDo
, ALfloat (*restrict OutBuffer
)[BUFFERSIZE
])
486 const ALsizei numChans
= Comp
->NumChans
;
487 const ALfloat preGain
= Comp
->PreGain
;
488 ALfloat
*restrict sideChain
;
491 ASSUME(SamplesToDo
> 0);
492 ASSUME(numChans
> 0);
496 for(c
= 0;c
< numChans
;c
++)
498 for(i
= 0;i
< SamplesToDo
;i
++)
499 OutBuffer
[c
][i
] *= preGain
;
503 LinkChannels(Comp
, SamplesToDo
, OutBuffer
);
505 if(Comp
->Auto
.Attack
|| Comp
->Auto
.Release
)
506 CrestDetector(Comp
, SamplesToDo
);
509 PeakHoldDetector(Comp
, SamplesToDo
);
511 PeakDetector(Comp
, SamplesToDo
);
513 GainCompressor(Comp
, SamplesToDo
);
516 SignalDelay(Comp
, SamplesToDo
, OutBuffer
);
518 sideChain
= Comp
->SideChain
;
519 for(c
= 0;c
< numChans
;c
++)
521 for(i
= 0;i
< SamplesToDo
;i
++)
522 OutBuffer
[c
][i
] *= sideChain
[i
];
525 memmove(sideChain
, sideChain
+SamplesToDo
, Comp
->LookAhead
*sizeof(ALfloat
));
529 ALsizei
GetCompressorLookAhead(const Compressor
*Comp
)
530 { return Comp
->LookAhead
; }