mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Bug 864091 - Part 1: Import the Dynamics Compressor implementation from Blink; r=padenot
The original code was copied from Blink SVN revision 148720.
This commit is contained in:
parent
d4f13dfcf1
commit
6d48241e2c
123
content/media/webaudio/blink/DenormalDisabler.h
Normal file
123
content/media/webaudio/blink/DenormalDisabler.h
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2011, Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef DenormalDisabler_h
|
||||
#define DenormalDisabler_h
|
||||
|
||||
#include <wtf/MathExtras.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
// Deal with denormals. They can very seriously impact performance on x86.
|
||||
|
||||
// Define HAVE_DENORMAL if we support flushing denormals to zero.
|
||||
#if OS(WINDOWS) && COMPILER(MSVC)
|
||||
#define HAVE_DENORMAL
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
#define HAVE_DENORMAL
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DENORMAL
|
||||
class DenormalDisabler {
|
||||
public:
|
||||
DenormalDisabler()
|
||||
: m_savedCSR(0)
|
||||
{
|
||||
#if OS(WINDOWS) && COMPILER(MSVC)
|
||||
// Save the current state, and set mode to flush denormals.
|
||||
//
|
||||
// http://stackoverflow.com/questions/637175/possible-bug-in-controlfp-s-may-not-restore-control-word-correctly
|
||||
_controlfp_s(&m_savedCSR, 0, 0);
|
||||
unsigned int unused;
|
||||
_controlfp_s(&unused, _DN_FLUSH, _MCW_DN);
|
||||
#else
|
||||
m_savedCSR = getCSR();
|
||||
setCSR(m_savedCSR | 0x8040);
|
||||
#endif
|
||||
}
|
||||
|
||||
~DenormalDisabler()
|
||||
{
|
||||
#if OS(WINDOWS) && COMPILER(MSVC)
|
||||
unsigned int unused;
|
||||
_controlfp_s(&unused, m_savedCSR, _MCW_DN);
|
||||
#else
|
||||
setCSR(m_savedCSR);
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is a nop if we can flush denormals to zero in hardware.
|
||||
static inline float flushDenormalFloatToZero(float f)
|
||||
{
|
||||
#if OS(WINDOWS) && COMPILER(MSVC) && (!_M_IX86_FP)
|
||||
// For systems using x87 instead of sse, there's no hardware support
|
||||
// to flush denormals automatically. Hence, we need to flush
|
||||
// denormals to zero manually.
|
||||
return (fabs(f) < FLT_MIN) ? 0.0f : f;
|
||||
#else
|
||||
return f;
|
||||
#endif
|
||||
}
|
||||
private:
|
||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
inline int getCSR()
|
||||
{
|
||||
int result;
|
||||
asm volatile("stmxcsr %0" : "=m" (result));
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void setCSR(int a)
|
||||
{
|
||||
int temp = a;
|
||||
asm volatile("ldmxcsr %0" : : "m" (temp));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
unsigned int m_savedCSR;
|
||||
};
|
||||
|
||||
#else
|
||||
// FIXME: add implementations for other architectures and compilers
|
||||
class DenormalDisabler {
|
||||
public:
|
||||
DenormalDisabler() { }
|
||||
|
||||
// Assume the worst case that other architectures and compilers
|
||||
// need to flush denormals to zero manually.
|
||||
static inline float flushDenormalFloatToZero(float f)
|
||||
{
|
||||
return (fabs(f) < FLT_MIN) ? 0.0f : f;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // WebCore
|
||||
|
||||
#undef HAVE_DENORMAL
|
||||
#endif // DenormalDisabler_h
|
286
content/media/webaudio/blink/DynamicsCompressor.cpp
Normal file
286
content/media/webaudio/blink/DynamicsCompressor.cpp
Normal file
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if ENABLE(WEB_AUDIO)
|
||||
|
||||
#include "DynamicsCompressor.h"
|
||||
|
||||
#include "AudioBus.h"
|
||||
#include "AudioUtilities.h"
|
||||
#include <wtf/MathExtras.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
using namespace AudioUtilities;
|
||||
|
||||
DynamicsCompressor::DynamicsCompressor(float sampleRate, unsigned numberOfChannels)
|
||||
: m_numberOfChannels(numberOfChannels)
|
||||
, m_sampleRate(sampleRate)
|
||||
, m_compressor(sampleRate, numberOfChannels)
|
||||
{
|
||||
// Uninitialized state - for parameter recalculation.
|
||||
m_lastFilterStageRatio = -1;
|
||||
m_lastAnchor = -1;
|
||||
m_lastFilterStageGain = -1;
|
||||
|
||||
setNumberOfChannels(numberOfChannels);
|
||||
initializeParameters();
|
||||
}
|
||||
|
||||
void DynamicsCompressor::setParameterValue(unsigned parameterID, float value)
|
||||
{
|
||||
ASSERT(parameterID < ParamLast);
|
||||
if (parameterID < ParamLast)
|
||||
m_parameters[parameterID] = value;
|
||||
}
|
||||
|
||||
void DynamicsCompressor::initializeParameters()
|
||||
{
|
||||
// Initializes compressor to default values.
|
||||
|
||||
m_parameters[ParamThreshold] = -24; // dB
|
||||
m_parameters[ParamKnee] = 30; // dB
|
||||
m_parameters[ParamRatio] = 12; // unit-less
|
||||
m_parameters[ParamAttack] = 0.003f; // seconds
|
||||
m_parameters[ParamRelease] = 0.250f; // seconds
|
||||
m_parameters[ParamPreDelay] = 0.006f; // seconds
|
||||
|
||||
// Release zone values 0 -> 1.
|
||||
m_parameters[ParamReleaseZone1] = 0.09f;
|
||||
m_parameters[ParamReleaseZone2] = 0.16f;
|
||||
m_parameters[ParamReleaseZone3] = 0.42f;
|
||||
m_parameters[ParamReleaseZone4] = 0.98f;
|
||||
|
||||
m_parameters[ParamFilterStageGain] = 4.4f; // dB
|
||||
m_parameters[ParamFilterStageRatio] = 2;
|
||||
m_parameters[ParamFilterAnchor] = 15000 / nyquist();
|
||||
|
||||
m_parameters[ParamPostGain] = 0; // dB
|
||||
m_parameters[ParamReduction] = 0; // dB
|
||||
|
||||
// Linear crossfade (0 -> 1).
|
||||
m_parameters[ParamEffectBlend] = 1;
|
||||
}
|
||||
|
||||
float DynamicsCompressor::parameterValue(unsigned parameterID)
|
||||
{
|
||||
ASSERT(parameterID < ParamLast);
|
||||
return m_parameters[parameterID];
|
||||
}
|
||||
|
||||
void DynamicsCompressor::setEmphasisStageParameters(unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */)
|
||||
{
|
||||
float gk = 1 - gain / 20;
|
||||
float f1 = normalizedFrequency * gk;
|
||||
float f2 = normalizedFrequency / gk;
|
||||
float r1 = expf(-f1 * piFloat);
|
||||
float r2 = expf(-f2 * piFloat);
|
||||
|
||||
ASSERT(m_numberOfChannels == m_preFilterPacks.size());
|
||||
|
||||
for (unsigned i = 0; i < m_numberOfChannels; ++i) {
|
||||
// Set pre-filter zero and pole to create an emphasis filter.
|
||||
ZeroPole& preFilter = m_preFilterPacks[i]->filters[stageIndex];
|
||||
preFilter.setZero(r1);
|
||||
preFilter.setPole(r2);
|
||||
|
||||
// Set post-filter with zero and pole reversed to create the de-emphasis filter.
|
||||
// If there were no compressor kernel in between, they would cancel each other out (allpass filter).
|
||||
ZeroPole& postFilter = m_postFilterPacks[i]->filters[stageIndex];
|
||||
postFilter.setZero(r2);
|
||||
postFilter.setPole(r1);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicsCompressor::setEmphasisParameters(float gain, float anchorFreq, float filterStageRatio)
|
||||
{
|
||||
setEmphasisStageParameters(0, gain, anchorFreq);
|
||||
setEmphasisStageParameters(1, gain, anchorFreq / filterStageRatio);
|
||||
setEmphasisStageParameters(2, gain, anchorFreq / (filterStageRatio * filterStageRatio));
|
||||
setEmphasisStageParameters(3, gain, anchorFreq / (filterStageRatio * filterStageRatio * filterStageRatio));
|
||||
}
|
||||
|
||||
void DynamicsCompressor::process(const AudioBus* sourceBus, AudioBus* destinationBus, unsigned framesToProcess)
|
||||
{
|
||||
// Though numberOfChannels is retrived from destinationBus, we still name it numberOfChannels instead of numberOfDestinationChannels.
|
||||
// It's because we internally match sourceChannels's size to destinationBus by channel up/down mix. Thus we need numberOfChannels
|
||||
// to do the loop work for both m_sourceChannels and m_destinationChannels.
|
||||
|
||||
unsigned numberOfChannels = destinationBus->numberOfChannels();
|
||||
unsigned numberOfSourceChannels = sourceBus->numberOfChannels();
|
||||
|
||||
ASSERT(numberOfChannels == m_numberOfChannels && numberOfSourceChannels);
|
||||
|
||||
if (numberOfChannels != m_numberOfChannels || !numberOfSourceChannels) {
|
||||
destinationBus->zero();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (numberOfChannels) {
|
||||
case 2: // stereo
|
||||
m_sourceChannels[0] = sourceBus->channel(0)->data();
|
||||
|
||||
if (numberOfSourceChannels > 1)
|
||||
m_sourceChannels[1] = sourceBus->channel(1)->data();
|
||||
else
|
||||
// Simply duplicate mono channel input data to right channel for stereo processing.
|
||||
m_sourceChannels[1] = m_sourceChannels[0];
|
||||
|
||||
break;
|
||||
default:
|
||||
// FIXME : support other number of channels.
|
||||
ASSERT_NOT_REACHED();
|
||||
destinationBus->zero();
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i)
|
||||
m_destinationChannels[i] = destinationBus->channel(i)->mutableData();
|
||||
|
||||
float filterStageGain = parameterValue(ParamFilterStageGain);
|
||||
float filterStageRatio = parameterValue(ParamFilterStageRatio);
|
||||
float anchor = parameterValue(ParamFilterAnchor);
|
||||
|
||||
if (filterStageGain != m_lastFilterStageGain || filterStageRatio != m_lastFilterStageRatio || anchor != m_lastAnchor) {
|
||||
m_lastFilterStageGain = filterStageGain;
|
||||
m_lastFilterStageRatio = filterStageRatio;
|
||||
m_lastAnchor = anchor;
|
||||
|
||||
setEmphasisParameters(filterStageGain, anchor, filterStageRatio);
|
||||
}
|
||||
|
||||
// Apply pre-emphasis filter.
|
||||
// Note that the final three stages are computed in-place in the destination buffer.
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i) {
|
||||
const float* sourceData = m_sourceChannels[i];
|
||||
float* destinationData = m_destinationChannels[i];
|
||||
ZeroPole* preFilters = m_preFilterPacks[i]->filters;
|
||||
|
||||
preFilters[0].process(sourceData, destinationData, framesToProcess);
|
||||
preFilters[1].process(destinationData, destinationData, framesToProcess);
|
||||
preFilters[2].process(destinationData, destinationData, framesToProcess);
|
||||
preFilters[3].process(destinationData, destinationData, framesToProcess);
|
||||
}
|
||||
|
||||
float dbThreshold = parameterValue(ParamThreshold);
|
||||
float dbKnee = parameterValue(ParamKnee);
|
||||
float ratio = parameterValue(ParamRatio);
|
||||
float attackTime = parameterValue(ParamAttack);
|
||||
float releaseTime = parameterValue(ParamRelease);
|
||||
float preDelayTime = parameterValue(ParamPreDelay);
|
||||
|
||||
// This is effectively a master volume on the compressed signal (pre-blending).
|
||||
float dbPostGain = parameterValue(ParamPostGain);
|
||||
|
||||
// Linear blending value from dry to completely processed (0 -> 1)
|
||||
// 0 means the signal is completely unprocessed.
|
||||
// 1 mixes in only the compressed signal.
|
||||
float effectBlend = parameterValue(ParamEffectBlend);
|
||||
|
||||
float releaseZone1 = parameterValue(ParamReleaseZone1);
|
||||
float releaseZone2 = parameterValue(ParamReleaseZone2);
|
||||
float releaseZone3 = parameterValue(ParamReleaseZone3);
|
||||
float releaseZone4 = parameterValue(ParamReleaseZone4);
|
||||
|
||||
// Apply compression to the pre-filtered signal.
|
||||
// The processing is performed in place.
|
||||
m_compressor.process(m_destinationChannels.get(),
|
||||
m_destinationChannels.get(),
|
||||
numberOfChannels,
|
||||
framesToProcess,
|
||||
|
||||
dbThreshold,
|
||||
dbKnee,
|
||||
ratio,
|
||||
attackTime,
|
||||
releaseTime,
|
||||
preDelayTime,
|
||||
dbPostGain,
|
||||
effectBlend,
|
||||
|
||||
releaseZone1,
|
||||
releaseZone2,
|
||||
releaseZone3,
|
||||
releaseZone4
|
||||
);
|
||||
|
||||
// Update the compression amount.
|
||||
setParameterValue(ParamReduction, m_compressor.meteringGain());
|
||||
|
||||
// Apply de-emphasis filter.
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i) {
|
||||
float* destinationData = m_destinationChannels[i];
|
||||
ZeroPole* postFilters = m_postFilterPacks[i]->filters;
|
||||
|
||||
postFilters[0].process(destinationData, destinationData, framesToProcess);
|
||||
postFilters[1].process(destinationData, destinationData, framesToProcess);
|
||||
postFilters[2].process(destinationData, destinationData, framesToProcess);
|
||||
postFilters[3].process(destinationData, destinationData, framesToProcess);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicsCompressor::reset()
|
||||
{
|
||||
m_lastFilterStageRatio = -1; // for recalc
|
||||
m_lastAnchor = -1;
|
||||
m_lastFilterStageGain = -1;
|
||||
|
||||
for (unsigned channel = 0; channel < m_numberOfChannels; ++channel) {
|
||||
for (unsigned stageIndex = 0; stageIndex < 4; ++stageIndex) {
|
||||
m_preFilterPacks[channel]->filters[stageIndex].reset();
|
||||
m_postFilterPacks[channel]->filters[stageIndex].reset();
|
||||
}
|
||||
}
|
||||
|
||||
m_compressor.reset();
|
||||
}
|
||||
|
||||
void DynamicsCompressor::setNumberOfChannels(unsigned numberOfChannels)
|
||||
{
|
||||
if (m_preFilterPacks.size() == numberOfChannels)
|
||||
return;
|
||||
|
||||
m_preFilterPacks.clear();
|
||||
m_postFilterPacks.clear();
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i) {
|
||||
m_preFilterPacks.append(adoptPtr(new ZeroPoleFilterPack4()));
|
||||
m_postFilterPacks.append(adoptPtr(new ZeroPoleFilterPack4()));
|
||||
}
|
||||
|
||||
m_sourceChannels = adoptArrayPtr(new const float* [numberOfChannels]);
|
||||
m_destinationChannels = adoptArrayPtr(new float* [numberOfChannels]);
|
||||
|
||||
m_compressor.setNumberOfChannels(numberOfChannels);
|
||||
m_numberOfChannels = numberOfChannels;
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // ENABLE(WEB_AUDIO)
|
118
content/media/webaudio/blink/DynamicsCompressor.h
Normal file
118
content/media/webaudio/blink/DynamicsCompressor.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef DynamicsCompressor_h
|
||||
#define DynamicsCompressor_h
|
||||
|
||||
#include "AudioArray.h"
|
||||
#include "DynamicsCompressorKernel.h"
|
||||
#include "ZeroPole.h"
|
||||
|
||||
#include <wtf/OwnArrayPtr.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
class AudioBus;
|
||||
|
||||
// DynamicsCompressor implements a flexible audio dynamics compression effect such as
|
||||
// is commonly used in musical production and game audio. It lowers the volume
|
||||
// of the loudest parts of the signal and raises the volume of the softest parts,
|
||||
// making the sound richer, fuller, and more controlled.
|
||||
|
||||
class DynamicsCompressor {
|
||||
public:
|
||||
enum {
|
||||
ParamThreshold,
|
||||
ParamKnee,
|
||||
ParamRatio,
|
||||
ParamAttack,
|
||||
ParamRelease,
|
||||
ParamPreDelay,
|
||||
ParamReleaseZone1,
|
||||
ParamReleaseZone2,
|
||||
ParamReleaseZone3,
|
||||
ParamReleaseZone4,
|
||||
ParamPostGain,
|
||||
ParamFilterStageGain,
|
||||
ParamFilterStageRatio,
|
||||
ParamFilterAnchor,
|
||||
ParamEffectBlend,
|
||||
ParamReduction,
|
||||
ParamLast
|
||||
};
|
||||
|
||||
DynamicsCompressor(float sampleRate, unsigned numberOfChannels);
|
||||
|
||||
void process(const AudioBus* sourceBus, AudioBus* destinationBus, unsigned framesToProcess);
|
||||
void reset();
|
||||
void setNumberOfChannels(unsigned);
|
||||
|
||||
void setParameterValue(unsigned parameterID, float value);
|
||||
float parameterValue(unsigned parameterID);
|
||||
|
||||
float sampleRate() const { return m_sampleRate; }
|
||||
float nyquist() const { return m_sampleRate / 2; }
|
||||
|
||||
double tailTime() const { return 0; }
|
||||
double latencyTime() const { return m_compressor.latencyFrames() / static_cast<double>(sampleRate()); }
|
||||
|
||||
protected:
|
||||
unsigned m_numberOfChannels;
|
||||
|
||||
// m_parameters holds the tweakable compressor parameters.
|
||||
float m_parameters[ParamLast];
|
||||
void initializeParameters();
|
||||
|
||||
float m_sampleRate;
|
||||
|
||||
// Emphasis filter controls.
|
||||
float m_lastFilterStageRatio;
|
||||
float m_lastAnchor;
|
||||
float m_lastFilterStageGain;
|
||||
|
||||
typedef struct {
|
||||
ZeroPole filters[4];
|
||||
} ZeroPoleFilterPack4;
|
||||
|
||||
// Per-channel emphasis filters.
|
||||
Vector<OwnPtr<ZeroPoleFilterPack4> > m_preFilterPacks;
|
||||
Vector<OwnPtr<ZeroPoleFilterPack4> > m_postFilterPacks;
|
||||
|
||||
OwnArrayPtr<const float*> m_sourceChannels;
|
||||
OwnArrayPtr<float*> m_destinationChannels;
|
||||
|
||||
void setEmphasisStageParameters(unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */);
|
||||
void setEmphasisParameters(float gain, float anchorFreq, float filterStageRatio);
|
||||
|
||||
// The core compressor.
|
||||
DynamicsCompressorKernel m_compressor;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // DynamicsCompressor_h
|
479
content/media/webaudio/blink/DynamicsCompressorKernel.cpp
Normal file
479
content/media/webaudio/blink/DynamicsCompressorKernel.cpp
Normal file
@ -0,0 +1,479 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if ENABLE(WEB_AUDIO)
|
||||
|
||||
#include "DynamicsCompressorKernel.h"
|
||||
|
||||
#include "AudioUtilities.h"
|
||||
#include "DenormalDisabler.h"
|
||||
#include <algorithm>
|
||||
#include <wtf/MathExtras.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
using namespace AudioUtilities;
|
||||
|
||||
// Metering hits peaks instantly, but releases this fast (in seconds).
|
||||
const float meteringReleaseTimeConstant = 0.325f;
|
||||
|
||||
const float uninitializedValue = -1;
|
||||
|
||||
DynamicsCompressorKernel::DynamicsCompressorKernel(float sampleRate, unsigned numberOfChannels)
|
||||
: m_sampleRate(sampleRate)
|
||||
, m_lastPreDelayFrames(DefaultPreDelayFrames)
|
||||
, m_preDelayReadIndex(0)
|
||||
, m_preDelayWriteIndex(DefaultPreDelayFrames)
|
||||
, m_ratio(uninitializedValue)
|
||||
, m_slope(uninitializedValue)
|
||||
, m_linearThreshold(uninitializedValue)
|
||||
, m_dbThreshold(uninitializedValue)
|
||||
, m_dbKnee(uninitializedValue)
|
||||
, m_kneeThreshold(uninitializedValue)
|
||||
, m_kneeThresholdDb(uninitializedValue)
|
||||
, m_ykneeThresholdDb(uninitializedValue)
|
||||
, m_K(uninitializedValue)
|
||||
{
|
||||
setNumberOfChannels(numberOfChannels);
|
||||
|
||||
// Initializes most member variables
|
||||
reset();
|
||||
|
||||
m_meteringReleaseK = static_cast<float>(discreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate));
|
||||
}
|
||||
|
||||
void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels)
|
||||
{
|
||||
if (m_preDelayBuffers.size() == numberOfChannels)
|
||||
return;
|
||||
|
||||
m_preDelayBuffers.clear();
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i)
|
||||
m_preDelayBuffers.append(adoptPtr(new AudioFloatArray(MaxPreDelayFrames)));
|
||||
}
|
||||
|
||||
void DynamicsCompressorKernel::setPreDelayTime(float preDelayTime)
|
||||
{
|
||||
// Re-configure look-ahead section pre-delay if delay time has changed.
|
||||
unsigned preDelayFrames = preDelayTime * sampleRate();
|
||||
if (preDelayFrames > MaxPreDelayFrames - 1)
|
||||
preDelayFrames = MaxPreDelayFrames - 1;
|
||||
|
||||
if (m_lastPreDelayFrames != preDelayFrames) {
|
||||
m_lastPreDelayFrames = preDelayFrames;
|
||||
for (unsigned i = 0; i < m_preDelayBuffers.size(); ++i)
|
||||
m_preDelayBuffers[i]->zero();
|
||||
|
||||
m_preDelayReadIndex = 0;
|
||||
m_preDelayWriteIndex = preDelayFrames;
|
||||
}
|
||||
}
|
||||
|
||||
// Exponential curve for the knee.
|
||||
// It is 1st derivative matched at m_linearThreshold and asymptotically approaches the value m_linearThreshold + 1 / k.
|
||||
float DynamicsCompressorKernel::kneeCurve(float x, float k)
|
||||
{
|
||||
// Linear up to threshold.
|
||||
if (x < m_linearThreshold)
|
||||
return x;
|
||||
|
||||
return m_linearThreshold + (1 - expf(-k * (x - m_linearThreshold))) / k;
|
||||
}
|
||||
|
||||
// Full compression curve with constant ratio after knee.
|
||||
float DynamicsCompressorKernel::saturate(float x, float k)
|
||||
{
|
||||
float y;
|
||||
|
||||
if (x < m_kneeThreshold)
|
||||
y = kneeCurve(x, k);
|
||||
else {
|
||||
// Constant ratio after knee.
|
||||
float xDb = linearToDecibels(x);
|
||||
float yDb = m_ykneeThresholdDb + m_slope * (xDb - m_kneeThresholdDb);
|
||||
|
||||
y = decibelsToLinear(yDb);
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
// Approximate 1st derivative with input and output expressed in dB.
|
||||
// This slope is equal to the inverse of the compression "ratio".
|
||||
// In other words, a compression ratio of 20 would be a slope of 1/20.
|
||||
float DynamicsCompressorKernel::slopeAt(float x, float k)
|
||||
{
|
||||
if (x < m_linearThreshold)
|
||||
return 1;
|
||||
|
||||
float x2 = x * 1.001;
|
||||
|
||||
float xDb = linearToDecibels(x);
|
||||
float x2Db = linearToDecibels(x2);
|
||||
|
||||
float yDb = linearToDecibels(kneeCurve(x, k));
|
||||
float y2Db = linearToDecibels(kneeCurve(x2, k));
|
||||
|
||||
float m = (y2Db - yDb) / (x2Db - xDb);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
float DynamicsCompressorKernel::kAtSlope(float desiredSlope)
|
||||
{
|
||||
float xDb = m_dbThreshold + m_dbKnee;
|
||||
float x = decibelsToLinear(xDb);
|
||||
|
||||
// Approximate k given initial values.
|
||||
float minK = 0.1;
|
||||
float maxK = 10000;
|
||||
float k = 5;
|
||||
|
||||
for (int i = 0; i < 15; ++i) {
|
||||
// A high value for k will more quickly asymptotically approach a slope of 0.
|
||||
float slope = slopeAt(x, k);
|
||||
|
||||
if (slope < desiredSlope) {
|
||||
// k is too high.
|
||||
maxK = k;
|
||||
} else {
|
||||
// k is too low.
|
||||
minK = k;
|
||||
}
|
||||
|
||||
// Re-calculate based on geometric mean.
|
||||
k = sqrtf(minK * maxK);
|
||||
}
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
float DynamicsCompressorKernel::updateStaticCurveParameters(float dbThreshold, float dbKnee, float ratio)
|
||||
{
|
||||
if (dbThreshold != m_dbThreshold || dbKnee != m_dbKnee || ratio != m_ratio) {
|
||||
// Threshold and knee.
|
||||
m_dbThreshold = dbThreshold;
|
||||
m_linearThreshold = decibelsToLinear(dbThreshold);
|
||||
m_dbKnee = dbKnee;
|
||||
|
||||
// Compute knee parameters.
|
||||
m_ratio = ratio;
|
||||
m_slope = 1 / m_ratio;
|
||||
|
||||
float k = kAtSlope(1 / m_ratio);
|
||||
|
||||
m_kneeThresholdDb = dbThreshold + dbKnee;
|
||||
m_kneeThreshold = decibelsToLinear(m_kneeThresholdDb);
|
||||
|
||||
m_ykneeThresholdDb = linearToDecibels(kneeCurve(m_kneeThreshold, k));
|
||||
|
||||
m_K = k;
|
||||
}
|
||||
return m_K;
|
||||
}
|
||||
|
||||
void DynamicsCompressorKernel::process(float* sourceChannels[],
|
||||
float* destinationChannels[],
|
||||
unsigned numberOfChannels,
|
||||
unsigned framesToProcess,
|
||||
|
||||
float dbThreshold,
|
||||
float dbKnee,
|
||||
float ratio,
|
||||
float attackTime,
|
||||
float releaseTime,
|
||||
float preDelayTime,
|
||||
float dbPostGain,
|
||||
float effectBlend, /* equal power crossfade */
|
||||
|
||||
float releaseZone1,
|
||||
float releaseZone2,
|
||||
float releaseZone3,
|
||||
float releaseZone4
|
||||
)
|
||||
{
|
||||
ASSERT(m_preDelayBuffers.size() == numberOfChannels);
|
||||
|
||||
float sampleRate = this->sampleRate();
|
||||
|
||||
float dryMix = 1 - effectBlend;
|
||||
float wetMix = effectBlend;
|
||||
|
||||
float k = updateStaticCurveParameters(dbThreshold, dbKnee, ratio);
|
||||
|
||||
// Makeup gain.
|
||||
float fullRangeGain = saturate(1, k);
|
||||
float fullRangeMakeupGain = 1 / fullRangeGain;
|
||||
|
||||
// Empirical/perceptual tuning.
|
||||
fullRangeMakeupGain = powf(fullRangeMakeupGain, 0.6f);
|
||||
|
||||
float masterLinearGain = decibelsToLinear(dbPostGain) * fullRangeMakeupGain;
|
||||
|
||||
// Attack parameters.
|
||||
attackTime = max(0.001f, attackTime);
|
||||
float attackFrames = attackTime * sampleRate;
|
||||
|
||||
// Release parameters.
|
||||
float releaseFrames = sampleRate * releaseTime;
|
||||
|
||||
// Detector release time.
|
||||
float satReleaseTime = 0.0025f;
|
||||
float satReleaseFrames = satReleaseTime * sampleRate;
|
||||
|
||||
// Create a smooth function which passes through four points.
|
||||
|
||||
// Polynomial of the form
|
||||
// y = a + b*x + c*x^2 + d*x^3 + e*x^4;
|
||||
|
||||
float y1 = releaseFrames * releaseZone1;
|
||||
float y2 = releaseFrames * releaseZone2;
|
||||
float y3 = releaseFrames * releaseZone3;
|
||||
float y4 = releaseFrames * releaseZone4;
|
||||
|
||||
// All of these coefficients were derived for 4th order polynomial curve fitting where the y values
|
||||
// match the evenly spaced x values as follows: (y1 : x == 0, y2 : x == 1, y3 : x == 2, y4 : x == 3)
|
||||
float kA = 0.9999999999999998f*y1 + 1.8432219684323923e-16f*y2 - 1.9373394351676423e-16f*y3 + 8.824516011816245e-18f*y4;
|
||||
float kB = -1.5788320352845888f*y1 + 2.3305837032074286f*y2 - 0.9141194204840429f*y3 + 0.1623677525612032f*y4;
|
||||
float kC = 0.5334142869106424f*y1 - 1.272736789213631f*y2 + 0.9258856042207512f*y3 - 0.18656310191776226f*y4;
|
||||
float kD = 0.08783463138207234f*y1 - 0.1694162967925622f*y2 + 0.08588057951595272f*y3 - 0.00429891410546283f*y4;
|
||||
float kE = -0.042416883008123074f*y1 + 0.1115693827987602f*y2 - 0.09764676325265872f*y3 + 0.028494263462021576f*y4;
|
||||
|
||||
// x ranges from 0 -> 3 0 1 2 3
|
||||
// -15 -10 -5 0db
|
||||
|
||||
// y calculates adaptive release frames depending on the amount of compression.
|
||||
|
||||
setPreDelayTime(preDelayTime);
|
||||
|
||||
const int nDivisionFrames = 32;
|
||||
|
||||
const int nDivisions = framesToProcess / nDivisionFrames;
|
||||
|
||||
unsigned frameIndex = 0;
|
||||
for (int i = 0; i < nDivisions; ++i) {
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Calculate desired gain
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// Fix gremlins.
|
||||
if (std::isnan(m_detectorAverage))
|
||||
m_detectorAverage = 1;
|
||||
if (std::isinf(m_detectorAverage))
|
||||
m_detectorAverage = 1;
|
||||
|
||||
float desiredGain = m_detectorAverage;
|
||||
|
||||
// Pre-warp so we get desiredGain after sin() warp below.
|
||||
float scaledDesiredGain = asinf(desiredGain) / (0.5f * piFloat);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Deal with envelopes
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// envelopeRate is the rate we slew from current compressor level to the desired level.
|
||||
// The exact rate depends on if we're attacking or releasing and by how much.
|
||||
float envelopeRate;
|
||||
|
||||
bool isReleasing = scaledDesiredGain > m_compressorGain;
|
||||
|
||||
// compressionDiffDb is the difference between current compression level and the desired level.
|
||||
float compressionDiffDb = linearToDecibels(m_compressorGain / scaledDesiredGain);
|
||||
|
||||
if (isReleasing) {
|
||||
// Release mode - compressionDiffDb should be negative dB
|
||||
m_maxAttackCompressionDiffDb = -1;
|
||||
|
||||
// Fix gremlins.
|
||||
if (std::isnan(compressionDiffDb))
|
||||
compressionDiffDb = -1;
|
||||
if (std::isinf(compressionDiffDb))
|
||||
compressionDiffDb = -1;
|
||||
|
||||
// Adaptive release - higher compression (lower compressionDiffDb) releases faster.
|
||||
|
||||
// Contain within range: -12 -> 0 then scale to go from 0 -> 3
|
||||
float x = compressionDiffDb;
|
||||
x = max(-12.0f, x);
|
||||
x = min(0.0f, x);
|
||||
x = 0.25f * (x + 12);
|
||||
|
||||
// Compute adaptive release curve using 4th order polynomial.
|
||||
// Normal values for the polynomial coefficients would create a monotonically increasing function.
|
||||
float x2 = x * x;
|
||||
float x3 = x2 * x;
|
||||
float x4 = x2 * x2;
|
||||
float releaseFrames = kA + kB * x + kC * x2 + kD * x3 + kE * x4;
|
||||
|
||||
#define kSpacingDb 5
|
||||
float dbPerFrame = kSpacingDb / releaseFrames;
|
||||
|
||||
envelopeRate = decibelsToLinear(dbPerFrame);
|
||||
} else {
|
||||
// Attack mode - compressionDiffDb should be positive dB
|
||||
|
||||
// Fix gremlins.
|
||||
if (std::isnan(compressionDiffDb))
|
||||
compressionDiffDb = 1;
|
||||
if (std::isinf(compressionDiffDb))
|
||||
compressionDiffDb = 1;
|
||||
|
||||
// As long as we're still in attack mode, use a rate based off
|
||||
// the largest compressionDiffDb we've encountered so far.
|
||||
if (m_maxAttackCompressionDiffDb == -1 || m_maxAttackCompressionDiffDb < compressionDiffDb)
|
||||
m_maxAttackCompressionDiffDb = compressionDiffDb;
|
||||
|
||||
float effAttenDiffDb = max(0.5f, m_maxAttackCompressionDiffDb);
|
||||
|
||||
float x = 0.25f / effAttenDiffDb;
|
||||
envelopeRate = 1 - powf(x, 1 / attackFrames);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Inner loop - calculate shaped power average - apply compression.
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
{
|
||||
int preDelayReadIndex = m_preDelayReadIndex;
|
||||
int preDelayWriteIndex = m_preDelayWriteIndex;
|
||||
float detectorAverage = m_detectorAverage;
|
||||
float compressorGain = m_compressorGain;
|
||||
|
||||
int loopFrames = nDivisionFrames;
|
||||
while (loopFrames--) {
|
||||
float compressorInput = 0;
|
||||
|
||||
// Predelay signal, computing compression amount from un-delayed version.
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i) {
|
||||
float* delayBuffer = m_preDelayBuffers[i]->data();
|
||||
float undelayedSource = sourceChannels[i][frameIndex];
|
||||
delayBuffer[preDelayWriteIndex] = undelayedSource;
|
||||
|
||||
float absUndelayedSource = undelayedSource > 0 ? undelayedSource : -undelayedSource;
|
||||
if (compressorInput < absUndelayedSource)
|
||||
compressorInput = absUndelayedSource;
|
||||
}
|
||||
|
||||
// Calculate shaped power on undelayed input.
|
||||
|
||||
float scaledInput = compressorInput;
|
||||
float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
|
||||
|
||||
// Put through shaping curve.
|
||||
// This is linear up to the threshold, then enters a "knee" portion followed by the "ratio" portion.
|
||||
// The transition from the threshold to the knee is smooth (1st derivative matched).
|
||||
// The transition from the knee to the ratio portion is smooth (1st derivative matched).
|
||||
float shapedInput = saturate(absInput, k);
|
||||
|
||||
float attenuation = absInput <= 0.0001f ? 1 : shapedInput / absInput;
|
||||
|
||||
float attenuationDb = -linearToDecibels(attenuation);
|
||||
attenuationDb = max(2.0f, attenuationDb);
|
||||
|
||||
float dbPerFrame = attenuationDb / satReleaseFrames;
|
||||
|
||||
float satReleaseRate = decibelsToLinear(dbPerFrame) - 1;
|
||||
|
||||
bool isRelease = (attenuation > detectorAverage);
|
||||
float rate = isRelease ? satReleaseRate : 1;
|
||||
|
||||
detectorAverage += (attenuation - detectorAverage) * rate;
|
||||
detectorAverage = min(1.0f, detectorAverage);
|
||||
|
||||
// Fix gremlins.
|
||||
if (std::isnan(detectorAverage))
|
||||
detectorAverage = 1;
|
||||
if (std::isinf(detectorAverage))
|
||||
detectorAverage = 1;
|
||||
|
||||
// Exponential approach to desired gain.
|
||||
if (envelopeRate < 1) {
|
||||
// Attack - reduce gain to desired.
|
||||
compressorGain += (scaledDesiredGain - compressorGain) * envelopeRate;
|
||||
} else {
|
||||
// Release - exponentially increase gain to 1.0
|
||||
compressorGain *= envelopeRate;
|
||||
compressorGain = min(1.0f, compressorGain);
|
||||
}
|
||||
|
||||
// Warp pre-compression gain to smooth out sharp exponential transition points.
|
||||
float postWarpCompressorGain = sinf(0.5f * piFloat * compressorGain);
|
||||
|
||||
// Calculate total gain using master gain and effect blend.
|
||||
float totalGain = dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
|
||||
|
||||
// Calculate metering.
|
||||
float dbRealGain = 20 * log10(postWarpCompressorGain);
|
||||
if (dbRealGain < m_meteringGain)
|
||||
m_meteringGain = dbRealGain;
|
||||
else
|
||||
m_meteringGain += (dbRealGain - m_meteringGain) * m_meteringReleaseK;
|
||||
|
||||
// Apply final gain.
|
||||
for (unsigned i = 0; i < numberOfChannels; ++i) {
|
||||
float* delayBuffer = m_preDelayBuffers[i]->data();
|
||||
destinationChannels[i][frameIndex] = delayBuffer[preDelayReadIndex] * totalGain;
|
||||
}
|
||||
|
||||
frameIndex++;
|
||||
preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
|
||||
preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
|
||||
}
|
||||
|
||||
// Locals back to member variables.
|
||||
m_preDelayReadIndex = preDelayReadIndex;
|
||||
m_preDelayWriteIndex = preDelayWriteIndex;
|
||||
m_detectorAverage = DenormalDisabler::flushDenormalFloatToZero(detectorAverage);
|
||||
m_compressorGain = DenormalDisabler::flushDenormalFloatToZero(compressorGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicsCompressorKernel::reset()
|
||||
{
|
||||
m_detectorAverage = 0;
|
||||
m_compressorGain = 1;
|
||||
m_meteringGain = 1;
|
||||
|
||||
// Predelay section.
|
||||
for (unsigned i = 0; i < m_preDelayBuffers.size(); ++i)
|
||||
m_preDelayBuffers[i]->zero();
|
||||
|
||||
m_preDelayReadIndex = 0;
|
||||
m_preDelayWriteIndex = DefaultPreDelayFrames;
|
||||
|
||||
m_maxAttackCompressionDiffDb = -1; // uninitialized state
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // ENABLE(WEB_AUDIO)
|
129
content/media/webaudio/blink/DynamicsCompressorKernel.h
Normal file
129
content/media/webaudio/blink/DynamicsCompressorKernel.h
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef DynamicsCompressorKernel_h
|
||||
#define DynamicsCompressorKernel_h
|
||||
|
||||
#include "AudioArray.h"
|
||||
|
||||
#include <wtf/OwnPtr.h>
|
||||
#include <wtf/PassOwnPtr.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
class DynamicsCompressorKernel {
|
||||
public:
|
||||
DynamicsCompressorKernel(float sampleRate, unsigned numberOfChannels);
|
||||
|
||||
void setNumberOfChannels(unsigned);
|
||||
|
||||
// Performs stereo-linked compression.
|
||||
void process(float* sourceChannels[],
|
||||
float* destinationChannels[],
|
||||
unsigned numberOfChannels,
|
||||
unsigned framesToProcess,
|
||||
|
||||
float dbThreshold,
|
||||
float dbKnee,
|
||||
float ratio,
|
||||
float attackTime,
|
||||
float releaseTime,
|
||||
float preDelayTime,
|
||||
float dbPostGain,
|
||||
float effectBlend,
|
||||
|
||||
float releaseZone1,
|
||||
float releaseZone2,
|
||||
float releaseZone3,
|
||||
float releaseZone4
|
||||
);
|
||||
|
||||
void reset();
|
||||
|
||||
unsigned latencyFrames() const { return m_lastPreDelayFrames; }
|
||||
|
||||
float sampleRate() const { return m_sampleRate; }
|
||||
|
||||
float meteringGain() const { return m_meteringGain; }
|
||||
|
||||
protected:
|
||||
float m_sampleRate;
|
||||
|
||||
float m_detectorAverage;
|
||||
float m_compressorGain;
|
||||
|
||||
// Metering
|
||||
float m_meteringReleaseK;
|
||||
float m_meteringGain;
|
||||
|
||||
// Lookahead section.
|
||||
enum { MaxPreDelayFrames = 1024 };
|
||||
enum { MaxPreDelayFramesMask = MaxPreDelayFrames - 1 };
|
||||
enum { DefaultPreDelayFrames = 256 }; // setPreDelayTime() will override this initial value
|
||||
unsigned m_lastPreDelayFrames;
|
||||
void setPreDelayTime(float);
|
||||
|
||||
Vector<OwnPtr<AudioFloatArray> > m_preDelayBuffers;
|
||||
int m_preDelayReadIndex;
|
||||
int m_preDelayWriteIndex;
|
||||
|
||||
float m_maxAttackCompressionDiffDb;
|
||||
|
||||
// Static compression curve.
|
||||
float kneeCurve(float x, float k);
|
||||
float saturate(float x, float k);
|
||||
float slopeAt(float x, float k);
|
||||
float kAtSlope(float desiredSlope);
|
||||
|
||||
float updateStaticCurveParameters(float dbThreshold, float dbKnee, float ratio);
|
||||
|
||||
// Amount of input change in dB required for 1 dB of output change.
|
||||
// This applies to the portion of the curve above m_kneeThresholdDb (see below).
|
||||
float m_ratio;
|
||||
float m_slope; // Inverse ratio.
|
||||
|
||||
// The input to output change below the threshold is linear 1:1.
|
||||
float m_linearThreshold;
|
||||
float m_dbThreshold;
|
||||
|
||||
// m_dbKnee is the number of dB above the threshold before we enter the "ratio" portion of the curve.
|
||||
// m_kneeThresholdDb = m_dbThreshold + m_dbKnee
|
||||
// The portion between m_dbThreshold and m_kneeThresholdDb is the "soft knee" portion of the curve
|
||||
// which transitions smoothly from the linear portion to the ratio portion.
|
||||
float m_dbKnee;
|
||||
float m_kneeThreshold;
|
||||
float m_kneeThresholdDb;
|
||||
float m_ykneeThresholdDb;
|
||||
|
||||
// Internal parameter for the knee portion of the curve.
|
||||
float m_K;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // DynamicsCompressorKernel_h
|
24
content/media/webaudio/blink/README
Normal file
24
content/media/webaudio/blink/README
Normal file
@ -0,0 +1,24 @@
|
||||
This directory contains the code originally borrowed from the Blink Web Audio
|
||||
implementation. We are forking the code here because in many cases the burden
|
||||
of adopting Blink specific utilities is too large compared to the prospect of
|
||||
importing upstream fixes by just copying newer versions of the code in the
|
||||
future.
|
||||
|
||||
The process of borrowing code from Blink is as follows:
|
||||
|
||||
* Try to borrow utility classes only, and avoid borrowing code which depends
|
||||
too much on the Blink specific utilities.
|
||||
* First, import the pristine files from the Blink repository before adding
|
||||
them to the build system, noting the SVN revision of Blink from which the
|
||||
original files were copied in the commit message.
|
||||
* In a separate commit, add the imported source files to the build system,
|
||||
and apply the necessary changes to make it build successfully.
|
||||
* Use the code in a separate commit.
|
||||
* Never add headers as exported headers. All headers should be included
|
||||
using the following convention: #include "blink/Header.h".
|
||||
* Leave the imported code in the WebCore namespace, and import the needed
|
||||
names into the Mozilla code via `using'.
|
||||
* Cherry-pick upsteam fixes manually when needed. In case you fix a problem
|
||||
that is not Mozilla specific locally, try to upstream your changes into
|
||||
Blink.
|
||||
* Ping ehsan for any questions.
|
74
content/media/webaudio/blink/ZeroPole.cpp
Normal file
74
content/media/webaudio/blink/ZeroPole.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if ENABLE(WEB_AUDIO)
|
||||
|
||||
#include "ZeroPole.h"
|
||||
|
||||
#include "DenormalDisabler.h"
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
void ZeroPole::process(const float *source, float *destination, unsigned framesToProcess)
|
||||
{
|
||||
float zero = m_zero;
|
||||
float pole = m_pole;
|
||||
|
||||
// Gain compensation to make 0dB @ 0Hz
|
||||
const float k1 = 1 / (1 - zero);
|
||||
const float k2 = 1 - pole;
|
||||
|
||||
// Member variables to locals.
|
||||
float lastX = m_lastX;
|
||||
float lastY = m_lastY;
|
||||
|
||||
while (framesToProcess--) {
|
||||
float input = *source++;
|
||||
|
||||
// Zero
|
||||
float output1 = k1 * (input - zero * lastX);
|
||||
lastX = input;
|
||||
|
||||
// Pole
|
||||
float output2 = k2 * output1 + pole * lastY;
|
||||
lastY = output2;
|
||||
|
||||
*destination++ = output2;
|
||||
}
|
||||
|
||||
// Locals to member variables. Flush denormals here so we don't
|
||||
// slow down the inner loop above.
|
||||
m_lastX = DenormalDisabler::flushDenormalFloatToZero(lastX);
|
||||
m_lastY = DenormalDisabler::flushDenormalFloatToZero(lastY);
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // ENABLE(WEB_AUDIO)
|
66
content/media/webaudio/blink/ZeroPole.h
Normal file
66
content/media/webaudio/blink/ZeroPole.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef ZeroPole_h
|
||||
#define ZeroPole_h
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
// ZeroPole is a simple filter with one zero and one pole.
|
||||
|
||||
class ZeroPole {
|
||||
public:
|
||||
ZeroPole()
|
||||
: m_zero(0)
|
||||
, m_pole(0)
|
||||
, m_lastX(0)
|
||||
, m_lastY(0)
|
||||
{
|
||||
}
|
||||
|
||||
void process(const float *source, float *destination, unsigned framesToProcess);
|
||||
|
||||
// Reset filter state.
|
||||
void reset() { m_lastX = 0; m_lastY = 0; }
|
||||
|
||||
void setZero(float zero) { m_zero = zero; }
|
||||
void setPole(float pole) { m_pole = pole; }
|
||||
|
||||
float zero() const { return m_zero; }
|
||||
float pole() const { return m_pole; }
|
||||
|
||||
private:
|
||||
float m_zero;
|
||||
float m_pole;
|
||||
float m_lastX;
|
||||
float m_lastY;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // ZeroPole_h
|
@ -866,7 +866,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
<h1><a id="apple"></a>Apple License</h1>
|
||||
|
||||
<p>This license applies to certain files in the directories <span class="path">js/src/assembler/assembler/</span>, <span class="path">js/src/assembler/wtf/</span>, <span class="path">js/src/yarr</span>, and <span class="path">widget/cocoa</span>.</p>
|
||||
<p>This license applies to certain files in the directories <span class="path">js/src/assembler/assembler/</span>, <span class="path">js/src/assembler/wtf/</span>, <span class="path">js/src/yarr</span>, <span class="path">content/media/webaudio/blink</span>, and <span class="path">widget/cocoa</span>.</p>
|
||||
|
||||
<pre>
|
||||
Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
|
||||
|
Loading…
Reference in New Issue
Block a user