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