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