Bug 815643 - Part 8: Import convolution tests from Blink; r=roc
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 10 Jun 2013 16:09:53 -0400
changeset 134592 c78350f78192
parent 134591 7cb2712f0237
child 134593 63386b71d1b5
push id29285
push usereakhgari@mozilla.com
push dateTue, 11 Jun 2013 00:10:14 +0000
treeherdermozilla-inbound@63386b71d1b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs815643, 150518
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 815643 - Part 8: Import convolution tests from Blink; r=roc Imported from Blink SVN revision 150518
content/media/webaudio/test/blink/convolution-testing.js
content/media/webaudio/test/test_convolverNode_mono_mono.html
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/blink/convolution-testing.js
@@ -0,0 +1,182 @@
+var sampleRate = 44100.0;
+
+var renderLengthSeconds = 8;
+var pulseLengthSeconds = 1;
+var pulseLengthFrames = pulseLengthSeconds * sampleRate;
+
+function createSquarePulseBuffer(context, sampleFrameLength) {
+    var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+    var n = audioBuffer.length;
+    var data = audioBuffer.getChannelData(0);
+
+    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 = audioBuffer.getChannelData(0);
+    
+    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;
+}
+
+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] - 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; 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 = -129.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: " + tail1MaxDecibels + " 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 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();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_convolverNode_mono_mono.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script type="text/javascript" src="resources/audio-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/convolution-testing.js"></script>
+</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.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+    
+    window.jsTestIsAsync = true;
+        
+    // Create offline audio context.
+    var context = new webkitOfflineAudioContext(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="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>