DynamicsCompressorKernel.cpp [plain text]
#include "config.h"
#if ENABLE(WEB_AUDIO)
#include "DynamicsCompressorKernel.h"
#include "AudioUtilities.h"
#include <algorithm>
#include <wtf/MathExtras.h>
using namespace std;
namespace WebCore {
using namespace AudioUtilities;
const double meteringReleaseTimeConstant = 0.325;
static double saturate(double x, double k)
{
return 1 - exp(-k * x);
}
DynamicsCompressorKernel::DynamicsCompressorKernel(double sampleRate)
: m_sampleRate(sampleRate)
, m_lastPreDelayFrames(DefaultPreDelayFrames)
, m_preDelayBufferL(MaxPreDelayFrames)
, m_preDelayBufferR(MaxPreDelayFrames)
, m_preDelayReadIndex(0)
, m_preDelayWriteIndex(DefaultPreDelayFrames)
{
reset();
m_meteringReleaseK = discreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate);
}
void DynamicsCompressorKernel::setPreDelayTime(float preDelayTime)
{
unsigned preDelayFrames = preDelayTime / sampleRate();
if (preDelayFrames > MaxPreDelayFrames - 1)
preDelayFrames = MaxPreDelayFrames - 1;
if (m_lastPreDelayFrames != preDelayFrames) {
m_lastPreDelayFrames = preDelayFrames;
m_preDelayBufferL.zero();
m_preDelayBufferR.zero();
m_preDelayReadIndex = 0;
m_preDelayWriteIndex = preDelayFrames;
}
}
void DynamicsCompressorKernel::process(float* sourceL,
float* destinationL,
float* sourceR,
float* destinationR,
unsigned framesToProcess,
float dbThreshold,
float dbHeadroom,
float attackTime,
float releaseTime,
float preDelayTime,
float dbPostGain,
float effectBlend,
float releaseZone1,
float releaseZone2,
float releaseZone3,
float releaseZone4
)
{
bool isStereo = destinationR;
float sampleRate = this->sampleRate();
float dryMix = 1 - effectBlend;
float wetMix = effectBlend;
double linearThreshold = decibelsToLinear(dbThreshold);
double linearHeadroom = decibelsToLinear(dbHeadroom);
double maximum = 1.05 * linearHeadroom * linearThreshold;
double kk = (maximum - linearThreshold);
double inverseKK = 1 / kk;
double fullRangeGain = (linearThreshold + kk * saturate(1 - linearThreshold, 1));
double fullRangeMakeupGain = 1 / fullRangeGain;
fullRangeMakeupGain = pow(fullRangeMakeupGain, 0.6);
float masterLinearGain = decibelsToLinear(dbPostGain) * fullRangeMakeupGain;
attackTime = max(0.001f, attackTime);
float attackFrames = attackTime * sampleRate;
float releaseFrames = sampleRate * releaseTime;
double satReleaseTime = 0.0025;
double satReleaseFrames = satReleaseTime * sampleRate;
double y1 = releaseFrames * releaseZone1;
double y2 = releaseFrames * releaseZone2;
double y3 = releaseFrames * releaseZone3;
double y4 = releaseFrames * releaseZone4;
double kA = 0.9999999999999998*y1 + 1.8432219684323923e-16*y2 - 1.9373394351676423e-16*y3 + 8.824516011816245e-18*y4;
double kB = -1.5788320352845888*y1 + 2.3305837032074286*y2 - 0.9141194204840429*y3 + 0.1623677525612032*y4;
double kC = 0.5334142869106424*y1 - 1.272736789213631*y2 + 0.9258856042207512*y3 - 0.18656310191776226*y4;
double kD = 0.08783463138207234*y1 - 0.1694162967925622*y2 + 0.08588057951595272*y3 - 0.00429891410546283*y4;
double kE = -0.042416883008123074*y1 + 0.1115693827987602*y2 - 0.09764676325265872*y3 + 0.028494263462021576*y4;
setPreDelayTime(preDelayTime);
const int nDivisionFrames = 32;
const int nDivisions = framesToProcess / nDivisionFrames;
for (int i = 0; i < nDivisions; ++i) {
if (isnan(m_detectorAverage))
m_detectorAverage = 1;
if (isinf(m_detectorAverage))
m_detectorAverage = 1;
float desiredGain = m_detectorAverage;
double scaledDesiredGain = asin(desiredGain) / (0.5 * piDouble);
float envelopeRate;
bool isReleasing = scaledDesiredGain > m_compressorGain;
double compressionDiffDb = linearToDecibels(m_compressorGain / scaledDesiredGain);
if (isReleasing) {
m_maxAttackCompressionDiffDb = -1;
if (isnan(compressionDiffDb))
compressionDiffDb = -1;
if (isinf(compressionDiffDb))
compressionDiffDb = -1;
double x = compressionDiffDb;
x = max(-12., x);
x = min(0., x);
x = 0.25 * (x + 12);
double x2 = x * x;
double x3 = x2 * x;
double x4 = x2 * x2;
double releaseFrames = kA + kB * x + kC * x2 + kD * x3 + kE * x4;
#define kSpacingDb 5
double dbPerFrame = kSpacingDb / releaseFrames;
envelopeRate = decibelsToLinear(dbPerFrame);
} else {
if (isnan(compressionDiffDb))
compressionDiffDb = 1;
if (isinf(compressionDiffDb))
compressionDiffDb = 1;
if (m_maxAttackCompressionDiffDb == -1 || m_maxAttackCompressionDiffDb < compressionDiffDb)
m_maxAttackCompressionDiffDb = compressionDiffDb;
double effAttenDiffDb = max(0.5f, m_maxAttackCompressionDiffDb);
double x = 0.25 / effAttenDiffDb;
envelopeRate = 1 - pow(x, double(1 / attackFrames));
}
{
float* delayBufferL = m_preDelayBufferL.data();
float* delayBufferR = m_preDelayBufferR.data();
int preDelayReadIndex = m_preDelayReadIndex;
int preDelayWriteIndex = m_preDelayWriteIndex;
float detectorAverage = m_detectorAverage;
float compressorGain = m_compressorGain;
int loopFrames = nDivisionFrames;
while (loopFrames--) {
float compressorInput;
float inputL;
float inputR = 0;
if (isStereo) {
float undelayedL = *sourceL++;
float undelayedR = *sourceR++;
compressorInput = 0.5 * (undelayedL + undelayedR);
inputL = delayBufferL[preDelayReadIndex];
inputR = delayBufferR[preDelayReadIndex];
delayBufferL[preDelayWriteIndex] = undelayedL;
delayBufferR[preDelayWriteIndex] = undelayedR;
} else {
compressorInput = *sourceL++;
inputL = delayBufferL[preDelayReadIndex];
delayBufferL[preDelayWriteIndex] = compressorInput;
}
preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
float scaledInput = compressorInput;
double absInput = scaledInput > 0 ? scaledInput : -scaledInput;
double shapedInput = absInput < linearThreshold ? absInput : linearThreshold + kk * saturate(absInput - linearThreshold, inverseKK);
double attenuation = absInput <= 0.0001 ? 1 : shapedInput / absInput;
double attenuationDb = -linearToDecibels(attenuation);
attenuationDb = max(2., attenuationDb);
double dbPerFrame = attenuationDb / satReleaseFrames;
double satReleaseRate = decibelsToLinear(dbPerFrame) - 1;
bool isRelease = (attenuation > detectorAverage);
double rate = isRelease ? satReleaseRate : 1;
detectorAverage += (attenuation - detectorAverage) * rate;
detectorAverage = min(1.0f, detectorAverage);
if (isnan(detectorAverage))
detectorAverage = 1;
if (isinf(detectorAverage))
detectorAverage = 1;
if (envelopeRate < 1) {
compressorGain += (scaledDesiredGain - compressorGain) * envelopeRate;
} else {
compressorGain *= envelopeRate;
compressorGain = min(1.0f, compressorGain);
}
double postWarpCompressorGain = sin(0.5 * piDouble * compressorGain);
double totalGain = dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
double dbRealGain = 20 * log10(postWarpCompressorGain);
if (dbRealGain < m_meteringGain)
m_meteringGain = dbRealGain;
else
m_meteringGain += (dbRealGain - m_meteringGain) * m_meteringReleaseK;
if (isStereo) {
float outputL = inputL;
float outputR = inputR;
outputL *= totalGain;
outputR *= totalGain;
*destinationL++ = outputL;
*destinationR++ = outputR;
} else
*destinationL++ = inputL * totalGain;
}
m_preDelayReadIndex = preDelayReadIndex;
m_preDelayWriteIndex = preDelayWriteIndex;
m_detectorAverage = detectorAverage;
m_compressorGain = compressorGain;
}
}
}
void DynamicsCompressorKernel::reset()
{
m_detectorAverage = 0;
m_compressorGain = 1;
m_meteringGain = 1;
m_preDelayBufferL.zero();
m_preDelayBufferR.zero();
m_preDelayReadIndex = 0;
m_preDelayWriteIndex = DefaultPreDelayFrames;
m_maxAttackCompressionDiffDb = -1; }
}
#endif // ENABLE(WEB_AUDIO)