| /* |
| Returns an array (typed or not), of the passed array with removed trailing and ending |
| zero-valued elements |
| */ |
| function trimEmptyElements(array) { |
| var start = 0; |
| var end = array.length; |
| |
| while (start < array.length) { |
| if (array[start] !== 0) { |
| break; |
| } |
| start++; |
| } |
| |
| while (end > 0) { |
| end--; |
| if (array[end] !== 0) { |
| break; |
| } |
| } |
| return array.subarray(start, end); |
| } |
| |
| |
| function fuzzyCompare(a, b) { |
| return Math.abs(a - b) < 9e-3; |
| } |
| |
| function compareChannels(buf1, buf2, |
| /*optional*/ length, |
| /*optional*/ sourceOffset, |
| /*optional*/ destOffset, |
| /*optional*/ skipLengthCheck) { |
| if (!skipLengthCheck) { |
| assert_equals(buf1.length, buf2.length, "Channels must have the same length"); |
| } |
| sourceOffset = sourceOffset || 0; |
| destOffset = destOffset || 0; |
| if (length == undefined) { |
| length = buf1.length - sourceOffset; |
| } |
| var difference = 0; |
| var maxDifference = 0; |
| var firstBadIndex = -1; |
| for (var i = 0; i < length; ++i) { |
| if (!fuzzyCompare(buf1[i + sourceOffset], buf2[i + destOffset])) { |
| difference++; |
| maxDifference = Math.max(maxDifference, Math.abs(buf1[i + sourceOffset] - buf2[i + destOffset])); |
| if (firstBadIndex == -1) { |
| firstBadIndex = i; |
| } |
| } |
| }; |
| |
| assert_equals(difference, 0, "maxDifference: " + maxDifference + |
| ", first bad index: " + firstBadIndex + " with test-data offset " + |
| sourceOffset + " and expected-data offset " + destOffset + |
| "; corresponding values " + buf1[firstBadIndex + sourceOffset] + " and " + |
| buf2[firstBadIndex + destOffset] + " --- differences"); |
| } |
| |
| function compareBuffers(got, expected) { |
| if (got.numberOfChannels != expected.numberOfChannels) { |
| assert_equals(got.numberOfChannels, expected.numberOfChannels, |
| "Correct number of buffer channels"); |
| return; |
| } |
| if (got.length != expected.length) { |
| assert_equals(got.length, expected.length, |
| "Correct buffer length"); |
| return; |
| } |
| if (got.sampleRate != expected.sampleRate) { |
| assert_equals(got.sampleRate, expected.sampleRate, |
| "Correct sample rate"); |
| return; |
| } |
| |
| for (var i = 0; i < got.numberOfChannels; ++i) { |
| compareChannels(got.getChannelData(i), expected.getChannelData(i), |
| got.length, 0, 0, true); |
| } |
| } |
| |
| /** |
| * This function assumes that the test is a "single page test" [0], and defines a |
| * single gTest variable with the following properties and methods: |
| * |
| * + numberOfChannels: optional property which specifies the number of channels |
| * in the output. The default value is 2. |
| * + createGraph: mandatory method which takes a context object and does |
| * everything needed in order to set up the Web Audio graph. |
| * This function returns the node to be inspected. |
| * + createGraphAsync: async version of createGraph. This function takes |
| * a callback which should be called with an argument |
| * set to the node to be inspected when the callee is |
| * ready to proceed with the test. Either this function |
| * or createGraph must be provided. |
| * + createExpectedBuffers: optional method which takes a context object and |
| * returns either one expected buffer or an array of |
| * them, designating what is expected to be observed |
| * in the output. If omitted, the output is expected |
| * to be silence. All buffers must have the same |
| * length, which must be a bufferSize supported by |
| * ScriptProcessorNode. This function is guaranteed |
| * to be called before createGraph. |
| * + length: property equal to the total number of frames which we are waiting |
| * to see in the output, mandatory if createExpectedBuffers is not |
| * provided, in which case it must be a bufferSize supported by |
| * ScriptProcessorNode (256, 512, 1024, 2048, 4096, 8192, or 16384). |
| * If createExpectedBuffers is provided then this must be equal to |
| * the number of expected buffers * the expected buffer length. |
| * |
| * + skipOfflineContextTests: optional. when true, skips running tests on an offline |
| * context by circumventing testOnOfflineContext. |
| * |
| * [0]: http://web-platform-tests.org/writing-tests/testharness-api.html#single-page-tests |
| */ |
| function runTest(name) |
| { |
| function runTestFunction () { |
| if (!gTest.numberOfChannels) { |
| gTest.numberOfChannels = 2; // default |
| } |
| |
| var testLength; |
| |
| function runTestOnContext(context, callback, testOutput) { |
| if (!gTest.createExpectedBuffers) { |
| // Assume that the output is silence |
| var expectedBuffers = getEmptyBuffer(context, gTest.length); |
| } else { |
| var expectedBuffers = gTest.createExpectedBuffers(context); |
| } |
| if (!(expectedBuffers instanceof Array)) { |
| expectedBuffers = [expectedBuffers]; |
| } |
| var expectedFrames = 0; |
| for (var i = 0; i < expectedBuffers.length; ++i) { |
| assert_equals(expectedBuffers[i].numberOfChannels, gTest.numberOfChannels, |
| "Correct number of channels for expected buffer " + i); |
| expectedFrames += expectedBuffers[i].length; |
| } |
| if (gTest.length && gTest.createExpectedBuffers) { |
| assert_equals(expectedFrames, |
| gTest.length, "Correct number of expected frames"); |
| } |
| |
| if (gTest.createGraphAsync) { |
| gTest.createGraphAsync(context, function(nodeToInspect) { |
| testOutput(nodeToInspect, expectedBuffers, callback); |
| }); |
| } else { |
| testOutput(gTest.createGraph(context), expectedBuffers, callback); |
| } |
| } |
| |
| function testOnNormalContext(callback) { |
| function testOutput(nodeToInspect, expectedBuffers, callback) { |
| testLength = 0; |
| var sp = context.createScriptProcessor(expectedBuffers[0].length, gTest.numberOfChannels, 0); |
| nodeToInspect.connect(sp); |
| sp.onaudioprocess = function(e) { |
| var expectedBuffer = expectedBuffers.shift(); |
| testLength += expectedBuffer.length; |
| compareBuffers(e.inputBuffer, expectedBuffer); |
| if (expectedBuffers.length == 0) { |
| sp.onaudioprocess = null; |
| callback(); |
| } |
| }; |
| } |
| var context = new AudioContext(); |
| runTestOnContext(context, callback, testOutput); |
| } |
| |
| function testOnOfflineContext(callback, sampleRate) { |
| function testOutput(nodeToInspect, expectedBuffers, callback) { |
| nodeToInspect.connect(context.destination); |
| context.oncomplete = function(e) { |
| var samplesSeen = 0; |
| while (expectedBuffers.length) { |
| var expectedBuffer = expectedBuffers.shift(); |
| assert_equals(e.renderedBuffer.numberOfChannels, expectedBuffer.numberOfChannels, |
| "Correct number of input buffer channels"); |
| for (var i = 0; i < e.renderedBuffer.numberOfChannels; ++i) { |
| compareChannels(e.renderedBuffer.getChannelData(i), |
| expectedBuffer.getChannelData(i), |
| expectedBuffer.length, |
| samplesSeen, |
| undefined, |
| true); |
| } |
| samplesSeen += expectedBuffer.length; |
| } |
| callback(); |
| }; |
| context.startRendering(); |
| } |
| |
| var context = new OfflineAudioContext(gTest.numberOfChannels, testLength, sampleRate); |
| runTestOnContext(context, callback, testOutput); |
| } |
| |
| testOnNormalContext(function() { |
| if (!gTest.skipOfflineContextTests) { |
| testOnOfflineContext(function() { |
| testOnOfflineContext(done, 44100); |
| }, 48000); |
| } else { |
| done(); |
| } |
| }); |
| }; |
| |
| runTestFunction(); |
| } |