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