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, unsigned numberOfChannels)
56 : m_sampleRate(sampleRate)
57 , m_lastPreDelayFrames(DefaultPreDelayFrames)
58 , m_preDelayReadIndex(0)
59 , m_preDelayWriteIndex(DefaultPreDelayFrames)
61 setNumberOfChannels(numberOfChannels);
63 // Initializes most member variables
66 m_meteringReleaseK = discreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate);
69 void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels)
71 if (m_preDelayBuffers.size() == numberOfChannels)
74 m_preDelayBuffers.clear();
75 for (unsigned i = 0; i < numberOfChannels; ++i)
76 m_preDelayBuffers.append(adoptPtr(new AudioFloatArray(MaxPreDelayFrames)));
79 void DynamicsCompressorKernel::setPreDelayTime(float preDelayTime)
81 // Re-configure look-ahead section pre-delay if delay time has changed.
82 unsigned preDelayFrames = preDelayTime * sampleRate();
83 if (preDelayFrames > MaxPreDelayFrames - 1)
84 preDelayFrames = MaxPreDelayFrames - 1;
86 if (m_lastPreDelayFrames != preDelayFrames) {
87 m_lastPreDelayFrames = preDelayFrames;
88 for (unsigned i = 0; i < m_preDelayBuffers.size(); ++i)
89 m_preDelayBuffers[i]->zero();
91 m_preDelayReadIndex = 0;
92 m_preDelayWriteIndex = preDelayFrames;
96 void DynamicsCompressorKernel::process(float* sourceChannels[],
97 float* destinationChannels[],
98 unsigned numberOfChannels,
99 unsigned framesToProcess,
107 float effectBlend, /* equal power crossfade */
115 ASSERT(m_preDelayBuffers.size() == numberOfChannels);
117 float sampleRate = this->sampleRate();
119 float dryMix = 1 - effectBlend;
120 float wetMix = effectBlend;
122 // Threshold and headroom.
123 float linearThreshold = decibelsToLinear(dbThreshold);
124 float linearHeadroom = decibelsToLinear(dbHeadroom);
127 float maximum = 1.05f * linearHeadroom * linearThreshold;
128 float kk = (maximum - linearThreshold);
129 float inverseKK = 1 / kk;
131 float fullRangeGain = (linearThreshold + kk * saturate(1 - linearThreshold, 1));
132 float fullRangeMakeupGain = 1 / fullRangeGain;
133 // Empirical/perceptual tuning.
134 fullRangeMakeupGain = powf(fullRangeMakeupGain, 0.6f);
136 float masterLinearGain = decibelsToLinear(dbPostGain) * fullRangeMakeupGain;
138 // Attack parameters.
139 attackTime = max(0.001f, attackTime);
140 float attackFrames = attackTime * sampleRate;
142 // Release parameters.
143 float releaseFrames = sampleRate * releaseTime;
145 // Detector release time.
146 float satReleaseTime = 0.0025f;
147 float satReleaseFrames = satReleaseTime * sampleRate;
149 // Create a smooth function which passes through four points.
151 // Polynomial of the form
152 // y = a + b*x + c*x^2 + d*x^3 + e*x^4;
154 float y1 = releaseFrames * releaseZone1;
155 float y2 = releaseFrames * releaseZone2;
156 float y3 = releaseFrames * releaseZone3;
157 float y4 = releaseFrames * releaseZone4;
159 // All of these coefficients were derived for 4th order polynomial curve fitting where the y values
160 // match the evenly spaced x values as follows: (y1 : x == 0, y2 : x == 1, y3 : x == 2, y4 : x == 3)
161 float kA = 0.9999999999999998f*y1 + 1.8432219684323923e-16f*y2 - 1.9373394351676423e-16f*y3 + 8.824516011816245e-18f*y4;
162 float kB = -1.5788320352845888f*y1 + 2.3305837032074286f*y2 - 0.9141194204840429f*y3 + 0.1623677525612032f*y4;
163 float kC = 0.5334142869106424f*y1 - 1.272736789213631f*y2 + 0.9258856042207512f*y3 - 0.18656310191776226f*y4;
164 float kD = 0.08783463138207234f*y1 - 0.1694162967925622f*y2 + 0.08588057951595272f*y3 - 0.00429891410546283f*y4;
165 float kE = -0.042416883008123074f*y1 + 0.1115693827987602f*y2 - 0.09764676325265872f*y3 + 0.028494263462021576f*y4;
167 // x ranges from 0 -> 3 0 1 2 3
170 // y calculates adaptive release frames depending on the amount of compression.
172 setPreDelayTime(preDelayTime);
174 const int nDivisionFrames = 32;
176 const int nDivisions = framesToProcess / nDivisionFrames;
178 unsigned frameIndex = 0;
179 for (int i = 0; i < nDivisions; ++i) {
180 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
181 // Calculate desired gain
182 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
185 if (isnan(m_detectorAverage))
186 m_detectorAverage = 1;
187 if (isinf(m_detectorAverage))
188 m_detectorAverage = 1;
190 float desiredGain = m_detectorAverage;
192 // Pre-warp so we get desiredGain after sin() warp below.
193 float scaledDesiredGain = asinf(desiredGain) / (0.5f * piFloat);
195 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
196 // Deal with envelopes
197 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
199 // envelopeRate is the rate we slew from current compressor level to the desired level.
200 // The exact rate depends on if we're attacking or releasing and by how much.
203 bool isReleasing = scaledDesiredGain > m_compressorGain;
205 // compressionDiffDb is the difference between current compression level and the desired level.
206 float compressionDiffDb = linearToDecibels(m_compressorGain / scaledDesiredGain);
209 // Release mode - compressionDiffDb should be negative dB
210 m_maxAttackCompressionDiffDb = -1;
213 if (isnan(compressionDiffDb))
214 compressionDiffDb = -1;
215 if (isinf(compressionDiffDb))
216 compressionDiffDb = -1;
218 // Adaptive release - higher compression (lower compressionDiffDb) releases faster.
220 // Contain within range: -12 -> 0 then scale to go from 0 -> 3
221 float x = compressionDiffDb;
224 x = 0.25f * (x + 12);
226 // Compute adaptive release curve using 4th order polynomial.
227 // Normal values for the polynomial coefficients would create a monotonically increasing function.
231 float releaseFrames = kA + kB * x + kC * x2 + kD * x3 + kE * x4;
234 float dbPerFrame = kSpacingDb / releaseFrames;
236 envelopeRate = decibelsToLinear(dbPerFrame);
238 // Attack mode - compressionDiffDb should be positive dB
241 if (isnan(compressionDiffDb))
242 compressionDiffDb = 1;
243 if (isinf(compressionDiffDb))
244 compressionDiffDb = 1;
246 // As long as we're still in attack mode, use a rate based off
247 // the largest compressionDiffDb we've encountered so far.
248 if (m_maxAttackCompressionDiffDb == -1 || m_maxAttackCompressionDiffDb < compressionDiffDb)
249 m_maxAttackCompressionDiffDb = compressionDiffDb;
251 float effAttenDiffDb = max(0.5f, m_maxAttackCompressionDiffDb);
253 float x = 0.25f / effAttenDiffDb;
254 envelopeRate = 1 - powf(x, 1 / attackFrames);
257 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258 // Inner loop - calculate shaped power average - apply compression.
259 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
262 int preDelayReadIndex = m_preDelayReadIndex;
263 int preDelayWriteIndex = m_preDelayWriteIndex;
264 float detectorAverage = m_detectorAverage;
265 float compressorGain = m_compressorGain;
267 int loopFrames = nDivisionFrames;
268 while (loopFrames--) {
269 float compressorInput = 0;
271 // Predelay signal, computing compression amount from un-delayed version.
272 for (unsigned i = 0; i < numberOfChannels; ++i) {
273 float* delayBuffer = m_preDelayBuffers[i]->data();
274 float undelayedSource = sourceChannels[i][frameIndex];
275 delayBuffer[preDelayWriteIndex] = undelayedSource;
277 float absUndelayedSource = undelayedSource > 0 ? undelayedSource : -undelayedSource;
278 if (compressorInput < absUndelayedSource)
279 compressorInput = absUndelayedSource;
282 // Calculate shaped power on undelayed input.
284 float scaledInput = compressorInput;
285 float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
287 // Put through shaping curve.
288 // This is linear up to the threshold, then exponentially approaches the maximum (headroom amount above threshold).
289 // The transition from the threshold to the exponential portion is smooth (1st derivative matched).
290 float shapedInput = absInput < linearThreshold ? absInput : linearThreshold + kk * saturate(absInput - linearThreshold, inverseKK);
292 float attenuation = absInput <= 0.0001f ? 1 : shapedInput / absInput;
294 float attenuationDb = -linearToDecibels(attenuation);
295 attenuationDb = max(2.0f, attenuationDb);
297 float dbPerFrame = attenuationDb / satReleaseFrames;
299 float satReleaseRate = decibelsToLinear(dbPerFrame) - 1;
301 bool isRelease = (attenuation > detectorAverage);
302 float rate = isRelease ? satReleaseRate : 1;
304 detectorAverage += (attenuation - detectorAverage) * rate;
305 detectorAverage = min(1.0f, detectorAverage);
308 if (isnan(detectorAverage))
310 if (isinf(detectorAverage))
313 // Exponential approach to desired gain.
314 if (envelopeRate < 1) {
315 // Attack - reduce gain to desired.
316 compressorGain += (scaledDesiredGain - compressorGain) * envelopeRate;
318 // Release - exponentially increase gain to 1.0
319 compressorGain *= envelopeRate;
320 compressorGain = min(1.0f, compressorGain);
323 // Warp pre-compression gain to smooth out sharp exponential transition points.
324 float postWarpCompressorGain = sinf(0.5f * piFloat * compressorGain);
326 // Calculate total gain using master gain and effect blend.
327 float totalGain = dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
329 // Calculate metering.
330 float dbRealGain = 20 * log10(postWarpCompressorGain);
331 if (dbRealGain < m_meteringGain)
332 m_meteringGain = dbRealGain;
334 m_meteringGain += (dbRealGain - m_meteringGain) * m_meteringReleaseK;
337 for (unsigned i = 0; i < numberOfChannels; ++i) {
338 float* delayBuffer = m_preDelayBuffers[i]->data();
339 destinationChannels[i][frameIndex] = delayBuffer[preDelayReadIndex] * totalGain;
343 preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
344 preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
347 // Locals back to member variables.
348 m_preDelayReadIndex = preDelayReadIndex;
349 m_preDelayWriteIndex = preDelayWriteIndex;
350 m_detectorAverage = DenormalDisabler::flushDenormalFloatToZero(detectorAverage);
351 m_compressorGain = DenormalDisabler::flushDenormalFloatToZero(compressorGain);
356 void DynamicsCompressorKernel::reset()
358 m_detectorAverage = 0;
359 m_compressorGain = 1;
363 for (unsigned i = 0; i < m_preDelayBuffers.size(); ++i)
364 m_preDelayBuffers[i]->zero();
366 m_preDelayReadIndex = 0;
367 m_preDelayWriteIndex = DefaultPreDelayFrames;
369 m_maxAttackCompressionDiffDb = -1; // uninitialized state
372 } // namespace WebCore
374 #endif // ENABLE(WEB_AUDIO)