content/media/webaudio/blink/DynamicsCompressorKernel.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:3f3dd8f71728
1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
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.
16 *
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.
27 */
28
29 #include "DynamicsCompressorKernel.h"
30
31 #include "DenormalDisabler.h"
32 #include <algorithm>
33
34 #include "mozilla/FloatingPoint.h"
35 #include "mozilla/Constants.h"
36 #include "WebAudioUtils.h"
37
38 using namespace std;
39
40 using namespace mozilla::dom; // for WebAudioUtils
41 using mozilla::IsInfinite;
42 using mozilla::IsNaN;
43
44 namespace WebCore {
45
46
47 // Metering hits peaks instantly, but releases this fast (in seconds).
48 const float meteringReleaseTimeConstant = 0.325f;
49
50 const float uninitializedValue = -1;
51
52 DynamicsCompressorKernel::DynamicsCompressorKernel(float sampleRate, unsigned numberOfChannels)
53 : m_sampleRate(sampleRate)
54 , m_lastPreDelayFrames(DefaultPreDelayFrames)
55 , m_preDelayReadIndex(0)
56 , m_preDelayWriteIndex(DefaultPreDelayFrames)
57 , m_ratio(uninitializedValue)
58 , m_slope(uninitializedValue)
59 , m_linearThreshold(uninitializedValue)
60 , m_dbThreshold(uninitializedValue)
61 , m_dbKnee(uninitializedValue)
62 , m_kneeThreshold(uninitializedValue)
63 , m_kneeThresholdDb(uninitializedValue)
64 , m_ykneeThresholdDb(uninitializedValue)
65 , m_K(uninitializedValue)
66 {
67 setNumberOfChannels(numberOfChannels);
68
69 // Initializes most member variables
70 reset();
71
72 m_meteringReleaseK =
73 static_cast<float>(WebAudioUtils::DiscreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate));
74 }
75
76 size_t DynamicsCompressorKernel::sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
77 {
78 size_t amount = 0;
79 amount += m_preDelayBuffers.SizeOfExcludingThis(aMallocSizeOf);
80 for (size_t i = 0; i < m_preDelayBuffers.Length(); i++) {
81 amount += m_preDelayBuffers[i].SizeOfExcludingThis(aMallocSizeOf);
82 }
83
84 return amount;
85 }
86
87 void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels)
88 {
89 if (m_preDelayBuffers.Length() == numberOfChannels)
90 return;
91
92 m_preDelayBuffers.Clear();
93 for (unsigned i = 0; i < numberOfChannels; ++i)
94 m_preDelayBuffers.AppendElement(new float[MaxPreDelayFrames]);
95 }
96
97 void DynamicsCompressorKernel::setPreDelayTime(float preDelayTime)
98 {
99 // Re-configure look-ahead section pre-delay if delay time has changed.
100 unsigned preDelayFrames = preDelayTime * sampleRate();
101 if (preDelayFrames > MaxPreDelayFrames - 1)
102 preDelayFrames = MaxPreDelayFrames - 1;
103
104 if (m_lastPreDelayFrames != preDelayFrames) {
105 m_lastPreDelayFrames = preDelayFrames;
106 for (unsigned i = 0; i < m_preDelayBuffers.Length(); ++i)
107 memset(m_preDelayBuffers[i], 0, sizeof(float) * MaxPreDelayFrames);
108
109 m_preDelayReadIndex = 0;
110 m_preDelayWriteIndex = preDelayFrames;
111 }
112 }
113
114 // Exponential curve for the knee.
115 // It is 1st derivative matched at m_linearThreshold and asymptotically approaches the value m_linearThreshold + 1 / k.
116 float DynamicsCompressorKernel::kneeCurve(float x, float k)
117 {
118 // Linear up to threshold.
119 if (x < m_linearThreshold)
120 return x;
121
122 return m_linearThreshold + (1 - expf(-k * (x - m_linearThreshold))) / k;
123 }
124
125 // Full compression curve with constant ratio after knee.
126 float DynamicsCompressorKernel::saturate(float x, float k)
127 {
128 float y;
129
130 if (x < m_kneeThreshold)
131 y = kneeCurve(x, k);
132 else {
133 // Constant ratio after knee.
134 float xDb = WebAudioUtils::ConvertLinearToDecibels(x, -1000.0f);
135 float yDb = m_ykneeThresholdDb + m_slope * (xDb - m_kneeThresholdDb);
136
137 y = WebAudioUtils::ConvertDecibelsToLinear(yDb);
138 }
139
140 return y;
141 }
142
143 // Approximate 1st derivative with input and output expressed in dB.
144 // This slope is equal to the inverse of the compression "ratio".
145 // In other words, a compression ratio of 20 would be a slope of 1/20.
146 float DynamicsCompressorKernel::slopeAt(float x, float k)
147 {
148 if (x < m_linearThreshold)
149 return 1;
150
151 float x2 = x * 1.001;
152
153 float xDb = WebAudioUtils::ConvertLinearToDecibels(x, -1000.0f);
154 float x2Db = WebAudioUtils::ConvertLinearToDecibels(x2, -1000.0f);
155
156 float yDb = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(x, k), -1000.0f);
157 float y2Db = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(x2, k), -1000.0f);
158
159 float m = (y2Db - yDb) / (x2Db - xDb);
160
161 return m;
162 }
163
164 float DynamicsCompressorKernel::kAtSlope(float desiredSlope)
165 {
166 float xDb = m_dbThreshold + m_dbKnee;
167 float x = WebAudioUtils::ConvertDecibelsToLinear(xDb);
168
169 // Approximate k given initial values.
170 float minK = 0.1;
171 float maxK = 10000;
172 float k = 5;
173
174 for (int i = 0; i < 15; ++i) {
175 // A high value for k will more quickly asymptotically approach a slope of 0.
176 float slope = slopeAt(x, k);
177
178 if (slope < desiredSlope) {
179 // k is too high.
180 maxK = k;
181 } else {
182 // k is too low.
183 minK = k;
184 }
185
186 // Re-calculate based on geometric mean.
187 k = sqrtf(minK * maxK);
188 }
189
190 return k;
191 }
192
193 float DynamicsCompressorKernel::updateStaticCurveParameters(float dbThreshold, float dbKnee, float ratio)
194 {
195 if (dbThreshold != m_dbThreshold || dbKnee != m_dbKnee || ratio != m_ratio) {
196 // Threshold and knee.
197 m_dbThreshold = dbThreshold;
198 m_linearThreshold = WebAudioUtils::ConvertDecibelsToLinear(dbThreshold);
199 m_dbKnee = dbKnee;
200
201 // Compute knee parameters.
202 m_ratio = ratio;
203 m_slope = 1 / m_ratio;
204
205 float k = kAtSlope(1 / m_ratio);
206
207 m_kneeThresholdDb = dbThreshold + dbKnee;
208 m_kneeThreshold = WebAudioUtils::ConvertDecibelsToLinear(m_kneeThresholdDb);
209
210 m_ykneeThresholdDb = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(m_kneeThreshold, k), -1000.0f);
211
212 m_K = k;
213 }
214 return m_K;
215 }
216
217 void DynamicsCompressorKernel::process(float* sourceChannels[],
218 float* destinationChannels[],
219 unsigned numberOfChannels,
220 unsigned framesToProcess,
221
222 float dbThreshold,
223 float dbKnee,
224 float ratio,
225 float attackTime,
226 float releaseTime,
227 float preDelayTime,
228 float dbPostGain,
229 float effectBlend, /* equal power crossfade */
230
231 float releaseZone1,
232 float releaseZone2,
233 float releaseZone3,
234 float releaseZone4
235 )
236 {
237 MOZ_ASSERT(m_preDelayBuffers.Length() == numberOfChannels);
238
239 float sampleRate = this->sampleRate();
240
241 float dryMix = 1 - effectBlend;
242 float wetMix = effectBlend;
243
244 float k = updateStaticCurveParameters(dbThreshold, dbKnee, ratio);
245
246 // Makeup gain.
247 float fullRangeGain = saturate(1, k);
248 float fullRangeMakeupGain = 1 / fullRangeGain;
249
250 // Empirical/perceptual tuning.
251 fullRangeMakeupGain = powf(fullRangeMakeupGain, 0.6f);
252
253 float masterLinearGain = WebAudioUtils::ConvertDecibelsToLinear(dbPostGain) * fullRangeMakeupGain;
254
255 // Attack parameters.
256 attackTime = max(0.001f, attackTime);
257 float attackFrames = attackTime * sampleRate;
258
259 // Release parameters.
260 float releaseFrames = sampleRate * releaseTime;
261
262 // Detector release time.
263 float satReleaseTime = 0.0025f;
264 float satReleaseFrames = satReleaseTime * sampleRate;
265
266 // Create a smooth function which passes through four points.
267
268 // Polynomial of the form
269 // y = a + b*x + c*x^2 + d*x^3 + e*x^4;
270
271 float y1 = releaseFrames * releaseZone1;
272 float y2 = releaseFrames * releaseZone2;
273 float y3 = releaseFrames * releaseZone3;
274 float y4 = releaseFrames * releaseZone4;
275
276 // All of these coefficients were derived for 4th order polynomial curve fitting where the y values
277 // match the evenly spaced x values as follows: (y1 : x == 0, y2 : x == 1, y3 : x == 2, y4 : x == 3)
278 float kA = 0.9999999999999998f*y1 + 1.8432219684323923e-16f*y2 - 1.9373394351676423e-16f*y3 + 8.824516011816245e-18f*y4;
279 float kB = -1.5788320352845888f*y1 + 2.3305837032074286f*y2 - 0.9141194204840429f*y3 + 0.1623677525612032f*y4;
280 float kC = 0.5334142869106424f*y1 - 1.272736789213631f*y2 + 0.9258856042207512f*y3 - 0.18656310191776226f*y4;
281 float kD = 0.08783463138207234f*y1 - 0.1694162967925622f*y2 + 0.08588057951595272f*y3 - 0.00429891410546283f*y4;
282 float kE = -0.042416883008123074f*y1 + 0.1115693827987602f*y2 - 0.09764676325265872f*y3 + 0.028494263462021576f*y4;
283
284 // x ranges from 0 -> 3 0 1 2 3
285 // -15 -10 -5 0db
286
287 // y calculates adaptive release frames depending on the amount of compression.
288
289 setPreDelayTime(preDelayTime);
290
291 const int nDivisionFrames = 32;
292
293 const int nDivisions = framesToProcess / nDivisionFrames;
294
295 unsigned frameIndex = 0;
296 for (int i = 0; i < nDivisions; ++i) {
297 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
298 // Calculate desired gain
299 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
300
301 // Fix gremlins.
302 if (IsNaN(m_detectorAverage))
303 m_detectorAverage = 1;
304 if (IsInfinite(m_detectorAverage))
305 m_detectorAverage = 1;
306
307 float desiredGain = m_detectorAverage;
308
309 // Pre-warp so we get desiredGain after sin() warp below.
310 float scaledDesiredGain = asinf(desiredGain) / (0.5f * M_PI);
311
312 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313 // Deal with envelopes
314 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
315
316 // envelopeRate is the rate we slew from current compressor level to the desired level.
317 // The exact rate depends on if we're attacking or releasing and by how much.
318 float envelopeRate;
319
320 bool isReleasing = scaledDesiredGain > m_compressorGain;
321
322 // compressionDiffDb is the difference between current compression level and the desired level.
323 float compressionDiffDb = WebAudioUtils::ConvertLinearToDecibels(m_compressorGain / scaledDesiredGain, -1000.0f);
324
325 if (isReleasing) {
326 // Release mode - compressionDiffDb should be negative dB
327 m_maxAttackCompressionDiffDb = -1;
328
329 // Fix gremlins.
330 if (IsNaN(compressionDiffDb))
331 compressionDiffDb = -1;
332 if (IsInfinite(compressionDiffDb))
333 compressionDiffDb = -1;
334
335 // Adaptive release - higher compression (lower compressionDiffDb) releases faster.
336
337 // Contain within range: -12 -> 0 then scale to go from 0 -> 3
338 float x = compressionDiffDb;
339 x = max(-12.0f, x);
340 x = min(0.0f, x);
341 x = 0.25f * (x + 12);
342
343 // Compute adaptive release curve using 4th order polynomial.
344 // Normal values for the polynomial coefficients would create a monotonically increasing function.
345 float x2 = x * x;
346 float x3 = x2 * x;
347 float x4 = x2 * x2;
348 float releaseFrames = kA + kB * x + kC * x2 + kD * x3 + kE * x4;
349
350 #define kSpacingDb 5
351 float dbPerFrame = kSpacingDb / releaseFrames;
352
353 envelopeRate = WebAudioUtils::ConvertDecibelsToLinear(dbPerFrame);
354 } else {
355 // Attack mode - compressionDiffDb should be positive dB
356
357 // Fix gremlins.
358 if (IsNaN(compressionDiffDb))
359 compressionDiffDb = 1;
360 if (IsInfinite(compressionDiffDb))
361 compressionDiffDb = 1;
362
363 // As long as we're still in attack mode, use a rate based off
364 // the largest compressionDiffDb we've encountered so far.
365 if (m_maxAttackCompressionDiffDb == -1 || m_maxAttackCompressionDiffDb < compressionDiffDb)
366 m_maxAttackCompressionDiffDb = compressionDiffDb;
367
368 float effAttenDiffDb = max(0.5f, m_maxAttackCompressionDiffDb);
369
370 float x = 0.25f / effAttenDiffDb;
371 envelopeRate = 1 - powf(x, 1 / attackFrames);
372 }
373
374 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
375 // Inner loop - calculate shaped power average - apply compression.
376 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
377
378 {
379 int preDelayReadIndex = m_preDelayReadIndex;
380 int preDelayWriteIndex = m_preDelayWriteIndex;
381 float detectorAverage = m_detectorAverage;
382 float compressorGain = m_compressorGain;
383
384 int loopFrames = nDivisionFrames;
385 while (loopFrames--) {
386 float compressorInput = 0;
387
388 // Predelay signal, computing compression amount from un-delayed version.
389 for (unsigned i = 0; i < numberOfChannels; ++i) {
390 float* delayBuffer = m_preDelayBuffers[i];
391 float undelayedSource = sourceChannels[i][frameIndex];
392 delayBuffer[preDelayWriteIndex] = undelayedSource;
393
394 float absUndelayedSource = undelayedSource > 0 ? undelayedSource : -undelayedSource;
395 if (compressorInput < absUndelayedSource)
396 compressorInput = absUndelayedSource;
397 }
398
399 // Calculate shaped power on undelayed input.
400
401 float scaledInput = compressorInput;
402 float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
403
404 // Put through shaping curve.
405 // This is linear up to the threshold, then enters a "knee" portion followed by the "ratio" portion.
406 // The transition from the threshold to the knee is smooth (1st derivative matched).
407 // The transition from the knee to the ratio portion is smooth (1st derivative matched).
408 float shapedInput = saturate(absInput, k);
409
410 float attenuation = absInput <= 0.0001f ? 1 : shapedInput / absInput;
411
412 float attenuationDb = -WebAudioUtils::ConvertLinearToDecibels(attenuation, -1000.0f);
413 attenuationDb = max(2.0f, attenuationDb);
414
415 float dbPerFrame = attenuationDb / satReleaseFrames;
416
417 float satReleaseRate = WebAudioUtils::ConvertDecibelsToLinear(dbPerFrame) - 1;
418
419 bool isRelease = (attenuation > detectorAverage);
420 float rate = isRelease ? satReleaseRate : 1;
421
422 detectorAverage += (attenuation - detectorAverage) * rate;
423 detectorAverage = min(1.0f, detectorAverage);
424
425 // Fix gremlins.
426 if (IsNaN(detectorAverage))
427 detectorAverage = 1;
428 if (IsInfinite(detectorAverage))
429 detectorAverage = 1;
430
431 // Exponential approach to desired gain.
432 if (envelopeRate < 1) {
433 // Attack - reduce gain to desired.
434 compressorGain += (scaledDesiredGain - compressorGain) * envelopeRate;
435 } else {
436 // Release - exponentially increase gain to 1.0
437 compressorGain *= envelopeRate;
438 compressorGain = min(1.0f, compressorGain);
439 }
440
441 // Warp pre-compression gain to smooth out sharp exponential transition points.
442 float postWarpCompressorGain = sinf(0.5f * M_PI * compressorGain);
443
444 // Calculate total gain using master gain and effect blend.
445 float totalGain = dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
446
447 // Calculate metering.
448 float dbRealGain = 20 * log10(postWarpCompressorGain);
449 if (dbRealGain < m_meteringGain)
450 m_meteringGain = dbRealGain;
451 else
452 m_meteringGain += (dbRealGain - m_meteringGain) * m_meteringReleaseK;
453
454 // Apply final gain.
455 for (unsigned i = 0; i < numberOfChannels; ++i) {
456 float* delayBuffer = m_preDelayBuffers[i];
457 destinationChannels[i][frameIndex] = delayBuffer[preDelayReadIndex] * totalGain;
458 }
459
460 frameIndex++;
461 preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
462 preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
463 }
464
465 // Locals back to member variables.
466 m_preDelayReadIndex = preDelayReadIndex;
467 m_preDelayWriteIndex = preDelayWriteIndex;
468 m_detectorAverage = DenormalDisabler::flushDenormalFloatToZero(detectorAverage);
469 m_compressorGain = DenormalDisabler::flushDenormalFloatToZero(compressorGain);
470 }
471 }
472 }
473
474 void DynamicsCompressorKernel::reset()
475 {
476 m_detectorAverage = 0;
477 m_compressorGain = 1;
478 m_meteringGain = 1;
479
480 // Predelay section.
481 for (unsigned i = 0; i < m_preDelayBuffers.Length(); ++i)
482 memset(m_preDelayBuffers[i], 0, sizeof(float) * MaxPreDelayFrames);
483
484 m_preDelayReadIndex = 0;
485 m_preDelayWriteIndex = DefaultPreDelayFrames;
486
487 m_maxAttackCompressionDiffDb = -1; // uninitialized state
488 }
489
490 } // namespace WebCore

mercurial