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 "DynamicsCompressor.h"
34
35#include "AudioBus.h"
36#include "AudioUtilities.h"
37#include <wtf/MathExtras.h>
38
39namespace WebCore {
40
41using namespace AudioUtilities;
42
43DynamicsCompressor::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
57void DynamicsCompressor::setParameterValue(unsigned parameterID, float value)
58{
59    ASSERT(parameterID < ParamLast);
60    if (parameterID < ParamLast)
61        m_parameters[parameterID] = value;
62}
63
64void 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
92float DynamicsCompressor::parameterValue(unsigned parameterID)
93{
94    ASSERT(parameterID < ParamLast);
95    return m_parameters[parameterID];
96}
97
98void 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
122void 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
130void 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
249void 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
265void 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