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