tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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