1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/blink/DynamicsCompressor.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,318 @@ 1.4 +/* 1.5 + * Copyright (C) 2011 Google Inc. All rights reserved. 1.6 + * 1.7 + * Redistribution and use in source and binary forms, with or without 1.8 + * modification, are permitted provided that the following conditions 1.9 + * are met: 1.10 + * 1.11 + * 1. Redistributions of source code must retain the above copyright 1.12 + * notice, this list of conditions and the following disclaimer. 1.13 + * 2. Redistributions in binary form must reproduce the above copyright 1.14 + * notice, this list of conditions and the following disclaimer in the 1.15 + * documentation and/or other materials provided with the distribution. 1.16 + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 1.17 + * its contributors may be used to endorse or promote products derived 1.18 + * from this software without specific prior written permission. 1.19 + * 1.20 + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 1.21 + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 1.22 + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 1.23 + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 1.24 + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 1.25 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 1.26 + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 1.27 + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 1.28 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 1.29 + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.30 + */ 1.31 + 1.32 +#include "DynamicsCompressor.h" 1.33 +#include "AudioSegment.h" 1.34 + 1.35 +#include <cmath> 1.36 +#include "AudioNodeEngine.h" 1.37 +#include "nsDebug.h" 1.38 + 1.39 +using mozilla::WEBAUDIO_BLOCK_SIZE; 1.40 +using mozilla::AudioBlockCopyChannelWithScale; 1.41 + 1.42 +namespace WebCore { 1.43 + 1.44 +DynamicsCompressor::DynamicsCompressor(float sampleRate, unsigned numberOfChannels) 1.45 + : m_numberOfChannels(numberOfChannels) 1.46 + , m_sampleRate(sampleRate) 1.47 + , m_compressor(sampleRate, numberOfChannels) 1.48 +{ 1.49 + // Uninitialized state - for parameter recalculation. 1.50 + m_lastFilterStageRatio = -1; 1.51 + m_lastAnchor = -1; 1.52 + m_lastFilterStageGain = -1; 1.53 + 1.54 + setNumberOfChannels(numberOfChannels); 1.55 + initializeParameters(); 1.56 +} 1.57 + 1.58 +size_t DynamicsCompressor::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const 1.59 +{ 1.60 + size_t amount = aMallocSizeOf(this); 1.61 + amount += m_preFilterPacks.SizeOfExcludingThis(aMallocSizeOf); 1.62 + for (size_t i = 0; i < m_preFilterPacks.Length(); i++) { 1.63 + if (m_preFilterPacks[i]) { 1.64 + amount += m_preFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf); 1.65 + } 1.66 + } 1.67 + 1.68 + amount += m_postFilterPacks.SizeOfExcludingThis(aMallocSizeOf); 1.69 + for (size_t i = 0; i < m_postFilterPacks.Length(); i++) { 1.70 + if (m_postFilterPacks[i]) { 1.71 + amount += m_postFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf); 1.72 + } 1.73 + } 1.74 + 1.75 + amount += m_sourceChannels.SizeOfExcludingThis(aMallocSizeOf); 1.76 + amount += m_destinationChannels.SizeOfExcludingThis(aMallocSizeOf); 1.77 + amount += m_compressor.sizeOfExcludingThis(aMallocSizeOf); 1.78 + return amount; 1.79 +} 1.80 + 1.81 +void DynamicsCompressor::setParameterValue(unsigned parameterID, float value) 1.82 +{ 1.83 + MOZ_ASSERT(parameterID < ParamLast); 1.84 + if (parameterID < ParamLast) 1.85 + m_parameters[parameterID] = value; 1.86 +} 1.87 + 1.88 +void DynamicsCompressor::initializeParameters() 1.89 +{ 1.90 + // Initializes compressor to default values. 1.91 + 1.92 + m_parameters[ParamThreshold] = -24; // dB 1.93 + m_parameters[ParamKnee] = 30; // dB 1.94 + m_parameters[ParamRatio] = 12; // unit-less 1.95 + m_parameters[ParamAttack] = 0.003f; // seconds 1.96 + m_parameters[ParamRelease] = 0.250f; // seconds 1.97 + m_parameters[ParamPreDelay] = 0.006f; // seconds 1.98 + 1.99 + // Release zone values 0 -> 1. 1.100 + m_parameters[ParamReleaseZone1] = 0.09f; 1.101 + m_parameters[ParamReleaseZone2] = 0.16f; 1.102 + m_parameters[ParamReleaseZone3] = 0.42f; 1.103 + m_parameters[ParamReleaseZone4] = 0.98f; 1.104 + 1.105 + m_parameters[ParamFilterStageGain] = 4.4f; // dB 1.106 + m_parameters[ParamFilterStageRatio] = 2; 1.107 + m_parameters[ParamFilterAnchor] = 15000 / nyquist(); 1.108 + 1.109 + m_parameters[ParamPostGain] = 0; // dB 1.110 + m_parameters[ParamReduction] = 0; // dB 1.111 + 1.112 + // Linear crossfade (0 -> 1). 1.113 + m_parameters[ParamEffectBlend] = 1; 1.114 +} 1.115 + 1.116 +float DynamicsCompressor::parameterValue(unsigned parameterID) 1.117 +{ 1.118 + MOZ_ASSERT(parameterID < ParamLast); 1.119 + return m_parameters[parameterID]; 1.120 +} 1.121 + 1.122 +void DynamicsCompressor::setEmphasisStageParameters(unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */) 1.123 +{ 1.124 + float gk = 1 - gain / 20; 1.125 + float f1 = normalizedFrequency * gk; 1.126 + float f2 = normalizedFrequency / gk; 1.127 + float r1 = expf(-f1 * M_PI); 1.128 + float r2 = expf(-f2 * M_PI); 1.129 + 1.130 + MOZ_ASSERT(m_numberOfChannels == m_preFilterPacks.Length()); 1.131 + 1.132 + for (unsigned i = 0; i < m_numberOfChannels; ++i) { 1.133 + // Set pre-filter zero and pole to create an emphasis filter. 1.134 + ZeroPole& preFilter = m_preFilterPacks[i]->filters[stageIndex]; 1.135 + preFilter.setZero(r1); 1.136 + preFilter.setPole(r2); 1.137 + 1.138 + // Set post-filter with zero and pole reversed to create the de-emphasis filter. 1.139 + // If there were no compressor kernel in between, they would cancel each other out (allpass filter). 1.140 + ZeroPole& postFilter = m_postFilterPacks[i]->filters[stageIndex]; 1.141 + postFilter.setZero(r2); 1.142 + postFilter.setPole(r1); 1.143 + } 1.144 +} 1.145 + 1.146 +void DynamicsCompressor::setEmphasisParameters(float gain, float anchorFreq, float filterStageRatio) 1.147 +{ 1.148 + setEmphasisStageParameters(0, gain, anchorFreq); 1.149 + setEmphasisStageParameters(1, gain, anchorFreq / filterStageRatio); 1.150 + setEmphasisStageParameters(2, gain, anchorFreq / (filterStageRatio * filterStageRatio)); 1.151 + setEmphasisStageParameters(3, gain, anchorFreq / (filterStageRatio * filterStageRatio * filterStageRatio)); 1.152 +} 1.153 + 1.154 +void DynamicsCompressor::process(const AudioChunk* sourceChunk, AudioChunk* destinationChunk, unsigned framesToProcess) 1.155 +{ 1.156 + // Though numberOfChannels is retrived from destinationBus, we still name it numberOfChannels instead of numberOfDestinationChannels. 1.157 + // It's because we internally match sourceChannels's size to destinationBus by channel up/down mix. Thus we need numberOfChannels 1.158 + // to do the loop work for both m_sourceChannels and m_destinationChannels. 1.159 + 1.160 + unsigned numberOfChannels = destinationChunk->mChannelData.Length(); 1.161 + unsigned numberOfSourceChannels = sourceChunk->mChannelData.Length(); 1.162 + 1.163 + MOZ_ASSERT(numberOfChannels == m_numberOfChannels && numberOfSourceChannels); 1.164 + 1.165 + if (numberOfChannels != m_numberOfChannels || !numberOfSourceChannels) { 1.166 + destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE); 1.167 + return; 1.168 + } 1.169 + 1.170 + switch (numberOfChannels) { 1.171 + case 2: // stereo 1.172 + m_sourceChannels[0] = static_cast<const float*>(sourceChunk->mChannelData[0]); 1.173 + 1.174 + if (numberOfSourceChannels > 1) 1.175 + m_sourceChannels[1] = static_cast<const float*>(sourceChunk->mChannelData[1]); 1.176 + else 1.177 + // Simply duplicate mono channel input data to right channel for stereo processing. 1.178 + m_sourceChannels[1] = m_sourceChannels[0]; 1.179 + 1.180 + break; 1.181 + default: 1.182 + // FIXME : support other number of channels. 1.183 + NS_WARNING("Support other number of channels"); 1.184 + destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE); 1.185 + return; 1.186 + } 1.187 + 1.188 + for (unsigned i = 0; i < numberOfChannels; ++i) 1.189 + m_destinationChannels[i] = const_cast<float*>(static_cast<const float*>( 1.190 + destinationChunk->mChannelData[i])); 1.191 + 1.192 + float filterStageGain = parameterValue(ParamFilterStageGain); 1.193 + float filterStageRatio = parameterValue(ParamFilterStageRatio); 1.194 + float anchor = parameterValue(ParamFilterAnchor); 1.195 + 1.196 + if (filterStageGain != m_lastFilterStageGain || filterStageRatio != m_lastFilterStageRatio || anchor != m_lastAnchor) { 1.197 + m_lastFilterStageGain = filterStageGain; 1.198 + m_lastFilterStageRatio = filterStageRatio; 1.199 + m_lastAnchor = anchor; 1.200 + 1.201 + setEmphasisParameters(filterStageGain, anchor, filterStageRatio); 1.202 + } 1.203 + 1.204 + float sourceWithVolume[WEBAUDIO_BLOCK_SIZE]; 1.205 + 1.206 + // Apply pre-emphasis filter. 1.207 + // Note that the final three stages are computed in-place in the destination buffer. 1.208 + for (unsigned i = 0; i < numberOfChannels; ++i) { 1.209 + const float* sourceData; 1.210 + if (sourceChunk->mVolume == 1.0f) { 1.211 + // Fast path, the volume scale doesn't need to get taken into account 1.212 + sourceData = m_sourceChannels[i]; 1.213 + } else { 1.214 + AudioBlockCopyChannelWithScale(m_sourceChannels[i], 1.215 + sourceChunk->mVolume, 1.216 + sourceWithVolume); 1.217 + sourceData = sourceWithVolume; 1.218 + } 1.219 + 1.220 + float* destinationData = m_destinationChannels[i]; 1.221 + ZeroPole* preFilters = m_preFilterPacks[i]->filters; 1.222 + 1.223 + preFilters[0].process(sourceData, destinationData, framesToProcess); 1.224 + preFilters[1].process(destinationData, destinationData, framesToProcess); 1.225 + preFilters[2].process(destinationData, destinationData, framesToProcess); 1.226 + preFilters[3].process(destinationData, destinationData, framesToProcess); 1.227 + } 1.228 + 1.229 + float dbThreshold = parameterValue(ParamThreshold); 1.230 + float dbKnee = parameterValue(ParamKnee); 1.231 + float ratio = parameterValue(ParamRatio); 1.232 + float attackTime = parameterValue(ParamAttack); 1.233 + float releaseTime = parameterValue(ParamRelease); 1.234 + float preDelayTime = parameterValue(ParamPreDelay); 1.235 + 1.236 + // This is effectively a master volume on the compressed signal (pre-blending). 1.237 + float dbPostGain = parameterValue(ParamPostGain); 1.238 + 1.239 + // Linear blending value from dry to completely processed (0 -> 1) 1.240 + // 0 means the signal is completely unprocessed. 1.241 + // 1 mixes in only the compressed signal. 1.242 + float effectBlend = parameterValue(ParamEffectBlend); 1.243 + 1.244 + float releaseZone1 = parameterValue(ParamReleaseZone1); 1.245 + float releaseZone2 = parameterValue(ParamReleaseZone2); 1.246 + float releaseZone3 = parameterValue(ParamReleaseZone3); 1.247 + float releaseZone4 = parameterValue(ParamReleaseZone4); 1.248 + 1.249 + // Apply compression to the pre-filtered signal. 1.250 + // The processing is performed in place. 1.251 + m_compressor.process(m_destinationChannels.get(), 1.252 + m_destinationChannels.get(), 1.253 + numberOfChannels, 1.254 + framesToProcess, 1.255 + 1.256 + dbThreshold, 1.257 + dbKnee, 1.258 + ratio, 1.259 + attackTime, 1.260 + releaseTime, 1.261 + preDelayTime, 1.262 + dbPostGain, 1.263 + effectBlend, 1.264 + 1.265 + releaseZone1, 1.266 + releaseZone2, 1.267 + releaseZone3, 1.268 + releaseZone4 1.269 + ); 1.270 + 1.271 + // Update the compression amount. 1.272 + setParameterValue(ParamReduction, m_compressor.meteringGain()); 1.273 + 1.274 + // Apply de-emphasis filter. 1.275 + for (unsigned i = 0; i < numberOfChannels; ++i) { 1.276 + float* destinationData = m_destinationChannels[i]; 1.277 + ZeroPole* postFilters = m_postFilterPacks[i]->filters; 1.278 + 1.279 + postFilters[0].process(destinationData, destinationData, framesToProcess); 1.280 + postFilters[1].process(destinationData, destinationData, framesToProcess); 1.281 + postFilters[2].process(destinationData, destinationData, framesToProcess); 1.282 + postFilters[3].process(destinationData, destinationData, framesToProcess); 1.283 + } 1.284 +} 1.285 + 1.286 +void DynamicsCompressor::reset() 1.287 +{ 1.288 + m_lastFilterStageRatio = -1; // for recalc 1.289 + m_lastAnchor = -1; 1.290 + m_lastFilterStageGain = -1; 1.291 + 1.292 + for (unsigned channel = 0; channel < m_numberOfChannels; ++channel) { 1.293 + for (unsigned stageIndex = 0; stageIndex < 4; ++stageIndex) { 1.294 + m_preFilterPacks[channel]->filters[stageIndex].reset(); 1.295 + m_postFilterPacks[channel]->filters[stageIndex].reset(); 1.296 + } 1.297 + } 1.298 + 1.299 + m_compressor.reset(); 1.300 +} 1.301 + 1.302 +void DynamicsCompressor::setNumberOfChannels(unsigned numberOfChannels) 1.303 +{ 1.304 + if (m_preFilterPacks.Length() == numberOfChannels) 1.305 + return; 1.306 + 1.307 + m_preFilterPacks.Clear(); 1.308 + m_postFilterPacks.Clear(); 1.309 + for (unsigned i = 0; i < numberOfChannels; ++i) { 1.310 + m_preFilterPacks.AppendElement(new ZeroPoleFilterPack4()); 1.311 + m_postFilterPacks.AppendElement(new ZeroPoleFilterPack4()); 1.312 + } 1.313 + 1.314 + m_sourceChannels = new const float* [numberOfChannels]; 1.315 + m_destinationChannels = new float* [numberOfChannels]; 1.316 + 1.317 + m_compressor.setNumberOfChannels(numberOfChannels); 1.318 + m_numberOfChannels = numberOfChannels; 1.319 +} 1.320 + 1.321 +} // namespace WebCore