michael@0: // Helpers for Web Audio tests michael@0: michael@0: function expectException(func, exceptionCode) { michael@0: var threw = false; michael@0: try { michael@0: func(); michael@0: } catch (ex) { michael@0: threw = true; michael@0: ok(ex instanceof DOMException, "Expect a DOM exception"); michael@0: is(ex.code, exceptionCode, "Expect the correct exception code"); michael@0: } michael@0: ok(threw, "The exception was thrown"); michael@0: } michael@0: michael@0: function expectNoException(func) { michael@0: var threw = false; michael@0: try { michael@0: func(); michael@0: } catch (ex) { michael@0: threw = true; michael@0: } michael@0: ok(!threw, "An exception was not thrown"); michael@0: } michael@0: michael@0: function expectTypeError(func) { michael@0: var threw = false; michael@0: try { michael@0: func(); michael@0: } catch (ex) { michael@0: threw = true; michael@0: ok(ex instanceof TypeError, "Expect a TypeError"); michael@0: } michael@0: ok(threw, "The exception was thrown"); michael@0: } michael@0: michael@0: function fuzzyCompare(a, b) { michael@0: return Math.abs(a - b) < 9e-3; michael@0: } michael@0: michael@0: function compareChannels(buf1, buf2, michael@0: /*optional*/ length, michael@0: /*optional*/ sourceOffset, michael@0: /*optional*/ destOffset, michael@0: /*optional*/ skipLengthCheck) { michael@0: if (!skipLengthCheck) { michael@0: is(buf1.length, buf2.length, "Channels must have the same length"); michael@0: } michael@0: sourceOffset = sourceOffset || 0; michael@0: destOffset = destOffset || 0; michael@0: if (length == undefined) { michael@0: length = buf1.length - sourceOffset; michael@0: } michael@0: var difference = 0; michael@0: var maxDifference = 0; michael@0: var firstBadIndex = -1; michael@0: for (var i = 0; i < length; ++i) { michael@0: if (!fuzzyCompare(buf1[i + sourceOffset], buf2[i + destOffset])) { michael@0: difference++; michael@0: maxDifference = Math.max(maxDifference, Math.abs(buf1[i + sourceOffset] - buf2[i + destOffset])); michael@0: if (firstBadIndex == -1) { michael@0: firstBadIndex = i; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: is(difference, 0, "maxDifference: " + maxDifference + michael@0: ", first bad index: " + firstBadIndex + michael@0: " with test-data offset " + sourceOffset + " and expected-data offset " + michael@0: destOffset + "; corresponding values " + buf1[firstBadIndex + sourceOffset] + michael@0: " and " + buf2[firstBadIndex + destOffset] + " --- differences"); michael@0: } michael@0: michael@0: function compareBuffers(got, expected) { michael@0: if (got.numberOfChannels != expected.numberOfChannels) { michael@0: is(got.numberOfChannels, expected.numberOfChannels, michael@0: "Correct number of buffer channels"); michael@0: return; michael@0: } michael@0: if (got.length != expected.length) { michael@0: is(got.length, expected.length, michael@0: "Correct buffer length"); michael@0: return; michael@0: } michael@0: if (got.sampleRate != expected.sampleRate) { michael@0: is(got.sampleRate, expected.sampleRate, michael@0: "Correct sample rate"); michael@0: return; michael@0: } michael@0: michael@0: for (var i = 0; i < got.numberOfChannels; ++i) { michael@0: compareChannels(got.getChannelData(i), expected.getChannelData(i), michael@0: got.length, 0, 0, true); michael@0: } michael@0: } michael@0: michael@0: function getEmptyBuffer(context, length) { michael@0: return context.createBuffer(gTest.numberOfChannels, length, context.sampleRate); michael@0: } michael@0: michael@0: /** michael@0: * This function assumes that the test file defines a single gTest variable with michael@0: * the following properties and methods: michael@0: * michael@0: * + numberOfChannels: optional property which specifies the number of channels michael@0: * in the output. The default value is 2. michael@0: * + createGraph: mandatory method which takes a context object and does michael@0: * everything needed in order to set up the Web Audio graph. michael@0: * This function returns the node to be inspected. michael@0: * + createGraphAsync: async version of createGraph. This function takes michael@0: * a callback which should be called with an argument michael@0: * set to the node to be inspected when the callee is michael@0: * ready to proceed with the test. Either this function michael@0: * or createGraph must be provided. michael@0: * + createExpectedBuffers: optional method which takes a context object and michael@0: * returns either one expected buffer or an array of michael@0: * them, designating what is expected to be observed michael@0: * in the output. If omitted, the output is expected michael@0: * to be silence. All buffers must have the same michael@0: * length, which must be a bufferSize supported by michael@0: * ScriptProcessorNode. This function is guaranteed michael@0: * to be called before createGraph. michael@0: * + length: property equal to the total number of frames which we are waiting michael@0: * to see in the output, mandatory if createExpectedBuffers is not michael@0: * provided, in which case it must be a bufferSize supported by michael@0: * ScriptProcessorNode (256, 512, 1024, 2048, 4096, 8192, or 16384). michael@0: * If createExpectedBuffers is provided then this must be equal to michael@0: * the number of expected buffers * the expected buffer length. michael@0: * michael@0: * + skipOfflineContextTests: optional. when true, skips running tests on an offline michael@0: * context by circumventing testOnOfflineContext. michael@0: */ michael@0: function runTest() michael@0: { michael@0: function done() { michael@0: SimpleTest.finish(); michael@0: } michael@0: michael@0: SimpleTest.waitForExplicitFinish(); michael@0: function runTestFunction () { michael@0: if (!gTest.numberOfChannels) { michael@0: gTest.numberOfChannels = 2; // default michael@0: } michael@0: michael@0: var testLength; michael@0: michael@0: function runTestOnContext(context, callback, testOutput) { michael@0: if (!gTest.createExpectedBuffers) { michael@0: // Assume that the output is silence michael@0: var expectedBuffers = getEmptyBuffer(context, gTest.length); michael@0: } else { michael@0: var expectedBuffers = gTest.createExpectedBuffers(context); michael@0: } michael@0: if (!(expectedBuffers instanceof Array)) { michael@0: expectedBuffers = [expectedBuffers]; michael@0: } michael@0: var expectedFrames = 0; michael@0: for (var i = 0; i < expectedBuffers.length; ++i) { michael@0: is(expectedBuffers[i].numberOfChannels, gTest.numberOfChannels, michael@0: "Correct number of channels for expected buffer " + i); michael@0: expectedFrames += expectedBuffers[i].length; michael@0: } michael@0: if (gTest.length && gTest.createExpectedBuffers) { michael@0: is(expectedFrames, gTest.length, "Correct number of expected frames"); michael@0: } michael@0: michael@0: if (gTest.createGraphAsync) { michael@0: gTest.createGraphAsync(context, function(nodeToInspect) { michael@0: testOutput(nodeToInspect, expectedBuffers, callback); michael@0: }); michael@0: } else { michael@0: testOutput(gTest.createGraph(context), expectedBuffers, callback); michael@0: } michael@0: } michael@0: michael@0: function testOnNormalContext(callback) { michael@0: function testOutput(nodeToInspect, expectedBuffers, callback) { michael@0: testLength = 0; michael@0: var sp = context.createScriptProcessor(expectedBuffers[0].length, gTest.numberOfChannels); michael@0: nodeToInspect.connect(sp); michael@0: sp.connect(context.destination); michael@0: sp.onaudioprocess = function(e) { michael@0: var expectedBuffer = expectedBuffers.shift(); michael@0: testLength += expectedBuffer.length; michael@0: compareBuffers(e.inputBuffer, expectedBuffer); michael@0: if (expectedBuffers.length == 0) { michael@0: sp.onaudioprocess = null; michael@0: callback(); michael@0: } michael@0: }; michael@0: } michael@0: var context = new AudioContext(); michael@0: runTestOnContext(context, callback, testOutput); michael@0: } michael@0: michael@0: function testOnOfflineContext(callback, sampleRate) { michael@0: function testOutput(nodeToInspect, expectedBuffers, callback) { michael@0: nodeToInspect.connect(context.destination); michael@0: context.oncomplete = function(e) { michael@0: var samplesSeen = 0; michael@0: while (expectedBuffers.length) { michael@0: var expectedBuffer = expectedBuffers.shift(); michael@0: is(e.renderedBuffer.numberOfChannels, expectedBuffer.numberOfChannels, michael@0: "Correct number of input buffer channels"); michael@0: for (var i = 0; i < e.renderedBuffer.numberOfChannels; ++i) { michael@0: compareChannels(e.renderedBuffer.getChannelData(i), michael@0: expectedBuffer.getChannelData(i), michael@0: expectedBuffer.length, michael@0: samplesSeen, michael@0: undefined, michael@0: true); michael@0: } michael@0: samplesSeen += expectedBuffer.length; michael@0: } michael@0: callback(); michael@0: }; michael@0: context.startRendering(); michael@0: } michael@0: michael@0: var context = new OfflineAudioContext(gTest.numberOfChannels, testLength, sampleRate); michael@0: runTestOnContext(context, callback, testOutput); michael@0: } michael@0: michael@0: testOnNormalContext(function() { michael@0: if (!gTest.skipOfflineContextTests) { michael@0: testOnOfflineContext(function() { michael@0: testOnOfflineContext(done, 44100); michael@0: }, 48000); michael@0: } else { michael@0: done(); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: if (document.readyState !== 'complete') { michael@0: addLoadEvent(runTestFunction); michael@0: } else { michael@0: runTestFunction(); michael@0: } michael@0: }