Index: LayoutTests/webaudio/resources/oscillator-testing.js |
diff --git a/LayoutTests/webaudio/resources/oscillator-testing.js b/LayoutTests/webaudio/resources/oscillator-testing.js |
index 68e6b9fe14c809f9601f6ba9e7fdad25e8452de0..131910244740a37489c6c21a1148b0ad9cb712db 100644 |
--- a/LayoutTests/webaudio/resources/oscillator-testing.js |
+++ b/LayoutTests/webaudio/resources/oscillator-testing.js |
@@ -9,6 +9,31 @@ |
// |
// QUESTION: Why does the very end of the generated signal appear to get slightly weaker? |
// ANSWER: This is an artifact of the algorithm to avoid aliasing. |
+// |
+// QUESTION: Since the tests compare the actual result with an expected reference file, how are the |
+// reference files created? |
+// ANSWER: Create an html with the following contents in the webaudio directory. Then run a layout |
+// test on this file. A new file names "<file>-actual.wav" is created that contains the new result |
+// that can be used as the new expected reference file. Replace the "sine" below with the |
+// oscillator type that you want to use. |
+// |
+// <!doctype html> |
+// <html> |
+// <head> |
+// <script src="resources/compatibility.js"></script> |
+// <script src="resources/buffer-loader.js"></script> |
+// <script src="../resources/js-test.js"></script> |
+// <script src="resources/oscillator-testing.js"></script> |
+// <script src="resources/audio-testing.js"></script> |
+// </head> |
+// <body> |
+// <script> |
+// OscillatorTestingUtils.createNewReference("sine"); |
+// </script> |
+// </body> |
+// </html> |
+ |
+OscillatorTestingUtils = (function () { |
var sampleRate = 44100.0; |
var nyquist = 0.5 * sampleRate; |
@@ -17,13 +42,47 @@ var lowFrequency = 10; |
var highFrequency = nyquist + 2000; // go slightly higher than nyquist to make sure we generate silence there |
var context = 0; |
+// Scaling factor for converting the 16-bit WAV data to float (and vice-versa). |
+var waveScaleFactor = 32768; |
+ |
+// Thresholds for verifying the test passes. The thresholds are experimentally determined. The |
+// default values here will cause the test to fail, which is useful for determining new thresholds, |
+// if needed. |
+ |
+// SNR must be greater than this to pass the test. |
+// Q: Why is the SNR threshold not infinity? |
+// A: The reference result is a 16-bit WAV file, so it won't compare exactly with the |
+// floating point result. |
+var thresholdSNR = 10000; |
+ |
+// Max diff must be less than this to pass the test. |
+var thresholdDiff = 0; |
+ |
+// Count the number of differences between the expected and actual result. The tests passes |
+// if the count is less than this threshold. |
+var thresholdDiffCount = 0; |
+ |
+// Mostly for debugging |
+ |
+// An AudioBuffer for the reference (expected) result. |
+var reference = 0; |
+ |
+// The actual rendered data produced by the test. |
+var renderedData = 0; |
+ |
+// Signal power of the reference |
+var signalPower = 0; |
+ |
+// Noise power of the difference between the reference and actual result. |
+var noisePower = 0; |
+ |
function generateExponentialOscillatorSweep(context, oscillatorType) { |
var osc = context.createOscillator(); |
if (oscillatorType == "custom") { |
// Create a simple waveform with three Fourier coefficients. |
// Note the first values are expected to be zero (DC for coeffA and Nyquist for coeffB). |
var coeffA = new Float32Array([0, 1, 0.5]); |
- var coeffB = new Float32Array([0, 0, 0]); |
+ var coeffB = new Float32Array([0, 0, 0]); |
var wave = context.createPeriodicWave(coeffA, coeffB); |
osc.setPeriodicWave(wave); |
} else { |
@@ -38,7 +97,117 @@ function generateExponentialOscillatorSweep(context, oscillatorType) { |
osc.start(0); |
- var nyquist = 0.5 * sampleRate; |
osc.frequency.setValueAtTime(10, 0); |
osc.frequency.exponentialRampToValueAtTime(highFrequency, lengthInSeconds); |
} |
+ |
+function calculateSNR(sPower, nPower) |
+{ |
+ if (nPower == 0 && sPower > 0) { |
+ return 1000; |
+ } |
+ return 10 * Math.log10(sPower / nPower); |
+} |
+ |
+function loadReferenceAndRunTest(oscType) { |
+ var bufferLoader = new BufferLoader( |
+ context, |
+ [ "oscillator-" + oscType + "-expected.wav" ], |
+ function (bufferList) { |
+ reference = bufferList[0].getChannelData(0); |
+ generateExponentialOscillatorSweep(context, oscType); |
+ context.oncomplete = checkResult; |
+ context.startRendering(); |
+ }); |
+ |
+ bufferLoader.load(); |
+} |
+ |
+function checkResult (event) { |
+ renderedData = event.renderedBuffer.getChannelData(0); |
+ // Compute signal to noise ratio between the result and the reference. Also keep track |
+ // of the max difference (and position). |
+ |
+ var maxError = -1; |
+ var errorPosition = -1; |
+ var diffCount = 0; |
+ |
+ for (var k = 0; k < renderedData.length; ++k) { |
+ var diff = renderedData[k] - reference[k]; |
+ noisePower += diff * diff; |
+ signalPower += reference[k] * reference[k]; |
+ if (Math.abs(diff) > maxError) { |
+ maxError = Math.abs(diff); |
+ errorPosition = k; |
+ } |
+ // The reference file is a 16-bit WAV file, so we will almost never get an exact match |
+ // between it and the actual floating-point result. |
+ if (diff > 1/waveScaleFactor) { |
+ diffCount++; |
+ } |
+ } |
+ |
+ var snr = calculateSNR(signalPower, noisePower); |
+ if (snr >= thresholdSNR) { |
+ testPassed("Exceeded SNR threshold of " + thresholdSNR + " dB"); |
+ } else { |
+ testFailed("Expected SNR of " + thresholdSNR + " dB, but actual SNR is " + snr + " dB"); |
+ } |
+ |
+ if (maxError <= thresholdDiff) { |
+ testPassed("Maximum difference below threshold of " |
+ + (thresholdDiff * waveScaleFactor) + " ulp (16-bits)"); |
+ } else { |
+ testFailed("Maximum difference of " + (maxError * waveScaleFactor) + " at " |
+ + errorPosition + " exceeded threshold of " + (thresholdDiff * waveScaleFactor) |
+ + " ulp (16-bits)"); |
+ } |
+ |
+ if (diffCount <= thresholdDiffCount) { |
+ testPassed("Number of differences between actual and expected result is less than " + thresholdDiffCount); |
+ } else { |
+ testFailed(diffCount + " differences found but expected no more than " + thresholdDiffCount); |
+ } |
+ |
+ finishJSTest(); |
+} |
+ |
+function setThresholds(thresholds) { |
+ thresholdSNR = thresholds.snr; |
+ thresholdDiff = thresholds.maxDiff / waveScaleFactor; |
+ thresholdDiffCount = thresholds.diffCount; |
+} |
+ |
+function runTest(oscType) { |
+ window.jsTestIsAsync = true; |
+ context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRate); |
+ loadReferenceAndRunTest(oscType); |
+} |
+ |
+function createNewReference(oscType) { |
+ if (!window.testRunner) |
+ return; |
+ |
+ context = new OfflineAudioContext(1, sampleRate * lengthInSeconds, sampleRate); |
+ generateExponentialOscillatorSweep(context, oscType); |
+ |
+ context.oncomplete = finishAudioTest; |
+ context.startRendering(); |
+ |
+ testRunner.waitUntilDone(); |
+} |
+ |
+return { |
+ sampleRate: sampleRate, |
+ lengthInSeconds: lengthInSeconds, |
+ thresholdSNR: thresholdSNR, |
+ thresholdDiff: thresholdDiff, |
+ thresholdDiffCount: thresholdDiffCount, |
+ waveScaleFactor: waveScaleFactor, |
+ setThresholds: setThresholds, |
+ loadReferenceAndRunTest: loadReferenceAndRunTest, |
+ runTest: runTest, |
+ createNewReference: createNewReference, |
+}; |
+ |
+}()); |