summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webaudio/js')
-rw-r--r--testing/web-platform/tests/webaudio/js/buffer-loader.js44
-rw-r--r--testing/web-platform/tests/webaudio/js/helpers.js250
-rw-r--r--testing/web-platform/tests/webaudio/js/worklet-recorder.js55
3 files changed, 349 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/js/buffer-loader.js b/testing/web-platform/tests/webaudio/js/buffer-loader.js
new file mode 100644
index 0000000000..453dc4a521
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/js/buffer-loader.js
@@ -0,0 +1,44 @@
+/* Taken from
+ https://raw.github.com/WebKit/webkit/master/LayoutTests/webaudio/resources/buffer-loader.js */
+
+function BufferLoader(context, urlList, callback) {
+ this.context = context;
+ this.urlList = urlList;
+ this.onload = callback;
+ this.bufferList = new Array();
+ this.loadCount = 0;
+}
+
+BufferLoader.prototype.loadBuffer = function(url, index) {
+ // Load buffer asynchronously
+ var request = new XMLHttpRequest();
+ request.open("GET", url, true);
+ request.responseType = "arraybuffer";
+
+ var loader = this;
+
+ request.onload = function() {
+ loader.context.decodeAudioData(request.response, decodeSuccessCallback, decodeErrorCallback);
+ };
+
+ request.onerror = function() {
+ alert('BufferLoader: XHR error');
+ };
+
+ var decodeSuccessCallback = function(buffer) {
+ loader.bufferList[index] = buffer;
+ if (++loader.loadCount == loader.urlList.length)
+ loader.onload(loader.bufferList);
+ };
+
+ var decodeErrorCallback = function() {
+ alert('decodeErrorCallback: decode error');
+ };
+
+ request.send();
+}
+
+BufferLoader.prototype.load = function() {
+ for (var i = 0; i < this.urlList.length; ++i)
+ this.loadBuffer(this.urlList[i], i);
+}
diff --git a/testing/web-platform/tests/webaudio/js/helpers.js b/testing/web-platform/tests/webaudio/js/helpers.js
new file mode 100644
index 0000000000..413c72051b
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/js/helpers.js
@@ -0,0 +1,250 @@
+/*
+ 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]: https://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, 1);
+ nodeToInspect.connect(sp).connect(context.destination);
+ 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();
+}
+
+// Simpler than audit.js, but still logs the message. Requires
+// `setup("explicit_done": true)` if testing code that runs after the "load"
+// event.
+function equals(a, b, msg) {
+ test(function() {
+ assert_equals(a, b);
+ }, msg);
+}
+function is_true(a, msg) {
+ test(function() {
+ assert_true(a);
+ }, msg);
+}
+
+// This allows writing AudioWorkletProcessor code in the same file as the rest
+// of the test, for quick one off AudioWorkletProcessor testing.
+function URLFromScriptsElements(ids)
+{
+ var scriptTexts = [];
+ for (let id of ids) {
+
+ const e = document.querySelector("script#"+id)
+ if (!e) {
+ throw id+" is not the id of a <script> tag";
+ }
+ scriptTexts.push(e.innerText);
+ }
+ const blob = new Blob(scriptTexts, {type: "application/javascript"});
+
+ return URL.createObjectURL(blob);
+}
diff --git a/testing/web-platform/tests/webaudio/js/worklet-recorder.js b/testing/web-platform/tests/webaudio/js/worklet-recorder.js
new file mode 100644
index 0000000000..913ab742aa
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/js/worklet-recorder.js
@@ -0,0 +1,55 @@
+/**
+ * @class RecorderProcessor
+ * @extends AudioWorkletProcessor
+ *
+ * A simple recorder AudioWorkletProcessor. Returns the recorded buffer to the
+ * node when recording is finished.
+ */
+class RecorderProcessor extends AudioWorkletProcessor {
+ /**
+ * @param {*} options
+ * @param {number} options.duration A duration to record in seconds.
+ * @param {number} options.channelCount A channel count to record.
+ */
+ constructor(options) {
+ super();
+ this._createdAt = currentTime;
+ this._elapsed = 0;
+ this._recordDuration = options.duration || 1;
+ this._recordChannelCount = options.channelCount || 1;
+ this._recordBufferLength = sampleRate * this._recordDuration;
+ this._recordBuffer = [];
+ for (let i = 0; i < this._recordChannelCount; ++i) {
+ this._recordBuffer[i] = new Float32Array(this._recordBufferLength);
+ }
+ }
+
+ process(inputs, outputs) {
+ if (this._recordBufferLength <= currentFrame) {
+ this.port.postMessage({
+ type: 'recordfinished',
+ recordBuffer: this._recordBuffer
+ });
+ this.port.close();
+ return false;
+ }
+
+ // Records the incoming data from |inputs| and also bypasses the data to
+ // |outputs|.
+ const input = inputs[0];
+ const output = outputs[0];
+ for (let channel = 0; channel < input.length; ++channel) {
+ const inputChannel = input[channel];
+ const outputChannel = output[channel];
+ outputChannel.set(inputChannel);
+
+ const buffer = this._recordBuffer[channel];
+ const capacity = buffer.length - currentFrame;
+ buffer.set(inputChannel.slice(0, capacity), currentFrame);
+ }
+
+ return true;
+ }
+}
+
+registerProcessor('recorder-processor', RecorderProcessor);