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