Diff
Modified: trunk/LayoutTests/ChangeLog (109022 => 109023)
--- trunk/LayoutTests/ChangeLog 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/LayoutTests/ChangeLog 2012-02-27 22:15:11 UTC (rev 109023)
@@ -1,3 +1,13 @@
+2012-02-27 Chris Rogers <crog...@google.com>
+
+ Implement static compression curve parameters for DynamicsCompressorNode
+ https://bugs.webkit.org/show_bug.cgi?id=78937
+
+ Reviewed by Kenneth Russell.
+
+ * webaudio/dynamicscompressor-basic-expected.txt: Added.
+ * webaudio/dynamicscompressor-basic.html: Added.
+
2012-02-27 Oliver Hunt <oli...@apple.com>
REGRESSION (r108112): AWS Management Console at amazon.com fails to initialize
Added: trunk/LayoutTests/webaudio/dynamicscompressor-basic-expected.txt (0 => 109023)
--- trunk/LayoutTests/webaudio/dynamicscompressor-basic-expected.txt (rev 0)
+++ trunk/LayoutTests/webaudio/dynamicscompressor-basic-expected.txt 2012-02-27 22:15:11 UTC (rev 109023)
@@ -0,0 +1,11 @@
+Basic tests for DynamicsCompressorNode API.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS threshold attribute has correct default value.
+PASS knee attribute has correct default value.
+PASS ratio attribute has correct default value.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/webaudio/dynamicscompressor-basic.html (0 => 109023)
--- trunk/LayoutTests/webaudio/dynamicscompressor-basic.html (rev 0)
+++ trunk/LayoutTests/webaudio/dynamicscompressor-basic.html 2012-02-27 22:15:11 UTC (rev 109023)
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script src=""
+<script type="text/_javascript_" src=""
+</head>
+
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Basic tests for DynamicsCompressorNode API.");
+
+var context;
+var compressor;
+
+function runTest() {
+ if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+ }
+
+ window.jsTestIsAsync = true;
+
+ context = new webkitAudioContext();
+ compressor = context.createDynamicsCompressor();
+
+ try {
+ if (compressor.threshold.value == -24)
+ testPassed("threshold attribute has correct default value.");
+ else
+ testFailed("threshold attribute has incorrect default value.");
+
+ if (compressor.knee.value == 30)
+ testPassed("knee attribute has correct default value.");
+ else
+ testFailed("knee attribute has incorrect default value.");
+
+ if (compressor.ratio.value == 12)
+ testPassed("ratio attribute has correct default value.");
+ else
+ testFailed("ratio attribute has incorrect default value.");
+
+ } catch(e) {
+ testFailed("Exception thrown when accessing DynamicsCompressorNode attributes.");
+ }
+
+ finishJSTest();
+}
+
+runTest();
+
+</script>
+
+<script src=""
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (109022 => 109023)
--- trunk/Source/WebCore/ChangeLog 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/ChangeLog 2012-02-27 22:15:11 UTC (rev 109023)
@@ -1,3 +1,44 @@
+2012-02-27 Chris Rogers <crog...@google.com>
+
+ Implement static compression curve parameters for DynamicsCompressorNode
+ https://bugs.webkit.org/show_bug.cgi?id=78937
+
+ Reviewed by Kenneth Russell.
+
+ Test: webaudio/dynamicscompressor-basic.html
+
+ * platform/audio/DynamicsCompressor.cpp:
+ (WebCore::DynamicsCompressor::setParameterValue):
+ (WebCore):
+ (WebCore::DynamicsCompressor::initializeParameters):
+ (WebCore::DynamicsCompressor::process):
+ * platform/audio/DynamicsCompressor.h:
+ * platform/audio/DynamicsCompressorKernel.cpp:
+ (WebCore):
+ (WebCore::DynamicsCompressorKernel::DynamicsCompressorKernel):
+ (WebCore::DynamicsCompressorKernel::setPreDelayTime):
+ (WebCore::DynamicsCompressorKernel::kneeCurve):
+ (WebCore::DynamicsCompressorKernel::saturate):
+ (WebCore::DynamicsCompressorKernel::slopeAt):
+ (WebCore::DynamicsCompressorKernel::kAtSlope):
+ (WebCore::DynamicsCompressorKernel::updateStaticCurveParameters):
+ (WebCore::DynamicsCompressorKernel::process):
+ * platform/audio/DynamicsCompressorKernel.h:
+ (DynamicsCompressorKernel):
+ (WebCore::DynamicsCompressorKernel::meteringGain):
+ * webaudio/DynamicsCompressorNode.cpp:
+ (WebCore::DynamicsCompressorNode::DynamicsCompressorNode):
+ (WebCore::DynamicsCompressorNode::process):
+ * webaudio/DynamicsCompressorNode.h:
+ (WebCore):
+ (WebCore::DynamicsCompressorNode::create):
+ (DynamicsCompressorNode):
+ (WebCore::DynamicsCompressorNode::threshold):
+ (WebCore::DynamicsCompressorNode::knee):
+ (WebCore::DynamicsCompressorNode::ratio):
+ (WebCore::DynamicsCompressorNode::reduction):
+ * webaudio/DynamicsCompressorNode.idl:
+
2012-02-27 Enrica Casucci <enr...@apple.com>
WebKit2: implement platform strategy to access Pasteboard in the UI process.
Modified: trunk/Source/WebCore/platform/audio/DynamicsCompressor.cpp (109022 => 109023)
--- trunk/Source/WebCore/platform/audio/DynamicsCompressor.cpp 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/platform/audio/DynamicsCompressor.cpp 2012-02-27 22:15:11 UTC (rev 109023)
@@ -53,12 +53,20 @@
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[ParamHeadroom] = 21; // 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
@@ -74,6 +82,7 @@
m_parameters[ParamFilterAnchor] = 15000 / nyquist();
m_parameters[ParamPostGain] = 0; // dB
+ m_parameters[ParamReduction] = 0; // dB
// Linear crossfade (0 -> 1).
m_parameters[ParamEffectBlend] = 1;
@@ -180,7 +189,8 @@
}
float dbThreshold = parameterValue(ParamThreshold);
- float dbHeadroom = parameterValue(ParamHeadroom);
+ float dbKnee = parameterValue(ParamKnee);
+ float ratio = parameterValue(ParamRatio);
float attackTime = parameterValue(ParamAttack);
float releaseTime = parameterValue(ParamRelease);
float preDelayTime = parameterValue(ParamPreDelay);
@@ -206,7 +216,8 @@
framesToProcess,
dbThreshold,
- dbHeadroom,
+ dbKnee,
+ ratio,
attackTime,
releaseTime,
preDelayTime,
@@ -218,6 +229,9 @@
releaseZone3,
releaseZone4
);
+
+ // Update the compression amount.
+ setParameterValue(ParamReduction, m_compressor.meteringGain());
// Apply de-emphasis filter.
for (unsigned i = 0; i < numberOfChannels; ++i) {
Modified: trunk/Source/WebCore/platform/audio/DynamicsCompressor.h (109022 => 109023)
--- trunk/Source/WebCore/platform/audio/DynamicsCompressor.h 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/platform/audio/DynamicsCompressor.h 2012-02-27 22:15:11 UTC (rev 109023)
@@ -48,7 +48,8 @@
public:
enum {
ParamThreshold,
- ParamHeadroom,
+ ParamKnee,
+ ParamRatio,
ParamAttack,
ParamRelease,
ParamPreDelay,
@@ -61,6 +62,7 @@
ParamFilterStageRatio,
ParamFilterAnchor,
ParamEffectBlend,
+ ParamReduction,
ParamLast
};
@@ -70,6 +72,7 @@
void reset();
void setNumberOfChannels(unsigned);
+ void setParameterValue(unsigned parameterID, float value);
float parameterValue(unsigned parameterID);
float sampleRate() const { return m_sampleRate; }
Modified: trunk/Source/WebCore/platform/audio/DynamicsCompressorKernel.cpp (109022 => 109023)
--- trunk/Source/WebCore/platform/audio/DynamicsCompressorKernel.cpp 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/platform/audio/DynamicsCompressorKernel.cpp 2012-02-27 22:15:11 UTC (rev 109023)
@@ -45,24 +45,29 @@
// Metering hits peaks instantly, but releases this fast (in seconds).
const float meteringReleaseTimeConstant = 0.325f;
-
-// Exponential saturation curve.
-static float saturate(float x, float k)
-{
- return 1 - exp(-k * x);
-}
+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 = discreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate);
}
@@ -82,7 +87,7 @@
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)
@@ -93,13 +98,117 @@
}
}
+// 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 dbHeadroom,
+ float dbKnee,
+ float ratio,
float attackTime,
float releaseTime,
float preDelayTime,
@@ -119,17 +228,12 @@
float dryMix = 1 - effectBlend;
float wetMix = effectBlend;
- // Threshold and headroom.
- float linearThreshold = decibelsToLinear(dbThreshold);
- float linearHeadroom = decibelsToLinear(dbHeadroom);
+ float k = updateStaticCurveParameters(dbThreshold, dbKnee, ratio);
// Makeup gain.
- float maximum = 1.05f * linearHeadroom * linearThreshold;
- float kk = (maximum - linearThreshold);
- float inverseKK = 1 / kk;
+ float fullRangeGain = saturate(1, k);
+ float fullRangeMakeupGain = 1 / fullRangeGain;
- float fullRangeGain = (linearThreshold + kk * saturate(1 - linearThreshold, 1));
- float fullRangeMakeupGain = 1 / fullRangeGain;
// Empirical/perceptual tuning.
fullRangeMakeupGain = powf(fullRangeMakeupGain, 0.6f);
@@ -141,7 +245,7 @@
// Release parameters.
float releaseFrames = sampleRate * releaseTime;
-
+
// Detector release time.
float satReleaseTime = 0.0025f;
float satReleaseFrames = satReleaseTime * sampleRate;
@@ -170,7 +274,7 @@
// y calculates adaptive release frames depending on the amount of compression.
setPreDelayTime(preDelayTime);
-
+
const int nDivisionFrames = 32;
const int nDivisions = framesToProcess / nDivisionFrames;
@@ -285,9 +389,10 @@
float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
// Put through shaping curve.
- // This is linear up to the threshold, then exponentially approaches the maximum (headroom amount above threshold).
- // The transition from the threshold to the exponential portion is smooth (1st derivative matched).
- float shapedInput = absInput < linearThreshold ? absInput : linearThreshold + kk * saturate(absInput - linearThreshold, inverseKK);
+ // 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;
Modified: trunk/Source/WebCore/platform/audio/DynamicsCompressorKernel.h (109022 => 109023)
--- trunk/Source/WebCore/platform/audio/DynamicsCompressorKernel.h 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/platform/audio/DynamicsCompressorKernel.h 2012-02-27 22:15:11 UTC (rev 109023)
@@ -49,7 +49,8 @@
unsigned framesToProcess,
float dbThreshold,
- float dbHeadroom,
+ float dbKnee,
+ float ratio,
float attackTime,
float releaseTime,
float preDelayTime,
@@ -68,6 +69,8 @@
float sampleRate() const { return m_sampleRate; }
+ float meteringGain() const { return m_meteringGain; }
+
protected:
float m_sampleRate;
@@ -90,6 +93,35 @@
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
Modified: trunk/Source/WebCore/webaudio/DynamicsCompressorNode.cpp (109022 => 109023)
--- trunk/Source/WebCore/webaudio/DynamicsCompressorNode.cpp 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/webaudio/DynamicsCompressorNode.cpp 2012-02-27 22:15:11 UTC (rev 109023)
@@ -43,9 +43,19 @@
{
addInput(adoptPtr(new AudioNodeInput(this)));
addOutput(adoptPtr(new AudioNodeOutput(this, defaultNumberOfOutputChannels)));
-
+
setNodeType(NodeTypeDynamicsCompressor);
-
+
+ m_threshold = AudioParam::create("threshold", -24, -100, 0);
+ m_knee = AudioParam::create("knee", 30, 0, 40);
+ m_ratio = AudioParam::create("ratio", 12, 1, 20);
+ m_reduction = AudioParam::create("reduction", 0, -20, 0);
+
+ m_threshold->setContext(context);
+ m_knee->setContext(context);
+ m_ratio->setContext(context);
+ m_reduction->setContext(context);
+
initialize();
}
@@ -59,7 +69,18 @@
AudioBus* outputBus = output(0)->bus();
ASSERT(outputBus);
+ float threshold = m_threshold->value();
+ float knee = m_knee->value();
+ float ratio = m_ratio->value();
+
+ m_dynamicsCompressor->setParameterValue(DynamicsCompressor::ParamThreshold, threshold);
+ m_dynamicsCompressor->setParameterValue(DynamicsCompressor::ParamKnee, knee);
+ m_dynamicsCompressor->setParameterValue(DynamicsCompressor::ParamRatio, ratio);
+
m_dynamicsCompressor->process(input(0)->bus(), outputBus, framesToProcess);
+
+ float reduction = m_dynamicsCompressor->parameterValue(DynamicsCompressor::ParamReduction);
+ m_reduction->setValue(reduction);
}
void DynamicsCompressorNode::reset()
Modified: trunk/Source/WebCore/webaudio/DynamicsCompressorNode.h (109022 => 109023)
--- trunk/Source/WebCore/webaudio/DynamicsCompressorNode.h 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/webaudio/DynamicsCompressorNode.h 2012-02-27 22:15:11 UTC (rev 109023)
@@ -26,31 +26,44 @@
#define DynamicsCompressorNode_h
#include "AudioNode.h"
+#include "AudioParam.h"
#include <wtf/OwnPtr.h>
namespace WebCore {
class DynamicsCompressor;
-
+
class DynamicsCompressorNode : public AudioNode {
public:
static PassRefPtr<DynamicsCompressorNode> create(AudioContext* context, float sampleRate)
{
- return adoptRef(new DynamicsCompressorNode(context, sampleRate));
+ return adoptRef(new DynamicsCompressorNode(context, sampleRate));
}
-
+
virtual ~DynamicsCompressorNode();
-
+
// AudioNode
virtual void process(size_t framesToProcess);
virtual void reset();
virtual void initialize();
virtual void uninitialize();
+ // Static compression curve parameters.
+ AudioParam* threshold() { return m_threshold.get(); }
+ AudioParam* knee() { return m_knee.get(); }
+ AudioParam* ratio() { return m_ratio.get(); }
+
+ // Amount by which the compressor is currently compressing the signal in decibels.
+ AudioParam* reduction() { return m_reduction.get(); }
+
private:
DynamicsCompressorNode(AudioContext*, float sampleRate);
OwnPtr<DynamicsCompressor> m_dynamicsCompressor;
+ RefPtr<AudioParam> m_threshold;
+ RefPtr<AudioParam> m_knee;
+ RefPtr<AudioParam> m_ratio;
+ RefPtr<AudioParam> m_reduction;
};
} // namespace WebCore
Modified: trunk/Source/WebCore/webaudio/DynamicsCompressorNode.idl (109022 => 109023)
--- trunk/Source/WebCore/webaudio/DynamicsCompressorNode.idl 2012-02-27 22:03:41 UTC (rev 109022)
+++ trunk/Source/WebCore/webaudio/DynamicsCompressorNode.idl 2012-02-27 22:15:11 UTC (rev 109023)
@@ -27,5 +27,9 @@
Conditional=WEB_AUDIO,
JSGenerateToJSObject
] DynamicsCompressorNode : AudioNode {
+ readonly attribute AudioParam threshold; // in Decibels
+ readonly attribute AudioParam knee; // in Decibels
+ readonly attribute AudioParam ratio; // unit-less
+ readonly attribute AudioParam reduction; // in Decibels
};
}