2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #include "DynamicsCompressorKernel.h"
35 #include "AudioUtilities.h"
36 #include "DenormalDisabler.h"
38 #include <wtf/MathExtras.h>
44 using namespace AudioUtilities;
46 // Metering hits peaks instantly, but releases this fast (in seconds).
47 const float meteringReleaseTimeConstant = 0.325f;
49 // Exponential saturation curve.
50 static float saturate(float x, float k)
52 return 1 - exp(-k * x);
55 DynamicsCompressorKernel::DynamicsCompressorKernel(float sampleRate)
56 : m_sampleRate(sampleRate)
57 , m_lastPreDelayFrames(DefaultPreDelayFrames)
58 , m_preDelayBufferL(MaxPreDelayFrames)
59 , m_preDelayBufferR(MaxPreDelayFrames)
60 , m_preDelayReadIndex(0)
61 , m_preDelayWriteIndex(DefaultPreDelayFrames)
63 // Initializes most member variables
66 m_meteringReleaseK = discreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate);
69 void DynamicsCompressorKernel::setPreDelayTime(float preDelayTime)
71 // Re-configure look-ahead section pre-delay if delay time has changed.
72 unsigned preDelayFrames = preDelayTime / sampleRate();
73 if (preDelayFrames > MaxPreDelayFrames - 1)
74 preDelayFrames = MaxPreDelayFrames - 1;
76 if (m_lastPreDelayFrames != preDelayFrames) {
77 m_lastPreDelayFrames = preDelayFrames;
78 m_preDelayBufferL.zero();
79 m_preDelayBufferR.zero();
80 m_preDelayReadIndex = 0;
81 m_preDelayWriteIndex = preDelayFrames;
85 void DynamicsCompressorKernel::process(float* sourceL,
87 float* sourceR, /* stereo-linked */
89 unsigned framesToProcess,
97 float effectBlend, /* equal power crossfade */
105 bool isStereo = destinationR;
106 float sampleRate = this->sampleRate();
108 float dryMix = 1 - effectBlend;
109 float wetMix = effectBlend;
111 // Threshold and headroom.
112 float linearThreshold = decibelsToLinear(dbThreshold);
113 float linearHeadroom = decibelsToLinear(dbHeadroom);
116 float maximum = 1.05f * linearHeadroom * linearThreshold;
117 float kk = (maximum - linearThreshold);
118 float inverseKK = 1 / kk;
120 float fullRangeGain = (linearThreshold + kk * saturate(1 - linearThreshold, 1));
121 float fullRangeMakeupGain = 1 / fullRangeGain;
122 // Empirical/perceptual tuning.
123 fullRangeMakeupGain = powf(fullRangeMakeupGain, 0.6f);
125 float masterLinearGain = decibelsToLinear(dbPostGain) * fullRangeMakeupGain;
127 // Attack parameters.
128 attackTime = max(0.001f, attackTime);
129 float attackFrames = attackTime * sampleRate;
131 // Release parameters.
132 float releaseFrames = sampleRate * releaseTime;
134 // Detector release time.
135 float satReleaseTime = 0.0025f;
136 float satReleaseFrames = satReleaseTime * sampleRate;
138 // Create a smooth function which passes through four points.
140 // Polynomial of the form
141 // y = a + b*x + c*x^2 + d*x^3 + e*x^4;
143 float y1 = releaseFrames * releaseZone1;
144 float y2 = releaseFrames * releaseZone2;
145 float y3 = releaseFrames * releaseZone3;
146 float y4 = releaseFrames * releaseZone4;
148 // All of these coefficients were derived for 4th order polynomial curve fitting where the y values
149 // match the evenly spaced x values as follows: (y1 : x == 0, y2 : x == 1, y3 : x == 2, y4 : x == 3)
150 float kA = 0.9999999999999998f*y1 + 1.8432219684323923e-16f*y2 - 1.9373394351676423e-16f*y3 + 8.824516011816245e-18f*y4;
151 float kB = -1.5788320352845888f*y1 + 2.3305837032074286f*y2 - 0.9141194204840429f*y3 + 0.1623677525612032f*y4;
152 float kC = 0.5334142869106424f*y1 - 1.272736789213631f*y2 + 0.9258856042207512f*y3 - 0.18656310191776226f*y4;
153 float kD = 0.08783463138207234f*y1 - 0.1694162967925622f*y2 + 0.08588057951595272f*y3 - 0.00429891410546283f*y4;
154 float kE = -0.042416883008123074f*y1 + 0.1115693827987602f*y2 - 0.09764676325265872f*y3 + 0.028494263462021576f*y4;
156 // x ranges from 0 -> 3 0 1 2 3
159 // y calculates adaptive release frames depending on the amount of compression.
161 setPreDelayTime(preDelayTime);
163 const int nDivisionFrames = 32;
165 const int nDivisions = framesToProcess / nDivisionFrames;
167 for (int i = 0; i < nDivisions; ++i) {
168 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
169 // Calculate desired gain
170 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
173 if (isnan(m_detectorAverage))
174 m_detectorAverage = 1;
175 if (isinf(m_detectorAverage))
176 m_detectorAverage = 1;
178 float desiredGain = m_detectorAverage;
180 // Pre-warp so we get desiredGain after sin() warp below.
181 float scaledDesiredGain = asinf(desiredGain) / (0.5f * piFloat);
183 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
184 // Deal with envelopes
185 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
187 // envelopeRate is the rate we slew from current compressor level to the desired level.
188 // The exact rate depends on if we're attacking or releasing and by how much.
191 bool isReleasing = scaledDesiredGain > m_compressorGain;
193 // compressionDiffDb is the difference between current compression level and the desired level.
194 float compressionDiffDb = linearToDecibels(m_compressorGain / scaledDesiredGain);
197 // Release mode - compressionDiffDb should be negative dB
198 m_maxAttackCompressionDiffDb = -1;
201 if (isnan(compressionDiffDb))
202 compressionDiffDb = -1;
203 if (isinf(compressionDiffDb))
204 compressionDiffDb = -1;
206 // Adaptive release - higher compression (lower compressionDiffDb) releases faster.
208 // Contain within range: -12 -> 0 then scale to go from 0 -> 3
209 float x = compressionDiffDb;
212 x = 0.25f * (x + 12);
214 // Compute adaptive release curve using 4th order polynomial.
215 // Normal values for the polynomial coefficients would create a monotonically increasing function.
219 float releaseFrames = kA + kB * x + kC * x2 + kD * x3 + kE * x4;
222 float dbPerFrame = kSpacingDb / releaseFrames;
224 envelopeRate = decibelsToLinear(dbPerFrame);
226 // Attack mode - compressionDiffDb should be positive dB
229 if (isnan(compressionDiffDb))
230 compressionDiffDb = 1;
231 if (isinf(compressionDiffDb))
232 compressionDiffDb = 1;
234 // As long as we're still in attack mode, use a rate based off
235 // the largest compressionDiffDb we've encountered so far.
236 if (m_maxAttackCompressionDiffDb == -1 || m_maxAttackCompressionDiffDb < compressionDiffDb)
237 m_maxAttackCompressionDiffDb = compressionDiffDb;
239 float effAttenDiffDb = max(0.5f, m_maxAttackCompressionDiffDb);
241 float x = 0.25f / effAttenDiffDb;
242 envelopeRate = 1 - powf(x, 1 / attackFrames);
245 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
246 // Inner loop - calculate shaped power average - apply compression.
247 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
250 float* delayBufferL = m_preDelayBufferL.data();
251 float* delayBufferR = m_preDelayBufferR.data();
252 int preDelayReadIndex = m_preDelayReadIndex;
253 int preDelayWriteIndex = m_preDelayWriteIndex;
254 float detectorAverage = m_detectorAverage;
255 float compressorGain = m_compressorGain;
257 int loopFrames = nDivisionFrames;
258 while (loopFrames--) {
259 float compressorInput;
263 // Predelay signal, computing compression amount from un-delayed version.
265 float undelayedL = *sourceL++;
266 float undelayedR = *sourceR++;
268 compressorInput = 0.5f * (undelayedL + undelayedR);
270 inputL = delayBufferL[preDelayReadIndex];
271 inputR = delayBufferR[preDelayReadIndex];
273 delayBufferL[preDelayWriteIndex] = undelayedL;
274 delayBufferR[preDelayWriteIndex] = undelayedR;
276 compressorInput = *sourceL++;
278 inputL = delayBufferL[preDelayReadIndex];
279 delayBufferL[preDelayWriteIndex] = compressorInput;
282 preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
283 preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
285 // Calculate shaped power on undelayed input.
287 float scaledInput = compressorInput;
288 float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
290 // Put through shaping curve.
291 // This is linear up to the threshold, then exponentially approaches the maximum (headroom amount above threshold).
292 // The transition from the threshold to the exponential portion is smooth (1st derivative matched).
293 float shapedInput = absInput < linearThreshold ? absInput : linearThreshold + kk * saturate(absInput - linearThreshold, inverseKK);
295 float attenuation = absInput <= 0.0001f ? 1 : shapedInput / absInput;
297 float attenuationDb = -linearToDecibels(attenuation);
298 attenuationDb = max(2.0f, attenuationDb);
300 float dbPerFrame = attenuationDb / satReleaseFrames;
302 float satReleaseRate = decibelsToLinear(dbPerFrame) - 1;
304 bool isRelease = (attenuation > detectorAverage);
305 float rate = isRelease ? satReleaseRate : 1;
307 detectorAverage += (attenuation - detectorAverage) * rate;
308 detectorAverage = min(1.0f, detectorAverage);
311 if (isnan(detectorAverage))
313 if (isinf(detectorAverage))
316 // Exponential approach to desired gain.
317 if (envelopeRate < 1) {
318 // Attack - reduce gain to desired.
319 compressorGain += (scaledDesiredGain - compressorGain) * envelopeRate;
321 // Release - exponentially increase gain to 1.0
322 compressorGain *= envelopeRate;
323 compressorGain = min(1.0f, compressorGain);
326 // Warp pre-compression gain to smooth out sharp exponential transition points.
327 float postWarpCompressorGain = sinf(0.5f * piFloat * compressorGain);
329 // Calculate total gain using master gain and effect blend.
330 float totalGain = dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
332 // Calculate metering.
333 float dbRealGain = 20 * log10(postWarpCompressorGain);
334 if (dbRealGain < m_meteringGain)
335 m_meteringGain = dbRealGain;
337 m_meteringGain += (dbRealGain - m_meteringGain) * m_meteringReleaseK;
341 float outputL = inputL;
342 float outputR = inputR;
344 outputL *= totalGain;
345 outputR *= totalGain;
347 *destinationL++ = outputL;
348 *destinationR++ = outputR;
350 *destinationL++ = inputL * totalGain;
353 // Locals back to member variables.
354 m_preDelayReadIndex = preDelayReadIndex;
355 m_preDelayWriteIndex = preDelayWriteIndex;
356 m_detectorAverage = DenormalDisabler::flushDenormalFloatToZero(detectorAverage);
357 m_compressorGain = DenormalDisabler::flushDenormalFloatToZero(compressorGain);
362 void DynamicsCompressorKernel::reset()
364 m_detectorAverage = 0;
365 m_compressorGain = 1;
369 m_preDelayBufferL.zero();
370 m_preDelayBufferR.zero();
371 m_preDelayReadIndex = 0;
372 m_preDelayWriteIndex = DefaultPreDelayFrames;
374 m_maxAttackCompressionDiffDb = -1; // uninitialized state
377 } // namespace WebCore
379 #endif // ENABLE(WEB_AUDIO)