Diff
Added: trunk/LayoutTests/webaudio/convolution-mono-mono-expected.txt (0 => 104476)
--- trunk/LayoutTests/webaudio/convolution-mono-mono-expected.txt (rev 0)
+++ trunk/LayoutTests/webaudio/convolution-mono-mono-expected.txt 2012-01-09 20:22:41 UTC (rev 104476)
@@ -0,0 +1,13 @@
+Tests ConvolverNode processing a mono channel with mono impulse response.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS Initial latency of convolver is silent.
+PASS Triangular portion of convolution is correct.
+PASS First part of tail of convolution is sufficiently small.
+PASS Rendered signal after tail of convolution is silent.
+PASS Test signal was correctly convolved.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/webaudio/convolution-mono-mono.html (0 => 104476)
--- trunk/LayoutTests/webaudio/convolution-mono-mono.html (rev 0)
+++ trunk/LayoutTests/webaudio/convolution-mono-mono.html 2012-01-09 20:22:41 UTC (rev 104476)
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests ConvolverNode processing a mono channel with mono impulse response.");
+
+// To test the convolver, we convolve two square pulses together to
+// produce a triangular pulse. To verify the result is correct we
+// check several parts of the result. First, we make sure the initial
+// part of the result is zero (due to the latency in the convolver).
+// Next, the triangular pulse should match the theoretical result to
+// within some roundoff. After the triangular pulse, the result
+// should be exactly zero, but round-off prevents that. We make sure
+// the part after the pulse is sufficiently close to zero. Finally,
+// the result should be exactly zero because the inputs are exactly
+// zero.
+function runTest() {
+ if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+ }
+
+ window.jsTestIsAsync = true;
+
+ // Create offline audio context.
+ var context = new webkitAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ var squarePulse = createSquarePulseBuffer(context, pulseLengthFrames);
+ var trianglePulse = createTrianglePulseBuffer(context, 2 * pulseLengthFrames);
+
+ var bufferSource = context.createBufferSource();
+ bufferSource.buffer = squarePulse;
+
+ var convolver = context.createConvolver();
+ convolver.normalize = false;
+ convolver.buffer = squarePulse;
+
+ bufferSource.connect(convolver);
+ convolver.connect(context.destination);
+
+ bufferSource.noteOn(0);
+
+ context._oncomplete_ = checkConvolvedResult(trianglePulse);
+ context.startRendering();
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+
+<script src=""
+</body>
+</html>
Added: trunk/LayoutTests/webaudio/resources/convolution-testing.js (0 => 104476)
--- trunk/LayoutTests/webaudio/resources/convolution-testing.js (rev 0)
+++ trunk/LayoutTests/webaudio/resources/convolution-testing.js 2012-01-09 20:22:41 UTC (rev 104476)
@@ -0,0 +1,213 @@
+var sampleRate = 44100.0;
+
+var renderLengthSeconds = 8;
+var pulseLengthSeconds = 1;
+var pulseLengthFrames = pulseLengthSeconds * sampleRate;
+
+// The convolver has a latency of 128 samples in the implementation.
+// We need to take this into account when verifying the output of the
+// convolver. See https://bugs.webkit.org/show_bug.cgi?id=75564.
+var convolveDelaySamples = 128;
+
+function createSquarePulseBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+ var n = audioBuffer.length;
+ var data = ""
+
+ for (var i = 0; i < n; ++i)
+ data[i] = 1;
+
+ return audioBuffer;
+}
+
+// The triangle buffer holds the expected result of the convolution.
+// It linearly ramps up from 0 to its maximum value (at the center)
+// then linearly ramps down to 0. The center value corresponds to the
+// point where the two square pulses overlap the most.
+function createTrianglePulseBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+ var n = audioBuffer.length;
+ var halfLength = n / 2;
+ var data = ""
+
+ for (var i = 0; i < halfLength; ++i)
+ data[i] = i + 1;
+
+ for (var i = halfLength; i < n; ++i)
+ data[i] = n - i - 1;
+
+ return audioBuffer;
+}
+
+// Verify that the initial latency of the convolver is exactly zero.
+// Return true if so.
+function checkLatency(data) {
+ var isZero = true;
+
+ for (var i = 0; i < convolveDelaySamples; ++i) {
+ if (data[i] != 0) {
+ isZero = false;
+ break;
+ }
+ }
+
+ if (isZero) {
+ testPassed("Initial latency of convolver is silent.");
+ } else {
+ testFailed("Initial latency of convolver is not silent.");
+ }
+
+ return isZero;
+}
+
+function log10(x) {
+ return Math.log(x)/Math.LN10;
+}
+
+function linearToDecibel(x) {
+ return 20*log10(x);
+}
+
+// Verify that the rendered result is very close to the reference
+// triangular pulse.
+function checkTriangularPulse(rendered, reference) {
+ var match = true;
+ var maxDelta = 0;
+ var valueAtMaxDelta = 0;
+ var maxDeltaIndex = 0;
+
+ for (var i = 0; i < reference.length; ++i) {
+ var diff = rendered[i + convolveDelaySamples] - reference[i];
+ var x = Math.abs(diff);
+ if (x > maxDelta) {
+ maxDelta = x;
+ valueAtMaxDelta = reference[i];
+ maxDeltaIndex = i;
+ }
+ }
+
+ // allowedDeviationFraction was determined experimentally. It
+ // is the threshold of the relative error at the maximum
+ // difference between the true triangular pulse and the
+ // rendered pulse.
+ var allowedDeviationDecibels = -133.5;
+ var maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta);
+
+ if (maxDeviationDecibels <= allowedDeviationDecibels) {
+ testPassed("Triangular portion of convolution is correct.");
+ } else {
+ testFailed("Triangular portion of convolution is not correct. Max deviation = " + maxDeviationDecibels + " dB at " + maxDeltaIndex);
+ match = false;
+ }
+
+ return match;
+}
+
+// Verify that the rendered data is close to zero for the first part
+// of the tail.
+function checkTail1(data, reference, breakpoint) {
+ var isZero = true;
+ var tail1Max = 0;
+
+ for (var i = reference.length + convolveDelaySamples; i < reference.length + breakpoint; ++i) {
+ var mag = Math.abs(data[i]);
+ if (mag > tail1Max) {
+ tail1Max = mag;
+ }
+ }
+
+ // Let's find the peak of the reference (even though we know a
+ // priori what it is).
+ var refMax = 0;
+ for (var i = 0; i < reference.length; ++i) {
+ refMax = Math.max(refMax, Math.abs(reference[i]));
+ }
+
+ // This threshold is experimentally determined by examining the
+ // value of tail1MaxDecibels.
+ var threshold1 = -146.7;
+
+ var tail1MaxDecibels = linearToDecibel(tail1Max/refMax);
+ if (tail1MaxDecibels <= threshold1) {
+ testPassed("First part of tail of convolution is sufficiently small.");
+ } else {
+ testFailed("First part of tail of convolution is not sufficiently small: " + tail1Max + " dB");
+ isZero = false;
+ }
+
+ return isZero;
+}
+
+// Verify that the second part of the tail of the convolution is
+// exactly zero.
+function checkTail2(data, reference, breakpoint) {
+ var isZero = true;
+ var tail2Max = 0;
+ // For the second part of the tail, the maximum value should be
+ // exactly zero.
+ var threshold2 = 0;
+ for (var i = reference.length + breakpoint; i < data.length; ++i) {
+ if (Math.abs(data[i]) > 0) {
+ isZero = false;
+ break;
+ }
+ }
+
+ if (isZero) {
+ testPassed("Rendered signal after tail of convolution is silent.");
+ } else {
+ testFailed("Rendered signal after tail of convolution should be silent.");
+ }
+
+ return isZero;
+}
+
+function checkConvolvedResult(trianglePulse) {
+ return function(event) {
+ var renderedBuffer = event.renderedBuffer;
+
+ var referenceData = trianglePulse.getChannelData(0);
+ var renderedData = renderedBuffer.getChannelData(0);
+
+ var success = true;
+
+ // Verify the initial part is exactly zero because of the
+ // latency in the convolver.
+
+ success = success && checkLatency(renderedData);
+
+ // Verify the triangular pulse is actually triangular.
+
+ success = success && checkTriangularPulse(renderedData, referenceData);
+
+ // Make sure that portion after convolved portion is totally
+ // silent. But round-off prevents this from being completely
+ // true. At the end of the triangle, it should be close to
+ // zero. If we go farther out, it should be even closer and
+ // eventually zero.
+
+ // For the tail of the convolution (where the result would be
+ // theoretically zero), we partition the tail into two
+ // parts. The first is the at the beginning of the tail,
+ // where we tolerate a small but non-zero value. The second part is
+ // farther along the tail where the result should be zero.
+
+ // breakpoint is the point dividing the first two tail parts
+ // we're looking at. Experimentally determined.
+ var breakpoint = 12800;
+
+ success = success && checkTail1(renderedData, referenceData, breakpoint);
+
+ success = success && checkTail2(renderedData, referenceData, breakpoint);
+
+ if (success) {
+ testPassed("Test signal was correctly convolved.");
+ } else {
+ testFailed("Test signal was not correctly convolved.");
+ }
+
+ finishJSTest();
+ }
+}
Modified: trunk/Source/WebCore/ChangeLog (104475 => 104476)
--- trunk/Source/WebCore/ChangeLog 2012-01-09 20:17:09 UTC (rev 104475)
+++ trunk/Source/WebCore/ChangeLog 2012-01-09 20:22:41 UTC (rev 104476)
@@ -1,3 +1,33 @@
+2012-01-09 Raymond Toy <[email protected]>
+
+ Add normalize attribute to ConvolverNode to disable normalization.
+ https://bugs.webkit.org/show_bug.cgi?id=75126
+
+ Reviewed by Kenneth Russell.
+
+ Tests added in convolution-mono-mono.html.
+
+ * platform/audio/Reverb.cpp:
+ (WebCore::Reverb::Reverb): Add extra arg to indicate whether
+ normalization is enabled or not, and do it.
+ * platform/audio/Reverb.h: Update declaration.
+ * webaudio/ConvolverNode.cpp:
+ (WebCore::ConvolverNode::ConvolverNode): Initialize attribute (to
+ true).
+ (WebCore::ConvolverNode::setBuffer): Call Reverb with
+ normalization argument.
+ * webaudio/ConvolverNode.h:
+ (WebCore::ConvolverNode::normalize): New method to return
+ normalization.
+ (WebCore::ConvolverNode::setNormalize): New method to set
+ normalization.
+ * webaudio/ConvolverNode.idl: Add normalize attribute.
+ * LayoutTests/webaudio/convolution-mono-mono.html:
+ * LayoutTests/webaudio/convolution-mono-mono-expected.txt:
+ * LayoutTests/webaudio/resources/convolution-testing.js:
+ Test for convolution. Tests only work when normalization is
+ disabled.
+
2012-01-09 Judy Hao <[email protected]>
[GStreamer] webkitwebsrc: pad template is leaked
Modified: trunk/Source/WebCore/platform/audio/Reverb.cpp (104475 => 104476)
--- trunk/Source/WebCore/platform/audio/Reverb.cpp 2012-01-09 20:17:09 UTC (rev 104475)
+++ trunk/Source/WebCore/platform/audio/Reverb.cpp 2012-01-09 20:22:41 UTC (rev 104476)
@@ -87,16 +87,23 @@
return scale;
}
-Reverb::Reverb(AudioBus* impulseResponse, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads)
+Reverb::Reverb(AudioBus* impulseResponse, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads, bool normalize)
{
- double scale = calculateNormalizationScale(impulseResponse);
- if (scale)
- impulseResponse->scale(scale);
+ double scale = 1;
+ if (normalize) {
+ scale = calculateNormalizationScale(impulseResponse);
+
+ if (scale)
+ impulseResponse->scale(scale);
+ }
+
initialize(impulseResponse, renderSliceSize, maxFFTSize, numberOfChannels, useBackgroundThreads);
- // Undo scaling since this shouldn't be a destructive operation on impulseResponse
- if (scale)
+ // Undo scaling since this shouldn't be a destructive operation on impulseResponse.
+ // FIXME: What about roundoff? Perhaps consider making a temporary scaled copy
+ // instead of scaling and unscaling in place.
+ if (normalize && scale)
impulseResponse->scale(1.0 / scale);
}
Modified: trunk/Source/WebCore/platform/audio/Reverb.h (104475 => 104476)
--- trunk/Source/WebCore/platform/audio/Reverb.h 2012-01-09 20:17:09 UTC (rev 104475)
+++ trunk/Source/WebCore/platform/audio/Reverb.h 2012-01-09 20:22:41 UTC (rev 104476)
@@ -43,7 +43,7 @@
enum { MaxFrameSize = 256 };
// renderSliceSize is a rendering hint, so the FFTs can be optimized to not all occur at the same time (very bad when rendering on a real-time thread).
- Reverb(AudioBus* impulseResponseBuffer, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads);
+ Reverb(AudioBus* impulseResponseBuffer, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads, bool normalize);
void process(AudioBus* sourceBus, AudioBus* destinationBus, size_t framesToProcess);
void reset();
Modified: trunk/Source/WebCore/webaudio/ConvolverNode.cpp (104475 => 104476)
--- trunk/Source/WebCore/webaudio/ConvolverNode.cpp 2012-01-09 20:17:09 UTC (rev 104475)
+++ trunk/Source/WebCore/webaudio/ConvolverNode.cpp 2012-01-09 20:22:41 UTC (rev 104476)
@@ -47,6 +47,7 @@
ConvolverNode::ConvolverNode(AudioContext* context, float sampleRate)
: AudioNode(context, sampleRate)
+ , m_normalize(true)
{
addInput(adoptPtr(new AudioNodeInput(this)));
addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
@@ -134,7 +135,7 @@
// Create the reverb with the given impulse response.
bool useBackgroundThreads = !context()->isOfflineContext();
- OwnPtr<Reverb> reverb = adoptPtr(new Reverb(&bufferBus, AudioNode::ProcessingSizeInFrames, MaxFFTSize, 2, useBackgroundThreads));
+ OwnPtr<Reverb> reverb = adoptPtr(new Reverb(&bufferBus, AudioNode::ProcessingSizeInFrames, MaxFFTSize, 2, useBackgroundThreads, m_normalize));
{
// Synchronize with process().
Modified: trunk/Source/WebCore/webaudio/ConvolverNode.h (104475 => 104476)
--- trunk/Source/WebCore/webaudio/ConvolverNode.h 2012-01-09 20:17:09 UTC (rev 104475)
+++ trunk/Source/WebCore/webaudio/ConvolverNode.h 2012-01-09 20:22:41 UTC (rev 104476)
@@ -54,6 +54,8 @@
void setBuffer(AudioBuffer*);
AudioBuffer* buffer();
+ bool normalize() const { return m_normalize; }
+ void setNormalize(bool normalize) { m_normalize = normalize; }
private:
ConvolverNode(AudioContext*, float sampleRate);
@@ -62,6 +64,9 @@
// This synchronizes dynamic changes to the convolution impulse response with process().
mutable Mutex m_processLock;
+
+ // Normalize the impulse response or not. Must default to true.
+ bool m_normalize;
};
} // namespace WebCore
Modified: trunk/Source/WebCore/webaudio/ConvolverNode.idl (104475 => 104476)
--- trunk/Source/WebCore/webaudio/ConvolverNode.idl 2012-01-09 20:17:09 UTC (rev 104475)
+++ trunk/Source/WebCore/webaudio/ConvolverNode.idl 2012-01-09 20:22:41 UTC (rev 104476)
@@ -29,5 +29,6 @@
GenerateToJS
] ConvolverNode : AudioNode {
attribute [JSCCustomSetter] AudioBuffer buffer;
+ attribute boolean normalize;
};
}