LCOV - code coverage report
Current view: top level - dom/media/webaudio/blink - DynamicsCompressor.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 155 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 10 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "DynamicsCompressor.h"
      30             : #include "AlignmentUtils.h"
      31             : #include "AudioBlock.h"
      32             : 
      33             : #include <cmath>
      34             : #include "AudioNodeEngine.h"
      35             : #include "nsDebug.h"
      36             : 
      37             : using mozilla::WEBAUDIO_BLOCK_SIZE;
      38             : using mozilla::AudioBlockCopyChannelWithScale;
      39             : 
      40             : namespace WebCore {
      41             : 
      42           0 : DynamicsCompressor::DynamicsCompressor(float sampleRate, unsigned numberOfChannels)
      43             :     : m_numberOfChannels(numberOfChannels)
      44             :     , m_sampleRate(sampleRate)
      45           0 :     , m_compressor(sampleRate, numberOfChannels)
      46             : {
      47             :     // Uninitialized state - for parameter recalculation.
      48           0 :     m_lastFilterStageRatio = -1;
      49           0 :     m_lastAnchor = -1;
      50           0 :     m_lastFilterStageGain = -1;
      51             : 
      52           0 :     setNumberOfChannels(numberOfChannels);
      53           0 :     initializeParameters();
      54           0 : }
      55             : 
      56           0 : size_t DynamicsCompressor::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
      57             : {
      58           0 :     size_t amount = aMallocSizeOf(this);
      59           0 :     amount += m_preFilterPacks.ShallowSizeOfExcludingThis(aMallocSizeOf);
      60           0 :     for (size_t i = 0; i < m_preFilterPacks.Length(); i++) {
      61           0 :         if (m_preFilterPacks[i]) {
      62           0 :             amount += m_preFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
      63             :         }
      64             :     }
      65             : 
      66           0 :     amount += m_postFilterPacks.ShallowSizeOfExcludingThis(aMallocSizeOf);
      67           0 :     for (size_t i = 0; i < m_postFilterPacks.Length(); i++) {
      68           0 :         if (m_postFilterPacks[i]) {
      69           0 :             amount += m_postFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
      70             :         }
      71             :     }
      72             : 
      73           0 :     amount += aMallocSizeOf(m_sourceChannels.get());
      74           0 :     amount += aMallocSizeOf(m_destinationChannels.get());
      75           0 :     amount += m_compressor.sizeOfExcludingThis(aMallocSizeOf);
      76           0 :     return amount;
      77             : }
      78             : 
      79           0 : void DynamicsCompressor::setParameterValue(unsigned parameterID, float value)
      80             : {
      81           0 :     MOZ_ASSERT(parameterID < ParamLast);
      82           0 :     if (parameterID < ParamLast)
      83           0 :         m_parameters[parameterID] = value;
      84           0 : }
      85             : 
      86           0 : void DynamicsCompressor::initializeParameters()
      87             : {
      88             :     // Initializes compressor to default values.
      89             : 
      90           0 :     m_parameters[ParamThreshold] = -24; // dB
      91           0 :     m_parameters[ParamKnee] = 30; // dB
      92           0 :     m_parameters[ParamRatio] = 12; // unit-less
      93           0 :     m_parameters[ParamAttack] = 0.003f; // seconds
      94           0 :     m_parameters[ParamRelease] = 0.250f; // seconds
      95           0 :     m_parameters[ParamPreDelay] = 0.006f; // seconds
      96             : 
      97             :     // Release zone values 0 -> 1.
      98           0 :     m_parameters[ParamReleaseZone1] = 0.09f;
      99           0 :     m_parameters[ParamReleaseZone2] = 0.16f;
     100           0 :     m_parameters[ParamReleaseZone3] = 0.42f;
     101           0 :     m_parameters[ParamReleaseZone4] = 0.98f;
     102             : 
     103           0 :     m_parameters[ParamFilterStageGain] = 4.4f; // dB
     104           0 :     m_parameters[ParamFilterStageRatio] = 2;
     105           0 :     m_parameters[ParamFilterAnchor] = 15000 / nyquist();
     106             : 
     107           0 :     m_parameters[ParamPostGain] = 0; // dB
     108           0 :     m_parameters[ParamReduction] = 0; // dB
     109             : 
     110             :     // Linear crossfade (0 -> 1).
     111           0 :     m_parameters[ParamEffectBlend] = 1;
     112           0 : }
     113             : 
     114           0 : float DynamicsCompressor::parameterValue(unsigned parameterID)
     115             : {
     116           0 :     MOZ_ASSERT(parameterID < ParamLast);
     117           0 :     return m_parameters[parameterID];
     118             : }
     119             : 
     120           0 : void DynamicsCompressor::setEmphasisStageParameters(unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */)
     121             : {
     122           0 :     float gk = 1 - gain / 20;
     123           0 :     float f1 = normalizedFrequency * gk;
     124           0 :     float f2 = normalizedFrequency / gk;
     125           0 :     float r1 = expf(-f1 * M_PI);
     126           0 :     float r2 = expf(-f2 * M_PI);
     127             : 
     128           0 :     MOZ_ASSERT(m_numberOfChannels == m_preFilterPacks.Length());
     129             : 
     130           0 :     for (unsigned i = 0; i < m_numberOfChannels; ++i) {
     131             :         // Set pre-filter zero and pole to create an emphasis filter.
     132           0 :         ZeroPole& preFilter = m_preFilterPacks[i]->filters[stageIndex];
     133           0 :         preFilter.setZero(r1);
     134           0 :         preFilter.setPole(r2);
     135             : 
     136             :         // Set post-filter with zero and pole reversed to create the de-emphasis filter.
     137             :         // If there were no compressor kernel in between, they would cancel each other out (allpass filter).
     138           0 :         ZeroPole& postFilter = m_postFilterPacks[i]->filters[stageIndex];
     139           0 :         postFilter.setZero(r2);
     140           0 :         postFilter.setPole(r1);
     141             :     }
     142           0 : }
     143             : 
     144           0 : void DynamicsCompressor::setEmphasisParameters(float gain, float anchorFreq, float filterStageRatio)
     145             : {
     146           0 :     setEmphasisStageParameters(0, gain, anchorFreq);
     147           0 :     setEmphasisStageParameters(1, gain, anchorFreq / filterStageRatio);
     148           0 :     setEmphasisStageParameters(2, gain, anchorFreq / (filterStageRatio * filterStageRatio));
     149           0 :     setEmphasisStageParameters(3, gain, anchorFreq / (filterStageRatio * filterStageRatio * filterStageRatio));
     150           0 : }
     151             : 
     152           0 : void DynamicsCompressor::process(const AudioBlock* sourceChunk, AudioBlock* destinationChunk, unsigned framesToProcess)
     153             : {
     154             :     // Though numberOfChannels is retrived from destinationBus, we still name it numberOfChannels instead of numberOfDestinationChannels.
     155             :     // It's because we internally match sourceChannels's size to destinationBus by channel up/down mix. Thus we need numberOfChannels
     156             :     // to do the loop work for both m_sourceChannels and m_destinationChannels.
     157             : 
     158           0 :     unsigned numberOfChannels = destinationChunk->ChannelCount();
     159           0 :     unsigned numberOfSourceChannels = sourceChunk->ChannelCount();
     160             : 
     161           0 :     MOZ_ASSERT(numberOfChannels == m_numberOfChannels && numberOfSourceChannels);
     162             : 
     163           0 :     if (numberOfChannels != m_numberOfChannels || !numberOfSourceChannels) {
     164           0 :         destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
     165           0 :         return;
     166             :     }
     167             : 
     168           0 :     switch (numberOfChannels) {
     169             :     case 2: // stereo
     170           0 :         m_sourceChannels[0] = static_cast<const float*>(sourceChunk->mChannelData[0]);
     171             : 
     172           0 :         if (numberOfSourceChannels > 1)
     173           0 :             m_sourceChannels[1] = static_cast<const float*>(sourceChunk->mChannelData[1]);
     174             :         else
     175             :             // Simply duplicate mono channel input data to right channel for stereo processing.
     176           0 :             m_sourceChannels[1] = m_sourceChannels[0];
     177             : 
     178           0 :         break;
     179             :     default:
     180             :         // FIXME : support other number of channels.
     181           0 :         NS_WARNING("Support other number of channels");
     182           0 :         destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
     183           0 :         return;
     184             :     }
     185             : 
     186           0 :     for (unsigned i = 0; i < numberOfChannels; ++i)
     187           0 :         m_destinationChannels[i] = const_cast<float*>(static_cast<const float*>(
     188           0 :             destinationChunk->mChannelData[i]));
     189             : 
     190           0 :     float filterStageGain = parameterValue(ParamFilterStageGain);
     191           0 :     float filterStageRatio = parameterValue(ParamFilterStageRatio);
     192           0 :     float anchor = parameterValue(ParamFilterAnchor);
     193             : 
     194           0 :     if (filterStageGain != m_lastFilterStageGain || filterStageRatio != m_lastFilterStageRatio || anchor != m_lastAnchor) {
     195           0 :         m_lastFilterStageGain = filterStageGain;
     196           0 :         m_lastFilterStageRatio = filterStageRatio;
     197           0 :         m_lastAnchor = anchor;
     198             : 
     199           0 :         setEmphasisParameters(filterStageGain, anchor, filterStageRatio);
     200             :     }
     201             : 
     202             :     float sourceWithVolume[WEBAUDIO_BLOCK_SIZE + 4];
     203           0 :     float* alignedSourceWithVolume = ALIGNED16(sourceWithVolume);
     204           0 :     ASSERT_ALIGNED16(alignedSourceWithVolume);
     205             : 
     206             :     // Apply pre-emphasis filter.
     207             :     // Note that the final three stages are computed in-place in the destination buffer.
     208           0 :     for (unsigned i = 0; i < numberOfChannels; ++i) {
     209             :         const float* sourceData;
     210           0 :         if (sourceChunk->mVolume == 1.0f) {
     211             :           // Fast path, the volume scale doesn't need to get taken into account
     212           0 :           sourceData = m_sourceChannels[i];
     213             :         } else {
     214           0 :           AudioBlockCopyChannelWithScale(m_sourceChannels[i],
     215           0 :                                          sourceChunk->mVolume,
     216           0 :                                          alignedSourceWithVolume);
     217           0 :           sourceData = alignedSourceWithVolume;
     218             :         }
     219             : 
     220           0 :         float* destinationData = m_destinationChannels[i];
     221           0 :         ZeroPole* preFilters = m_preFilterPacks[i]->filters;
     222             : 
     223           0 :         preFilters[0].process(sourceData, destinationData, framesToProcess);
     224           0 :         preFilters[1].process(destinationData, destinationData, framesToProcess);
     225           0 :         preFilters[2].process(destinationData, destinationData, framesToProcess);
     226           0 :         preFilters[3].process(destinationData, destinationData, framesToProcess);
     227             :     }
     228             : 
     229           0 :     float dbThreshold = parameterValue(ParamThreshold);
     230           0 :     float dbKnee = parameterValue(ParamKnee);
     231           0 :     float ratio = parameterValue(ParamRatio);
     232           0 :     float attackTime = parameterValue(ParamAttack);
     233           0 :     float releaseTime = parameterValue(ParamRelease);
     234           0 :     float preDelayTime = parameterValue(ParamPreDelay);
     235             : 
     236             :     // This is effectively a master volume on the compressed signal (pre-blending).
     237           0 :     float dbPostGain = parameterValue(ParamPostGain);
     238             : 
     239             :     // Linear blending value from dry to completely processed (0 -> 1)
     240             :     // 0 means the signal is completely unprocessed.
     241             :     // 1 mixes in only the compressed signal.
     242           0 :     float effectBlend = parameterValue(ParamEffectBlend);
     243             : 
     244           0 :     float releaseZone1 = parameterValue(ParamReleaseZone1);
     245           0 :     float releaseZone2 = parameterValue(ParamReleaseZone2);
     246           0 :     float releaseZone3 = parameterValue(ParamReleaseZone3);
     247           0 :     float releaseZone4 = parameterValue(ParamReleaseZone4);
     248             : 
     249             :     // Apply compression to the pre-filtered signal.
     250             :     // The processing is performed in place.
     251           0 :     m_compressor.process(m_destinationChannels.get(),
     252             :                          m_destinationChannels.get(),
     253             :                          numberOfChannels,
     254             :                          framesToProcess,
     255             : 
     256             :                          dbThreshold,
     257             :                          dbKnee,
     258             :                          ratio,
     259             :                          attackTime,
     260             :                          releaseTime,
     261             :                          preDelayTime,
     262             :                          dbPostGain,
     263             :                          effectBlend,
     264             : 
     265             :                          releaseZone1,
     266             :                          releaseZone2,
     267             :                          releaseZone3,
     268             :                          releaseZone4
     269           0 :                          );
     270             : 
     271             :     // Update the compression amount.
     272           0 :     setParameterValue(ParamReduction, m_compressor.meteringGain());
     273             : 
     274             :     // Apply de-emphasis filter.
     275           0 :     for (unsigned i = 0; i < numberOfChannels; ++i) {
     276           0 :         float* destinationData = m_destinationChannels[i];
     277           0 :         ZeroPole* postFilters = m_postFilterPacks[i]->filters;
     278             : 
     279           0 :         postFilters[0].process(destinationData, destinationData, framesToProcess);
     280           0 :         postFilters[1].process(destinationData, destinationData, framesToProcess);
     281           0 :         postFilters[2].process(destinationData, destinationData, framesToProcess);
     282           0 :         postFilters[3].process(destinationData, destinationData, framesToProcess);
     283             :     }
     284             : }
     285             : 
     286           0 : void DynamicsCompressor::reset()
     287             : {
     288           0 :     m_lastFilterStageRatio = -1; // for recalc
     289           0 :     m_lastAnchor = -1;
     290           0 :     m_lastFilterStageGain = -1;
     291             : 
     292           0 :     for (unsigned channel = 0; channel < m_numberOfChannels; ++channel) {
     293           0 :         for (unsigned stageIndex = 0; stageIndex < 4; ++stageIndex) {
     294           0 :             m_preFilterPacks[channel]->filters[stageIndex].reset();
     295           0 :             m_postFilterPacks[channel]->filters[stageIndex].reset();
     296             :         }
     297             :     }
     298             : 
     299           0 :     m_compressor.reset();
     300           0 : }
     301             : 
     302           0 : void DynamicsCompressor::setNumberOfChannels(unsigned numberOfChannels)
     303             : {
     304           0 :     if (m_preFilterPacks.Length() == numberOfChannels)
     305           0 :         return;
     306             : 
     307           0 :     m_preFilterPacks.Clear();
     308           0 :     m_postFilterPacks.Clear();
     309           0 :     for (unsigned i = 0; i < numberOfChannels; ++i) {
     310           0 :         m_preFilterPacks.AppendElement(new ZeroPoleFilterPack4());
     311           0 :         m_postFilterPacks.AppendElement(new ZeroPoleFilterPack4());
     312             :     }
     313             : 
     314           0 :     m_sourceChannels = mozilla::MakeUnique<const float* []>(numberOfChannels);
     315           0 :     m_destinationChannels = mozilla::MakeUnique<float* []>(numberOfChannels);
     316             : 
     317           0 :     m_compressor.setNumberOfChannels(numberOfChannels);
     318           0 :     m_numberOfChannels = numberOfChannels;
     319             : }
     320             : 
     321             : } // namespace WebCore

Generated by: LCOV version 1.13