content/media/webaudio/blink/DynamicsCompressorKernel.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

     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  */
    29 #include "DynamicsCompressorKernel.h"
    31 #include "DenormalDisabler.h"
    32 #include <algorithm>
    34 #include "mozilla/FloatingPoint.h"
    35 #include "mozilla/Constants.h"
    36 #include "WebAudioUtils.h"
    38 using namespace std;
    40 using namespace mozilla::dom; // for WebAudioUtils
    41 using mozilla::IsInfinite;
    42 using mozilla::IsNaN;
    44 namespace WebCore {
    47 // Metering hits peaks instantly, but releases this fast (in seconds).
    48 const float meteringReleaseTimeConstant = 0.325f;
    50 const float uninitializedValue = -1;
    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);
    69     // Initializes most member variables
    70     reset();
    72     m_meteringReleaseK =
    73         static_cast<float>(WebAudioUtils::DiscreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate));
    74 }
    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     }
    84     return amount;
    85 }
    87 void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels)
    88 {
    89     if (m_preDelayBuffers.Length() == numberOfChannels)
    90         return;
    92     m_preDelayBuffers.Clear();
    93     for (unsigned i = 0; i < numberOfChannels; ++i)
    94         m_preDelayBuffers.AppendElement(new float[MaxPreDelayFrames]);
    95 }
    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;
   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);
   109         m_preDelayReadIndex = 0;
   110         m_preDelayWriteIndex = preDelayFrames;
   111     }
   112 }
   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;
   122     return m_linearThreshold + (1 - expf(-k * (x - m_linearThreshold))) / k;
   123 }
   125 // Full compression curve with constant ratio after knee.
   126 float DynamicsCompressorKernel::saturate(float x, float k)
   127 {
   128     float y;
   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);
   137         y = WebAudioUtils::ConvertDecibelsToLinear(yDb);
   138     }
   140     return y;
   141 }
   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;
   151     float x2 = x * 1.001;
   153     float xDb = WebAudioUtils::ConvertLinearToDecibels(x, -1000.0f);
   154     float x2Db = WebAudioUtils::ConvertLinearToDecibels(x2, -1000.0f);
   156     float yDb = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(x, k), -1000.0f);
   157     float y2Db = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(x2, k), -1000.0f);
   159     float m = (y2Db - yDb) / (x2Db - xDb);
   161     return m;
   162 }
   164 float DynamicsCompressorKernel::kAtSlope(float desiredSlope)
   165 {
   166     float xDb = m_dbThreshold + m_dbKnee;
   167     float x = WebAudioUtils::ConvertDecibelsToLinear(xDb);
   169     // Approximate k given initial values.
   170     float minK = 0.1;
   171     float maxK = 10000;
   172     float k = 5;
   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);
   178         if (slope < desiredSlope) {
   179             // k is too high.
   180             maxK = k;
   181         } else {
   182             // k is too low.
   183             minK = k;
   184         }
   186         // Re-calculate based on geometric mean.
   187         k = sqrtf(minK * maxK);
   188     }
   190     return k;
   191 }
   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;
   201         // Compute knee parameters.
   202         m_ratio = ratio;
   203         m_slope = 1 / m_ratio;
   205         float k = kAtSlope(1 / m_ratio);
   207         m_kneeThresholdDb = dbThreshold + dbKnee;
   208         m_kneeThreshold = WebAudioUtils::ConvertDecibelsToLinear(m_kneeThresholdDb);
   210         m_ykneeThresholdDb = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(m_kneeThreshold, k), -1000.0f);
   212         m_K = k;
   213     }
   214     return m_K;
   215 }
   217 void DynamicsCompressorKernel::process(float* sourceChannels[],
   218                                        float* destinationChannels[],
   219                                        unsigned numberOfChannels,
   220                                        unsigned framesToProcess,
   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 */
   231                                        float releaseZone1,
   232                                        float releaseZone2,
   233                                        float releaseZone3,
   234                                        float releaseZone4
   235                                        )
   236 {
   237     MOZ_ASSERT(m_preDelayBuffers.Length() == numberOfChannels);
   239     float sampleRate = this->sampleRate();
   241     float dryMix = 1 - effectBlend;
   242     float wetMix = effectBlend;
   244     float k = updateStaticCurveParameters(dbThreshold, dbKnee, ratio);
   246     // Makeup gain.
   247     float fullRangeGain = saturate(1, k);
   248     float fullRangeMakeupGain = 1 / fullRangeGain;
   250     // Empirical/perceptual tuning.
   251     fullRangeMakeupGain = powf(fullRangeMakeupGain, 0.6f);
   253     float masterLinearGain = WebAudioUtils::ConvertDecibelsToLinear(dbPostGain) * fullRangeMakeupGain;
   255     // Attack parameters.
   256     attackTime = max(0.001f, attackTime);
   257     float attackFrames = attackTime * sampleRate;
   259     // Release parameters.
   260     float releaseFrames = sampleRate * releaseTime;
   262     // Detector release time.
   263     float satReleaseTime = 0.0025f;
   264     float satReleaseFrames = satReleaseTime * sampleRate;
   266     // Create a smooth function which passes through four points.
   268     // Polynomial of the form
   269     // y = a + b*x + c*x^2 + d*x^3 + e*x^4;
   271     float y1 = releaseFrames * releaseZone1;
   272     float y2 = releaseFrames * releaseZone2;
   273     float y3 = releaseFrames * releaseZone3;
   274     float y4 = releaseFrames * releaseZone4;
   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;
   284     // x ranges from 0 -> 3       0    1    2   3
   285     //                           -15  -10  -5   0db
   287     // y calculates adaptive release frames depending on the amount of compression.
   289     setPreDelayTime(preDelayTime);
   291     const int nDivisionFrames = 32;
   293     const int nDivisions = framesToProcess / nDivisionFrames;
   295     unsigned frameIndex = 0;
   296     for (int i = 0; i < nDivisions; ++i) {
   297         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   298         // Calculate desired gain
   299         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   301         // Fix gremlins.
   302         if (IsNaN(m_detectorAverage))
   303             m_detectorAverage = 1;
   304         if (IsInfinite(m_detectorAverage))
   305             m_detectorAverage = 1;
   307         float desiredGain = m_detectorAverage;
   309         // Pre-warp so we get desiredGain after sin() warp below.
   310         float scaledDesiredGain = asinf(desiredGain) / (0.5f * M_PI);
   312         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   313         // Deal with envelopes
   314         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   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;
   320         bool isReleasing = scaledDesiredGain > m_compressorGain;
   322         // compressionDiffDb is the difference between current compression level and the desired level.
   323         float compressionDiffDb = WebAudioUtils::ConvertLinearToDecibels(m_compressorGain / scaledDesiredGain, -1000.0f);
   325         if (isReleasing) {
   326             // Release mode - compressionDiffDb should be negative dB
   327             m_maxAttackCompressionDiffDb = -1;
   329             // Fix gremlins.
   330             if (IsNaN(compressionDiffDb))
   331                 compressionDiffDb = -1;
   332             if (IsInfinite(compressionDiffDb))
   333                 compressionDiffDb = -1;
   335             // Adaptive release - higher compression (lower compressionDiffDb)  releases faster.
   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);
   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;
   350 #define kSpacingDb 5
   351             float dbPerFrame = kSpacingDb / releaseFrames;
   353             envelopeRate = WebAudioUtils::ConvertDecibelsToLinear(dbPerFrame);
   354         } else {
   355             // Attack mode - compressionDiffDb should be positive dB
   357             // Fix gremlins.
   358             if (IsNaN(compressionDiffDb))
   359                 compressionDiffDb = 1;
   360             if (IsInfinite(compressionDiffDb))
   361                 compressionDiffDb = 1;
   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;
   368             float effAttenDiffDb = max(0.5f, m_maxAttackCompressionDiffDb);
   370             float x = 0.25f / effAttenDiffDb;
   371             envelopeRate = 1 - powf(x, 1 / attackFrames);
   372         }
   374         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   375         // Inner loop - calculate shaped power average - apply compression.
   376         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   378         {
   379             int preDelayReadIndex = m_preDelayReadIndex;
   380             int preDelayWriteIndex = m_preDelayWriteIndex;
   381             float detectorAverage = m_detectorAverage;
   382             float compressorGain = m_compressorGain;
   384             int loopFrames = nDivisionFrames;
   385             while (loopFrames--) {
   386                 float compressorInput = 0;
   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;
   394                     float absUndelayedSource = undelayedSource > 0 ? undelayedSource : -undelayedSource;
   395                     if (compressorInput < absUndelayedSource)
   396                         compressorInput = absUndelayedSource;
   397                 }
   399                 // Calculate shaped power on undelayed input.
   401                 float scaledInput = compressorInput;
   402                 float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
   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);
   410                 float attenuation = absInput <= 0.0001f ? 1 : shapedInput / absInput;
   412                 float attenuationDb = -WebAudioUtils::ConvertLinearToDecibels(attenuation, -1000.0f);
   413                 attenuationDb = max(2.0f, attenuationDb);
   415                 float dbPerFrame = attenuationDb / satReleaseFrames;
   417                 float satReleaseRate = WebAudioUtils::ConvertDecibelsToLinear(dbPerFrame) - 1;
   419                 bool isRelease = (attenuation > detectorAverage);
   420                 float rate = isRelease ? satReleaseRate : 1;
   422                 detectorAverage += (attenuation - detectorAverage) * rate;
   423                 detectorAverage = min(1.0f, detectorAverage);
   425                 // Fix gremlins.
   426                 if (IsNaN(detectorAverage))
   427                     detectorAverage = 1;
   428                 if (IsInfinite(detectorAverage))
   429                     detectorAverage = 1;
   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                 }
   441                 // Warp pre-compression gain to smooth out sharp exponential transition points.
   442                 float postWarpCompressorGain = sinf(0.5f * M_PI * compressorGain);
   444                 // Calculate total gain using master gain and effect blend.
   445                 float totalGain = dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
   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;
   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                 }
   460                 frameIndex++;
   461                 preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
   462                 preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
   463             }
   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 }
   474 void DynamicsCompressorKernel::reset()
   475 {
   476     m_detectorAverage = 0;
   477     m_compressorGain = 1;
   478     m_meteringGain = 1;
   480     // Predelay section.
   481     for (unsigned i = 0; i < m_preDelayBuffers.Length(); ++i)
   482         memset(m_preDelayBuffers[i], 0, sizeof(float) * MaxPreDelayFrames);
   484     m_preDelayReadIndex = 0;
   485     m_preDelayWriteIndex = DefaultPreDelayFrames;
   487     m_maxAttackCompressionDiffDb = -1; // uninitialized state
   488 }
   490 } // namespace WebCore

mercurial