content/media/webaudio/blink/DynamicsCompressor.cpp

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

mercurial