Title: [104476] trunk
Revision
104476
Author
[email protected]
Date
2012-01-09 12:22:41 -0800 (Mon, 09 Jan 2012)

Log Message

Add normalize attribute to ConvolverNode to disable normalization.
https://bugs.webkit.org/show_bug.cgi?id=75126

Patch by Raymond Toy <[email protected]> on 2012-01-09
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.

Modified Paths

Added Paths

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;
     };
 }
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to