/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 |