Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/webaudio/blink/DynamicsCompressor.cpp
Line
Count
Source (jump to first uncovered line)
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
DynamicsCompressor::DynamicsCompressor(float sampleRate, unsigned numberOfChannels)
43
    : m_numberOfChannels(numberOfChannels)
44
    , m_sampleRate(sampleRate)
45
    , m_compressor(sampleRate, numberOfChannels)
46
0
{
47
0
    // Uninitialized state - for parameter recalculation.
48
0
    m_lastFilterStageRatio = -1;
49
0
    m_lastAnchor = -1;
50
0
    m_lastFilterStageGain = -1;
51
0
52
0
    setNumberOfChannels(numberOfChannels);
53
0
    initializeParameters();
54
0
}
55
56
size_t DynamicsCompressor::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
57
0
{
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
0
        }
64
0
    }
65
0
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
0
        }
71
0
    }
72
0
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
0
}
78
79
void DynamicsCompressor::setParameterValue(unsigned parameterID, float value)
80
0
{
81
0
    MOZ_ASSERT(parameterID < ParamLast);
82
0
    if (parameterID < ParamLast)
83
0
        m_parameters[parameterID] = value;
84
0
}
85
86
void DynamicsCompressor::initializeParameters()
87
0
{
88
0
    // Initializes compressor to default values.
89
0
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
0
97
0
    // 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
0
103
0
    m_parameters[ParamFilterStageGain] = 4.4f; // dB
104
0
    m_parameters[ParamFilterStageRatio] = 2;
105
0
    m_parameters[ParamFilterAnchor] = 15000 / nyquist();
106
0
107
0
    m_parameters[ParamPostGain] = 0; // dB
108
0
    m_parameters[ParamReduction] = 0; // dB
109
0
110
0
    // Linear crossfade (0 -> 1).
111
0
    m_parameters[ParamEffectBlend] = 1;
112
0
}
113
114
float DynamicsCompressor::parameterValue(unsigned parameterID)
115
0
{
116
0
    MOZ_ASSERT(parameterID < ParamLast);
117
0
    return m_parameters[parameterID];
118
0
}
119
120
void DynamicsCompressor::setEmphasisStageParameters(unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */)
121
0
{
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
0
128
0
    MOZ_ASSERT(m_numberOfChannels == m_preFilterPacks.Length());
129
0
130
0
    for (unsigned i = 0; i < m_numberOfChannels; ++i) {
131
0
        // 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
0
136
0
        // Set post-filter with zero and pole reversed to create the de-emphasis filter.
137
0
        // 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
0
    }
142
0
}
143
144
void DynamicsCompressor::setEmphasisParameters(float gain, float anchorFreq, float filterStageRatio)
145
0
{
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
void DynamicsCompressor::process(const AudioBlock* sourceChunk, AudioBlock* destinationChunk, unsigned framesToProcess)
153
0
{
154
0
    // Though numberOfChannels is retrived from destinationBus, we still name it numberOfChannels instead of numberOfDestinationChannels.
155
0
    // It's because we internally match sourceChannels's size to destinationBus by channel up/down mix. Thus we need numberOfChannels
156
0
    // to do the loop work for both m_sourceChannels and m_destinationChannels.
157
0
158
0
    unsigned numberOfChannels = destinationChunk->ChannelCount();
159
0
    unsigned numberOfSourceChannels = sourceChunk->ChannelCount();
160
0
161
0
    MOZ_ASSERT(numberOfChannels == m_numberOfChannels && numberOfSourceChannels);
162
0
163
0
    if (numberOfChannels != m_numberOfChannels || !numberOfSourceChannels) {
164
0
        destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
165
0
        return;
166
0
    }
167
0
168
0
    switch (numberOfChannels) {
169
0
    case 2: // stereo
170
0
        m_sourceChannels[0] = static_cast<const float*>(sourceChunk->mChannelData[0]);
171
0
172
0
        if (numberOfSourceChannels > 1)
173
0
            m_sourceChannels[1] = static_cast<const float*>(sourceChunk->mChannelData[1]);
174
0
        else
175
0
            // Simply duplicate mono channel input data to right channel for stereo processing.
176
0
            m_sourceChannels[1] = m_sourceChannels[0];
177
0
178
0
        break;
179
0
    default:
180
0
        // 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
0
    }
185
0
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
0
190
0
    float filterStageGain = parameterValue(ParamFilterStageGain);
191
0
    float filterStageRatio = parameterValue(ParamFilterStageRatio);
192
0
    float anchor = parameterValue(ParamFilterAnchor);
193
0
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
0
199
0
        setEmphasisParameters(filterStageGain, anchor, filterStageRatio);
200
0
    }
201
0
202
0
    float sourceWithVolume[WEBAUDIO_BLOCK_SIZE + 4];
203
0
    float* alignedSourceWithVolume = ALIGNED16(sourceWithVolume);
204
0
    ASSERT_ALIGNED16(alignedSourceWithVolume);
205
0
206
0
    // Apply pre-emphasis filter.
207
0
    // Note that the final three stages are computed in-place in the destination buffer.
208
0
    for (unsigned i = 0; i < numberOfChannels; ++i) {
209
0
        const float* sourceData;
210
0
        if (sourceChunk->mVolume == 1.0f) {
211
0
          // Fast path, the volume scale doesn't need to get taken into account
212
0
          sourceData = m_sourceChannels[i];
213
0
        } else {
214
0
          AudioBlockCopyChannelWithScale(m_sourceChannels[i],
215
0
                                         sourceChunk->mVolume,
216
0
                                         alignedSourceWithVolume);
217
0
          sourceData = alignedSourceWithVolume;
218
0
        }
219
0
220
0
        float* destinationData = m_destinationChannels[i];
221
0
        ZeroPole* preFilters = m_preFilterPacks[i]->filters;
222
0
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
0
    }
228
0
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
0
236
0
    // This is effectively a master volume on the compressed signal (pre-blending).
237
0
    float dbPostGain = parameterValue(ParamPostGain);
238
0
239
0
    // Linear blending value from dry to completely processed (0 -> 1)
240
0
    // 0 means the signal is completely unprocessed.
241
0
    // 1 mixes in only the compressed signal.
242
0
    float effectBlend = parameterValue(ParamEffectBlend);
243
0
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
0
249
0
    // Apply compression to the pre-filtered signal.
250
0
    // The processing is performed in place.
251
0
    m_compressor.process(m_destinationChannels.get(),
252
0
                         m_destinationChannels.get(),
253
0
                         numberOfChannels,
254
0
                         framesToProcess,
255
0
256
0
                         dbThreshold,
257
0
                         dbKnee,
258
0
                         ratio,
259
0
                         attackTime,
260
0
                         releaseTime,
261
0
                         preDelayTime,
262
0
                         dbPostGain,
263
0
                         effectBlend,
264
0
265
0
                         releaseZone1,
266
0
                         releaseZone2,
267
0
                         releaseZone3,
268
0
                         releaseZone4
269
0
                         );
270
0
271
0
    // Update the compression amount.
272
0
    setParameterValue(ParamReduction, m_compressor.meteringGain());
273
0
274
0
    // 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
0
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
0
    }
284
0
}
285
286
void DynamicsCompressor::reset()
287
0
{
288
0
    m_lastFilterStageRatio = -1; // for recalc
289
0
    m_lastAnchor = -1;
290
0
    m_lastFilterStageGain = -1;
291
0
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
0
        }
297
0
    }
298
0
299
0
    m_compressor.reset();
300
0
}
301
302
void DynamicsCompressor::setNumberOfChannels(unsigned numberOfChannels)
303
0
{
304
0
    if (m_preFilterPacks.Length() == numberOfChannels)
305
0
        return;
306
0
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
0
    }
313
0
314
0
    m_sourceChannels = mozilla::MakeUnique<const float* []>(numberOfChannels);
315
0
    m_destinationChannels = mozilla::MakeUnique<float* []>(numberOfChannels);
316
0
317
0
    m_compressor.setNumberOfChannels(numberOfChannels);
318
0
    m_numberOfChannels = numberOfChannels;
319
0
}
320
321
} // namespace WebCore