diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/media/webaudio/test | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/media/webaudio/test')
213 files changed, 14278 insertions, 0 deletions
diff --git a/dom/media/webaudio/test/audio-expected.wav b/dom/media/webaudio/test/audio-expected.wav Binary files differnew file mode 100644 index 000000000..151927077 --- /dev/null +++ b/dom/media/webaudio/test/audio-expected.wav diff --git a/dom/media/webaudio/test/audio-mono-expected-2.wav b/dom/media/webaudio/test/audio-mono-expected-2.wav Binary files differnew file mode 100644 index 000000000..68c90dfa1 --- /dev/null +++ b/dom/media/webaudio/test/audio-mono-expected-2.wav diff --git a/dom/media/webaudio/test/audio-mono-expected.wav b/dom/media/webaudio/test/audio-mono-expected.wav Binary files differnew file mode 100644 index 000000000..bf00e5cdf --- /dev/null +++ b/dom/media/webaudio/test/audio-mono-expected.wav diff --git a/dom/media/webaudio/test/audio-quad.wav b/dom/media/webaudio/test/audio-quad.wav Binary files differnew file mode 100644 index 000000000..093f0197a --- /dev/null +++ b/dom/media/webaudio/test/audio-quad.wav diff --git a/dom/media/webaudio/test/audio.ogv b/dom/media/webaudio/test/audio.ogv Binary files differnew file mode 100644 index 000000000..68dee3cf2 --- /dev/null +++ b/dom/media/webaudio/test/audio.ogv diff --git a/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js b/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js new file mode 100644 index 000000000..2a5a4bff8 --- /dev/null +++ b/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js @@ -0,0 +1,3 @@ +onmessage = function (event) { + postMessage("Pong"); +}; diff --git a/dom/media/webaudio/test/audiovideo.mp4 b/dom/media/webaudio/test/audiovideo.mp4 Binary files differnew file mode 100644 index 000000000..fe93122d2 --- /dev/null +++ b/dom/media/webaudio/test/audiovideo.mp4 diff --git a/dom/media/webaudio/test/blink/README b/dom/media/webaudio/test/blink/README new file mode 100644 index 000000000..1d819221f --- /dev/null +++ b/dom/media/webaudio/test/blink/README @@ -0,0 +1,9 @@ +This directory contains tests originally borrowed from the Blink Web Audio test +suite. + +The process of borrowing tests from Blink is as follows: + +* Import the pristine file from the Blink repo, noting the revision in the + commit message. +* Modify the test files to turn the LayoutTest into a mochitest-plain and add +* them to the test suite in a separate commit. diff --git a/dom/media/webaudio/test/blink/audio-testing.js b/dom/media/webaudio/test/blink/audio-testing.js new file mode 100644 index 000000000..c66d32c7f --- /dev/null +++ b/dom/media/webaudio/test/blink/audio-testing.js @@ -0,0 +1,192 @@ +if (window.testRunner) + testRunner.overridePreference("WebKitWebAudioEnabled", "1"); + +function writeString(s, a, offset) { + for (var i = 0; i < s.length; ++i) { + a[offset + i] = s.charCodeAt(i); + } +} + +function writeInt16(n, a, offset) { + n = Math.floor(n); + + var b1 = n & 255; + var b2 = (n >> 8) & 255; + + a[offset + 0] = b1; + a[offset + 1] = b2; +} + +function writeInt32(n, a, offset) { + n = Math.floor(n); + var b1 = n & 255; + var b2 = (n >> 8) & 255; + var b3 = (n >> 16) & 255; + var b4 = (n >> 24) & 255; + + a[offset + 0] = b1; + a[offset + 1] = b2; + a[offset + 2] = b3; + a[offset + 3] = b4; +} + +function writeAudioBuffer(audioBuffer, a, offset) { + var n = audioBuffer.length; + var channels = audioBuffer.numberOfChannels; + + for (var i = 0; i < n; ++i) { + for (var k = 0; k < channels; ++k) { + var buffer = audioBuffer.getChannelData(k); + var sample = buffer[i] * 32768.0; + + // Clip samples to the limitations of 16-bit. + // If we don't do this then we'll get nasty wrap-around distortion. + if (sample < -32768) + sample = -32768; + if (sample > 32767) + sample = 32767; + + writeInt16(sample, a, offset); + offset += 2; + } + } +} + +function createWaveFileData(audioBuffer) { + var frameLength = audioBuffer.length; + var numberOfChannels = audioBuffer.numberOfChannels; + var sampleRate = audioBuffer.sampleRate; + var bitsPerSample = 16; + var byteRate = sampleRate * numberOfChannels * bitsPerSample/8; + var blockAlign = numberOfChannels * bitsPerSample/8; + var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio + var headerByteLength = 44; + var totalLength = headerByteLength + wavDataByteLength; + + var waveFileData = new Uint8Array(totalLength); + + var subChunk1Size = 16; // for linear PCM + var subChunk2Size = wavDataByteLength; + var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size); + + writeString("RIFF", waveFileData, 0); + writeInt32(chunkSize, waveFileData, 4); + writeString("WAVE", waveFileData, 8); + writeString("fmt ", waveFileData, 12); + + writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4) + writeInt16(1, waveFileData, 20); // AudioFormat (2) + writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2) + writeInt32(sampleRate, waveFileData, 24); // SampleRate (4) + writeInt32(byteRate, waveFileData, 28); // ByteRate (4) + writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2) + writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4) + + writeString("data", waveFileData, 36); + writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4) + + // Write actual audio data starting at offset 44. + writeAudioBuffer(audioBuffer, waveFileData, 44); + + return waveFileData; +} + +function createAudioData(audioBuffer) { + return createWaveFileData(audioBuffer); +} + +function finishAudioTest(event) { + var audioData = createAudioData(event.renderedBuffer); + testRunner.setAudioData(audioData); + testRunner.notifyDone(); +} + +// Create an impulse in a buffer of length sampleFrameLength +function createImpulseBuffer(context, sampleFrameLength) { + var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); + var n = audioBuffer.length; + var dataL = audioBuffer.getChannelData(0); + + for (var k = 0; k < n; ++k) { + dataL[k] = 0; + } + dataL[0] = 1; + + return audioBuffer; +} + +// Create a buffer of the given length with a linear ramp having values 0 <= x < 1. +function createLinearRampBuffer(context, sampleFrameLength) { + var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); + var n = audioBuffer.length; + var dataL = audioBuffer.getChannelData(0); + + for (var i = 0; i < n; ++i) + dataL[i] = i / n; + + return audioBuffer; +} + +// Create a buffer of the given length having a constant value. +function createConstantBuffer(context, sampleFrameLength, constantValue) { + var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); + var n = audioBuffer.length; + var dataL = audioBuffer.getChannelData(0); + + for (var i = 0; i < n; ++i) + dataL[i] = constantValue; + + return audioBuffer; +} + +// Create a stereo impulse in a buffer of length sampleFrameLength +function createStereoImpulseBuffer(context, sampleFrameLength) { + var audioBuffer = context.createBuffer(2, sampleFrameLength, context.sampleRate); + var n = audioBuffer.length; + var dataL = audioBuffer.getChannelData(0); + var dataR = audioBuffer.getChannelData(1); + + for (var k = 0; k < n; ++k) { + dataL[k] = 0; + dataR[k] = 0; + } + dataL[0] = 1; + dataR[0] = 1; + + return audioBuffer; +} + +// Convert time (in seconds) to sample frames. +function timeToSampleFrame(time, sampleRate) { + return Math.floor(0.5 + time * sampleRate); +} + +// Compute the number of sample frames consumed by start with +// the specified |grainOffset|, |duration|, and |sampleRate|. +function grainLengthInSampleFrames(grainOffset, duration, sampleRate) { + var startFrame = timeToSampleFrame(grainOffset, sampleRate); + var endFrame = timeToSampleFrame(grainOffset + duration, sampleRate); + + return endFrame - startFrame; +} + +// True if the number is not an infinity or NaN +function isValidNumber(x) { + return !isNaN(x) && (x != Infinity) && (x != -Infinity); +} + +function shouldThrowTypeError(func, text) { + var ok = false; + try { + func(); + } catch (e) { + if (e instanceof TypeError) { + ok = true; + } + } + if (ok) { + testPassed(text + " threw TypeError."); + } else { + testFailed(text + " should throw TypeError."); + } +} diff --git a/dom/media/webaudio/test/blink/biquad-filters.js b/dom/media/webaudio/test/blink/biquad-filters.js new file mode 100644 index 000000000..06fff98b1 --- /dev/null +++ b/dom/media/webaudio/test/blink/biquad-filters.js @@ -0,0 +1,368 @@ +// Taken from WebKit/LayoutTests/webaudio/resources/biquad-filters.js + +// A biquad filter has a z-transform of +// H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2) +// +// The formulas for the various filters were taken from +// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt. + + +// Lowpass filter. +function createLowpassFilter(freq, q, gain) { + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + + if (freq == 1) { + // The formula below works, except for roundoff. When freq = 1, + // the filter is just a wire, so hardwire the coefficients. + b0 = 1; + b1 = 0; + b2 = 0; + a0 = 1; + a1 = 0; + a2 = 0; + } else { + var w0 = Math.PI * freq; + var alpha = 0.5 * Math.sin(w0) / Math.pow(10, q / 20); + var cos_w0 = Math.cos(w0); + + b0 = 0.5 * (1 - cos_w0); + b1 = 1 - cos_w0; + b2 = b0; + a0 = 1 + alpha; + a1 = -2.0 * cos_w0; + a2 = 1 - alpha; + } + + return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); +} + +function createHighpassFilter(freq, q, gain) { + var b0; + var b1; + var b2; + var a1; + var a2; + + if (freq == 1) { + // The filter is 0 + b0 = 0; + b1 = 0; + b2 = 0; + a0 = 1; + a1 = 0; + a2 = 0; + } else if (freq == 0) { + // The filter is 1. Computation of coefficients below is ok, but + // there's a pole at 1 and a zero at 1, so round-off could make + // the filter unstable. + b0 = 1; + b1 = 0; + b2 = 0; + a0 = 1; + a1 = 0; + a2 = 0; + } else { + var w0 = Math.PI * freq; + var alpha = 0.5 * Math.sin(w0) / Math.pow(10, q / 20); + var cos_w0 = Math.cos(w0); + + b0 = 0.5 * (1 + cos_w0); + b1 = -1 - cos_w0; + b2 = b0; + a0 = 1 + alpha; + a1 = -2.0 * cos_w0; + a2 = 1 - alpha; + } + + return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); +} + +function normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2) { + var scale = 1 / a0; + + return {b0 : b0 * scale, + b1 : b1 * scale, + b2 : b2 * scale, + a1 : a1 * scale, + a2 : a2 * scale}; +} + +function createBandpassFilter(freq, q, gain) { + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + var coef; + + if (freq > 0 && freq < 1) { + var w0 = Math.PI * freq; + if (q > 0) { + var alpha = Math.sin(w0) / (2 * q); + var k = Math.cos(w0); + + b0 = alpha; + b1 = 0; + b2 = -alpha; + a0 = 1 + alpha; + a1 = -2 * k; + a2 = 1 - alpha; + + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // q = 0, and frequency is not 0 or 1. The above formula has a + // divide by zero problem. The limit of the z-transform as q + // approaches 0 is 1, so set the filter that way. + coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + } else { + // When freq = 0 or 1, the z-transform is identically 0, + // independent of q. + coef = {b0 : 0, b1 : 0, b2 : 0, a1 : 0, a2 : 0} + } + + return coef; +} + +function createLowShelfFilter(freq, q, gain) { + // q not used + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + var coef; + + var S = 1; + var A = Math.pow(10, gain / 40); + + if (freq == 1) { + // The filter is just a constant gain + coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } else if (freq == 0) { + // The filter is 1 + coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } else { + var w0 = Math.PI * freq; + var alpha = 1 / 2 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2); + var k = Math.cos(w0); + var k2 = 2 * Math.sqrt(A) * alpha; + var Ap1 = A + 1; + var Am1 = A - 1; + + b0 = A * (Ap1 - Am1 * k + k2); + b1 = 2 * A * (Am1 - Ap1 * k); + b2 = A * (Ap1 - Am1 * k - k2); + a0 = Ap1 + Am1 * k + k2; + a1 = -2 * (Am1 + Ap1 * k); + a2 = Ap1 + Am1 * k - k2; + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } + + return coef; +} + +function createHighShelfFilter(freq, q, gain) { + // q not used + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + var coef; + + var A = Math.pow(10, gain / 40); + + if (freq == 1) { + // When freq = 1, the z-transform is 1 + coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } else if (freq > 0) { + var w0 = Math.PI * freq; + var S = 1; + var alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2); + var k = Math.cos(w0); + var k2 = 2 * Math.sqrt(A) * alpha; + var Ap1 = A + 1; + var Am1 = A - 1; + + b0 = A * (Ap1 + Am1 * k + k2); + b1 = -2 * A * (Am1 + Ap1 * k); + b2 = A * (Ap1 + Am1 * k - k2); + a0 = Ap1 - Am1 * k + k2; + a1 = 2 * (Am1 - Ap1*k); + a2 = Ap1 - Am1 * k-k2; + + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // When freq = 0, the filter is just a gain + coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + + return coef; +} + +function createPeakingFilter(freq, q, gain) { + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + var coef; + + var A = Math.pow(10, gain / 40); + + if (freq > 0 && freq < 1) { + if (q > 0) { + var w0 = Math.PI * freq; + var alpha = Math.sin(w0) / (2 * q); + var k = Math.cos(w0); + + b0 = 1 + alpha * A; + b1 = -2 * k; + b2 = 1 - alpha * A; + a0 = 1 + alpha / A; + a1 = -2 * k; + a2 = 1 - alpha / A; + + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // q = 0, we have a divide by zero problem in the formulas + // above. But if we look at the z-transform, we see that the + // limit as q approaches 0 is A^2. + coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + } else { + // freq = 0 or 1, the z-transform is 1 + coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + + return coef; +} + +function createNotchFilter(freq, q, gain) { + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + var coef; + + if (freq > 0 && freq < 1) { + if (q > 0) { + var w0 = Math.PI * freq; + var alpha = Math.sin(w0) / (2 * q); + var k = Math.cos(w0); + + b0 = 1; + b1 = -2 * k; + b2 = 1; + a0 = 1 + alpha; + a1 = -2 * k; + a2 = 1 - alpha; + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // When q = 0, we get a divide by zero above. The limit of the + // z-transform as q approaches 0 is 0, so set the coefficients + // appropriately. + coef = {b0 : 0, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + } else { + // When freq = 0 or 1, the z-transform is 1 + coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + + return coef; +} + +function createAllpassFilter(freq, q, gain) { + var b0; + var b1; + var b2; + var a0; + var a1; + var a2; + var coef; + + if (freq > 0 && freq < 1) { + if (q > 0) { + var w0 = Math.PI * freq; + var alpha = Math.sin(w0) / (2 * q); + var k = Math.cos(w0); + + b0 = 1 - alpha; + b1 = -2 * k; + b2 = 1 + alpha; + a0 = 1 + alpha; + a1 = -2 * k; + a2 = 1 - alpha; + coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2); + } else { + // q = 0 + coef = {b0 : -1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + } else { + coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0}; + } + + return coef; +} + +function filterData(filterCoef, signal, len) { + var y = new Array(len); + var b0 = filterCoef.b0; + var b1 = filterCoef.b1; + var b2 = filterCoef.b2; + var a1 = filterCoef.a1; + var a2 = filterCoef.a2; + + // Prime the pump. (Assumes the signal has length >= 2!) + y[0] = b0 * signal[0]; + y[1] = b0 * signal[1] + b1 * signal[0] - a1 * y[0]; + + // Filter all of the signal that we have. + for (var k = 2; k < Math.min(signal.length, len); ++k) { + y[k] = b0 * signal[k] + b1 * signal[k-1] + b2 * signal[k-2] - a1 * y[k-1] - a2 * y[k-2]; + } + + // If we need to filter more, but don't have any signal left, + // assume the signal is zero. + for (var k = signal.length; k < len; ++k) { + y[k] = - a1 * y[k-1] - a2 * y[k-2]; + } + + return y; +} + +// Map the filter type name to a function that computes the filter coefficents for the given filter +// type. +var filterCreatorFunction = {"lowpass": createLowpassFilter, + "highpass": createHighpassFilter, + "bandpass": createBandpassFilter, + "lowshelf": createLowShelfFilter, + "highshelf": createHighShelfFilter, + "peaking": createPeakingFilter, + "notch": createNotchFilter, + "allpass": createAllpassFilter}; + +var filterTypeName = {"lowpass": "Lowpass filter", + "highpass": "Highpass filter", + "bandpass": "Bandpass filter", + "lowshelf": "Lowshelf filter", + "highshelf": "Highshelf filter", + "peaking": "Peaking filter", + "notch": "Notch filter", + "allpass": "Allpass filter"}; + +function createFilter(filterType, freq, q, gain) { + return filterCreatorFunction[filterType](freq, q, gain); +} diff --git a/dom/media/webaudio/test/blink/biquad-testing.js b/dom/media/webaudio/test/blink/biquad-testing.js new file mode 100644 index 000000000..795adf601 --- /dev/null +++ b/dom/media/webaudio/test/blink/biquad-testing.js @@ -0,0 +1,153 @@ +// Globals, to make testing and debugging easier. +var context; +var filter; +var signal; +var renderedBuffer; +var renderedData; + +var sampleRate = 44100.0; +var pulseLengthFrames = .1 * sampleRate; + +// Maximum allowed error for the test to succeed. Experimentally determined. +var maxAllowedError = 5.9e-8; + +// This must be large enough so that the filtered result is +// essentially zero. See comments for createTestAndRun. +var timeStep = .1; + +// Maximum number of filters we can process (mostly for setting the +// render length correctly.) +var maxFilters = 5; + +// How long to render. Must be long enough for all of the filters we +// want to test. +var renderLengthSeconds = timeStep * (maxFilters + 1) ; + +var renderLengthSamples = Math.round(renderLengthSeconds * sampleRate); + +// Number of filters that will be processed. +var nFilters; + +function createImpulseBuffer(context, length) { + var impulse = context.createBuffer(1, length, context.sampleRate); + var data = impulse.getChannelData(0); + for (var k = 1; k < data.length; ++k) { + data[k] = 0; + } + data[0] = 1; + + return impulse; +} + + +function createTestAndRun(context, filterType, filterParameters) { + // To test the filters, we apply a signal (an impulse) to each of + // the specified filters, with each signal starting at a different + // time. The output of the filters is summed together at the + // output. Thus for filter k, the signal input to the filter + // starts at time k * timeStep. For this to work well, timeStep + // must be large enough for the output of each filter to have + // decayed to zero with timeStep seconds. That way the filter + // outputs don't interfere with each other. + + nFilters = Math.min(filterParameters.length, maxFilters); + + signal = new Array(nFilters); + filter = new Array(nFilters); + + impulse = createImpulseBuffer(context, pulseLengthFrames); + + // Create all of the signal sources and filters that we need. + for (var k = 0; k < nFilters; ++k) { + signal[k] = context.createBufferSource(); + signal[k].buffer = impulse; + + filter[k] = context.createBiquadFilter(); + filter[k].type = filterType; + filter[k].frequency.value = context.sampleRate / 2 * filterParameters[k].cutoff; + filter[k].detune.value = (filterParameters[k].detune === undefined) ? 0 : filterParameters[k].detune; + filter[k].Q.value = filterParameters[k].q; + filter[k].gain.value = filterParameters[k].gain; + + signal[k].connect(filter[k]); + filter[k].connect(context.destination); + + signal[k].start(timeStep * k); + } + + context.oncomplete = checkFilterResponse(filterType, filterParameters); + context.startRendering(); +} + +function addSignal(dest, src, destOffset) { + // Add src to dest at the given dest offset. + for (var k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) { + dest[k] += src[j]; + } +} + +function generateReference(filterType, filterParameters) { + var result = new Array(renderLengthSamples); + var data = new Array(renderLengthSamples); + // Initialize the result array and data. + for (var k = 0; k < result.length; ++k) { + result[k] = 0; + data[k] = 0; + } + // Make data an impulse. + data[0] = 1; + + for (var k = 0; k < nFilters; ++k) { + // Filter an impulse + var detune = (filterParameters[k].detune === undefined) ? 0 : filterParameters[k].detune; + var frequency = filterParameters[k].cutoff * Math.pow(2, detune / 1200); // Apply detune, converting from Cents. + + var filterCoef = createFilter(filterType, + frequency, + filterParameters[k].q, + filterParameters[k].gain); + var y = filterData(filterCoef, data, renderLengthSamples); + + // Accumulate this filtered data into the final output at the desired offset. + addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate)); + } + + return result; +} + +function checkFilterResponse(filterType, filterParameters) { + return function(event) { + renderedBuffer = event.renderedBuffer; + renderedData = renderedBuffer.getChannelData(0); + + reference = generateReference(filterType, filterParameters); + + var len = Math.min(renderedData.length, reference.length); + + var success = true; + + // Maximum error between rendered data and expected data + var maxError = 0; + + // Sample offset where the maximum error occurred. + var maxPosition = 0; + + // Number of infinities or NaNs that occurred in the rendered data. + var invalidNumberCount = 0; + + ok(nFilters == filterParameters.length, "Test wanted " + filterParameters.length + " filters but only " + maxFilters + " allowed."); + + compareChannels(renderedData, reference, len, 0, 0, true); + + // Check for bad numbers in the rendered output too. + // There shouldn't be any. + for (var k = 0; k < len; ++k) { + if (!isValidNumber(renderedData[k])) { + ++invalidNumberCount; + } + } + + ok(invalidNumberCount == 0, "Rendered output has " + invalidNumberCount + " infinities or NaNs."); + SimpleTest.finish(); + } +} diff --git a/dom/media/webaudio/test/blink/convolution-testing.js b/dom/media/webaudio/test/blink/convolution-testing.js new file mode 100644 index 000000000..98ff0c775 --- /dev/null +++ b/dom/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 = -129.4; + 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(); + } +} diff --git a/dom/media/webaudio/test/blink/mochitest.ini b/dom/media/webaudio/test/blink/mochitest.ini new file mode 100644 index 000000000..28bceb3a4 --- /dev/null +++ b/dom/media/webaudio/test/blink/mochitest.ini @@ -0,0 +1,23 @@ +[DEFAULT] +tags=msg +tags = webaudio +subsuite = media +support-files = + biquad-filters.js + biquad-testing.js + ../webaudio.js + +[test_biquadFilterNodeAllPass.html] +[test_biquadFilterNodeAutomation.html] +skip-if = true # Known problems with Biquad automation, e.g. Bug 1155709 +[test_biquadFilterNodeBandPass.html] +[test_biquadFilterNodeGetFrequencyResponse.html] +[test_biquadFilterNodeHighPass.html] +[test_biquadFilterNodeHighShelf.html] +[test_biquadFilterNodeLowPass.html] +[test_biquadFilterNodeLowShelf.html] +[test_biquadFilterNodeNotch.html] +[test_biquadFilterNodePeaking.html] +[test_biquadFilterNodeTail.html] +[test_iirFilterNode.html] +[test_iirFilterNodeGetFrequencyResponse.html] diff --git a/dom/media/webaudio/test/blink/panner-model-testing.js b/dom/media/webaudio/test/blink/panner-model-testing.js new file mode 100644 index 000000000..45460e276 --- /dev/null +++ b/dom/media/webaudio/test/blink/panner-model-testing.js @@ -0,0 +1,210 @@ +var sampleRate = 48000.0; + +var numberOfChannels = 1; + +// Time step when each panner node starts. +var timeStep = 0.001; + +// Length of the impulse signal. +var pulseLengthFrames = Math.round(timeStep * sampleRate); + +// How many panner nodes to create for the test +var nodesToCreate = 100; + +// Be sure we render long enough for all of our nodes. +var renderLengthSeconds = timeStep * (nodesToCreate + 1); + +// These are global mostly for debugging. +var context; +var impulse; +var bufferSource; +var panner; +var position; +var time; + +var renderedBuffer; +var renderedLeft; +var renderedRight; + +function createGraph(context, nodeCount) { + bufferSource = new Array(nodeCount); + panner = new Array(nodeCount); + position = new Array(nodeCount); + time = new Array(nodeCount); + // Angle between panner locations. (nodeCount - 1 because we want + // to include both 0 and 180 deg. + var angleStep = Math.PI / (nodeCount - 1); + + if (numberOfChannels == 2) { + impulse = createStereoImpulseBuffer(context, pulseLengthFrames); + } + else + impulse = createImpulseBuffer(context, pulseLengthFrames); + + for (var k = 0; k < nodeCount; ++k) { + bufferSource[k] = context.createBufferSource(); + bufferSource[k].buffer = impulse; + + panner[k] = context.createPanner(); + panner[k].panningModel = "equalpower"; + panner[k].distanceModel = "linear"; + + var angle = angleStep * k; + position[k] = {angle : angle, x : Math.cos(angle), z : Math.sin(angle)}; + panner[k].positionX.value = position[k].x; + panner[k].positionZ.value = position[k].z; + + bufferSource[k].connect(panner[k]); + panner[k].connect(context.destination); + + // Start the source + time[k] = k * timeStep; + bufferSource[k].start(time[k]); + } +} + +function createTestAndRun(context, nodeCount, numberOfSourceChannels) { + numberOfChannels = numberOfSourceChannels; + + createGraph(context, nodeCount); + + context.oncomplete = checkResult; + context.startRendering(); +} + +// Map our position angle to the azimuth angle (in degrees). +// +// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg. +function angleToAzimuth(angle) { + return 90 - angle * 180 / Math.PI; +} + +// The gain caused by the EQUALPOWER panning model +function equalPowerGain(angle) { + var azimuth = angleToAzimuth(angle); + + if (numberOfChannels == 1) { + var panPosition = (azimuth + 90) / 180; + + var gainL = Math.cos(0.5 * Math.PI * panPosition); + var gainR = Math.sin(0.5 * Math.PI * panPosition); + + return { left : gainL, right : gainR }; + } else { + if (azimuth <= 0) { + var panPosition = (azimuth + 90) / 90; + + var gainL = 1 + Math.cos(0.5 * Math.PI * panPosition); + var gainR = Math.sin(0.5 * Math.PI * panPosition); + + return { left : gainL, right : gainR }; + } else { + var panPosition = azimuth / 90; + + var gainL = Math.cos(0.5 * Math.PI * panPosition); + var gainR = 1 + Math.sin(0.5 * Math.PI * panPosition); + + return { left : gainL, right : gainR }; + } + } +} + +function checkResult(event) { + renderedBuffer = event.renderedBuffer; + renderedLeft = renderedBuffer.getChannelData(0); + renderedRight = renderedBuffer.getChannelData(1); + + // The max error we allow between the rendered impulse and the + // expected value. This value is experimentally determined. Set + // to 0 to make the test fail to see what the actual error is. + var maxAllowedError = 1.3e-6; + + var success = true; + + // Number of impulses found in the rendered result. + var impulseCount = 0; + + // Max (relative) error and the index of the maxima for the left + // and right channels. + var maxErrorL = 0; + var maxErrorIndexL = 0; + var maxErrorR = 0; + var maxErrorIndexR = 0; + + // Number of impulses that don't match our expected locations. + var timeCount = 0; + + // Locations of where the impulses aren't at the expected locations. + var timeErrors = new Array(); + + for (var k = 0; k < renderedLeft.length; ++k) { + // We assume that the left and right channels start at the same instant. + if (renderedLeft[k] != 0 || renderedRight[k] != 0) { + // The expected gain for the left and right channels. + var pannerGain = equalPowerGain(position[impulseCount].angle); + var expectedL = pannerGain.left; + var expectedR = pannerGain.right; + + // Absolute error in the gain. + var errorL = Math.abs(renderedLeft[k] - expectedL); + var errorR = Math.abs(renderedRight[k] - expectedR); + + if (Math.abs(errorL) > maxErrorL) { + maxErrorL = Math.abs(errorL); + maxErrorIndexL = impulseCount; + } + if (Math.abs(errorR) > maxErrorR) { + maxErrorR = Math.abs(errorR); + maxErrorIndexR = impulseCount; + } + + // Keep track of the impulses that didn't show up where we + // expected them to be. + var expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate); + if (k != expectedOffset) { + timeErrors[timeCount] = { actual : k, expected : expectedOffset}; + ++timeCount; + } + ++impulseCount; + } + } + + if (impulseCount == nodesToCreate) { + testPassed("Number of impulses matches the number of panner nodes."); + } else { + testFailed("Number of impulses is incorrect. (Found " + impulseCount + " but expected " + nodesToCreate + ")"); + success = false; + } + + if (timeErrors.length > 0) { + success = false; + testFailed(timeErrors.length + " timing errors found in " + nodesToCreate + " panner nodes."); + for (var k = 0; k < timeErrors.length; ++k) { + testFailed("Impulse at sample " + timeErrors[k].actual + " but expected " + timeErrors[k].expected); + } + } else { + testPassed("All impulses at expected offsets."); + } + + if (maxErrorL <= maxAllowedError) { + testPassed("Left channel gain values are correct."); + } else { + testFailed("Left channel gain values are incorrect. Max error = " + maxErrorL + " at time " + time[maxErrorIndexL] + " (threshold = " + maxAllowedError + ")"); + success = false; + } + + if (maxErrorR <= maxAllowedError) { + testPassed("Right channel gain values are correct."); + } else { + testFailed("Right channel gain values are incorrect. Max error = " + maxErrorR + " at time " + time[maxErrorIndexR] + " (threshold = " + maxAllowedError + ")"); + success = false; + } + + if (success) { + testPassed("EqualPower panner test passed"); + } else { + testFailed("EqualPower panner test failed"); + } + + finishJSTest(); +} diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html new file mode 100644 index 000000000..266521c52 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode All Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + var filterParameters = [{cutoff : 0, q : 10, gain : 1 }, + {cutoff : 1, q : 10, gain : 1 }, + {cutoff : .5, q : 0, gain : 1 }, + {cutoff : 0.25, q : 10, gain : 1 }, + ]; + createTestAndRun(context, "allpass", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html new file mode 100644 index 000000000..08ce71cce --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html @@ -0,0 +1,351 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode All Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Don't need to run these tests at high sampling rate, so just use a low one to reduce memory + // usage and complexity. + var sampleRate = 16000; + + // How long to render for each test. + var renderDuration = 1; + + // The definition of the linear ramp automation function. + function linearRamp(t, v0, v1, t0, t1) { + return v0 + (v1 - v0) * (t - t0) / (t1 - t0); + } + + // Generate the filter coefficients for the specified filter using the given parameters for + // the given duration. |filterTypeFunction| is a function that returns the filter + // coefficients for one set of parameters. |parameters| is a property bag that contains the + // start and end values (as an array) for each of the biquad attributes. The properties are + // |freq|, |Q|, |gain|, and |detune|. |duration| is the number of seconds for which the + // coefficients are generated. + // + // A property bag with properties |b0|, |b1|, |b2|, |a1|, |a2|. Each propery is an array + // consisting of the coefficients for the time-varying biquad filter. + function generateFilterCoefficients(filterTypeFunction, parameters, duration) { + var endFrame = Math.ceil(duration * sampleRate); + var nCoef = endFrame; + var b0 = new Float64Array(nCoef); + var b1 = new Float64Array(nCoef); + var b2 = new Float64Array(nCoef); + var a1 = new Float64Array(nCoef); + var a2 = new Float64Array(nCoef); + + var k = 0; + // If the property is not given, use the defaults. + var freqs = parameters.freq || [350, 350]; + var qs = parameters.Q || [1, 1]; + var gains = parameters.gain || [0, 0]; + var detunes = parameters.detune || [0, 0]; + + for (var frame = 0; frame < endFrame; ++frame) { + // Apply linear ramp at frame |frame|. + var f = linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, duration); + var q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration); + var g = linearRamp(frame / sampleRate, gains[0], gains[1], 0, duration); + var d = linearRamp(frame / sampleRate, detunes[0], detunes[1], 0, duration); + + // Compute actual frequency parameter + f = f * Math.pow(2, d / 1200); + + // Compute filter coefficients + var coef = filterTypeFunction(f / (sampleRate / 2), q, g); + b0[k] = coef.b0; + b1[k] = coef.b1; + b2[k] = coef.b2; + a1[k] = coef.a1; + a2[k] = coef.a2; + ++k; + } + + return {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2}; + } + + // Apply the given time-varying biquad filter to the given signal, |signal|. |coef| should be + // the time-varying coefficients of the filter, as returned by |generateFilterCoefficients|. + function timeVaryingFilter(signal, coef) { + var length = signal.length; + // Use double precision for the internal computations. + var y = new Float64Array(length); + + // Prime the pump. (Assumes the signal has length >= 2!) + y[0] = coef.b0[0] * signal[0]; + y[1] = coef.b0[1] * signal[1] + coef.b1[1] * signal[0] - coef.a1[1] * y[0]; + + for (var n = 2; n < length; ++n) { + y[n] = coef.b0[n] * signal[n] + coef.b1[n] * signal[n-1] + coef.b2[n] * signal[n-2]; + y[n] -= coef.a1[n] * y[n-1] + coef.a2[n] * y[n-2]; + } + + // But convert the result to single precision for comparison. + return y.map(Math.fround); + } + + // Configure the audio graph using |context|. Returns the biquad filter node and the + // AudioBuffer used for the source. + function configureGraph(context, toneFrequency) { + // The source is just a simple sine wave. + var src = context.createBufferSource(); + var b = context.createBuffer(1, renderDuration * sampleRate, sampleRate); + var data = b.getChannelData(0); + var omega = 2 * Math.PI * toneFrequency / sampleRate; + for (var k = 0; k < data.length; ++k) { + data[k] = Math.sin(omega * k); + } + src.buffer = b; + var f = context.createBiquadFilter(); + src.connect(f); + f.connect(context.destination); + + src.start(); + + return {filter: f, source: b}; + } + + function createFilterVerifier(filterCreator, threshold, parameters, input, message) { + return function (resultBuffer) { + var actual = resultBuffer.getChannelData(0); + var coefs = generateFilterCoefficients(filterCreator, parameters, renderDuration); + + reference = timeVaryingFilter(input, coefs); + + compareChannels(actual, reference); + }; + } + + var testPromises = []; + + // Automate just the frequency parameter. A bandpass filter is used where the center + // frequency is swept across the source (which is a simple tone). + testPromises.push(function () { + var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + + // Center frequency of bandpass filter and also the frequency of the test tone. + var centerFreq = 10*440; + + // Sweep the frequency +/- 9*440 Hz from the center. This should cause the output to low at + // the beginning and end of the test where the done is outside the pass band of the filter, + // but high in the center where the tone is near the center of the pass band. + var parameters = { + freq: [centerFreq - 9*440, centerFreq + 9*440] + } + var graph = configureGraph(context, centerFreq); + var f = graph.filter; + var b = graph.source; + + f.type = "bandpass"; + f.frequency.setValueAtTime(parameters.freq[0], 0); + f.frequency.linearRampToValueAtTime(parameters.freq[1], renderDuration); + + return context.startRendering() + .then(createFilterVerifier(createBandpassFilter, 5e-5, parameters, b.getChannelData(0), + "Output of bandpass filter with frequency automation")); + }()); + + // Automate just the Q parameter. A bandpass filter is used where the Q of the filter is + // swept. + testPromises.push(function() { + var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + + // The frequency of the test tone. + var centerFreq = 440; + + // Sweep the Q paramter between 1 and 200. This will cause the output of the filter to pass + // most of the tone at the beginning to passing less of the tone at the end. This is + // because we set center frequency of the bandpass filter to be slightly off from the actual + // tone. + var parameters = { + Q: [1, 200], + // Center frequency of the bandpass filter is just 25 Hz above the tone frequency. + freq: [centerFreq + 25, centerFreq + 25] + }; + var graph = configureGraph(context, centerFreq); + var f = graph.filter; + var b = graph.source; + + f.type = "bandpass"; + f.frequency.value = parameters.freq[0]; + f.Q.setValueAtTime(parameters.Q[0], 0); + f.Q.linearRampToValueAtTime(parameters.Q[1], renderDuration); + + return context.startRendering() + .then(createFilterVerifier(createBandpassFilter, 1.4e-6, parameters, b.getChannelData(0), + "Output of bandpass filter with Q automation")); + }()); + + // Automate just the gain of the lowshelf filter. A test tone will be in the lowshelf part of + // the filter. The output will vary as the gain of the lowshelf is changed. + testPromises.push(function() { + var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + + // Frequency of the test tone. + var centerFreq = 440; + + // Set the cutoff frequency of the lowshelf to be significantly higher than the test tone. + // Sweep the gain from 20 dB to -20 dB. (We go from 20 to -20 to easily verify that the + // filter didn't go unstable.) + var parameters = { + freq: [3500, 3500], + gain: [20, -20] + } + var graph = configureGraph(context, centerFreq); + var f = graph.filter; + var b = graph.source; + + f.type = "lowshelf"; + f.frequency.value = parameters.freq[0]; + f.gain.setValueAtTime(parameters.gain[0], 0); + f.gain.linearRampToValueAtTime(parameters.gain[1], renderDuration); + + context.startRendering() + .then(createFilterVerifier(createLowShelfFilter, 8e-6, parameters, b.getChannelData(0), + "Output of lowshelf filter with gain automation")); + }()); + + // Automate just the detune parameter. Basically the same test as for the frequncy parameter + // but we just use the detune parameter to modulate the frequency parameter. + testPromises.push(function() { + var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + var centerFreq = 10*440; + var parameters = { + freq: [centerFreq, centerFreq], + detune: [-10*1200, 10*1200] + }; + var graph = configureGraph(context, centerFreq); + var f = graph.filter; + var b = graph.source; + + f.type = "bandpass"; + f.frequency.value = parameters.freq[0]; + f.detune.setValueAtTime(parameters.detune[0], 0); + f.detune.linearRampToValueAtTime(parameters.detune[1], renderDuration); + + context.startRendering() + .then(createFilterVerifier(createBandpassFilter, 5e-6, parameters, b.getChannelData(0), + "Output of bandpass filter with detune automation")); + }()); + + // Automate all of the filter parameters at once. This is a basic check that everything is + // working. A peaking filter is used because it uses all of the parameters. + testPromises.push(function() { + var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + var graph = configureGraph(context, 10*440); + var f = graph.filter; + var b = graph.source; + + // Sweep all of the filter parameters. These are pretty much arbitrary. + var parameters = { + freq: [10000, 100], + Q: [f.Q.value, .0001], + gain: [f.gain.value, 20], + detune: [2400, -2400] + }; + + f.type = "peaking"; + // Set starting points for all parameters of the filter. Start at 10 kHz for the center + // frequency, and the defaults for Q and gain. + f.frequency.setValueAtTime(parameters.freq[0], 0); + f.Q.setValueAtTime(parameters.Q[0], 0); + f.gain.setValueAtTime(parameters.gain[0], 0); + f.detune.setValueAtTime(parameters.detune[0], 0); + + // Linear ramp each parameter + f.frequency.linearRampToValueAtTime(parameters.freq[1], renderDuration); + f.Q.linearRampToValueAtTime(parameters.Q[1], renderDuration); + f.gain.linearRampToValueAtTime(parameters.gain[1], renderDuration); + f.detune.linearRampToValueAtTime(parameters.detune[1], renderDuration); + + context.startRendering() + .then(createFilterVerifier(createPeakingFilter, 3.3e-4, parameters, b.getChannelData(0), + "Output of peaking filter with automation of all parameters")); + }()); + + // Test that modulation of the frequency parameter of the filter works. A sinusoid of 440 Hz + // is the test signal that is applied to a bandpass biquad filter. The frequency parameter of + // the filter is modulated by a sinusoid at 103 Hz, and the frequency modulation varies from + // 116 to 412 Hz. (This test was taken from the description in + // https://github.com/WebAudio/web-audio-api/issues/509#issuecomment-94731355) + testPromises.push(function() { + var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); + + // Create a graph with the sinusoidal source at 440 Hz as the input to a biquad filter. + var graph = configureGraph(context, 440); + var f = graph.filter; + var b = graph.source; + + f.type = "bandpass"; + f.Q.value = 5; + f.frequency.value = 264; + + // Create the modulation source, a sinusoid with frequency 103 Hz and amplitude 148. (The + // amplitude of 148 is added to the filter's frequency value of 264 to produce a sinusoidal + // modulation of the frequency parameter from 116 to 412 Hz.) + var mod = context.createBufferSource(); + var mbuffer = context.createBuffer(1, renderDuration * sampleRate, sampleRate); + var d = mbuffer.getChannelData(0); + var omega = 2 * Math.PI * 103 / sampleRate; + for (var k = 0; k < d.length; ++k) { + d[k] = 148 * Math.sin(omega * k); + } + mod.buffer = mbuffer; + + mod.connect(f.frequency); + + mod.start(); + return context.startRendering() + .then(function (resultBuffer) { + var actual = resultBuffer.getChannelData(0); + // Compute the filter coefficients using the mod sine wave + + var endFrame = Math.ceil(renderDuration * sampleRate); + var nCoef = endFrame; + var b0 = new Float64Array(nCoef); + var b1 = new Float64Array(nCoef); + var b2 = new Float64Array(nCoef); + var a1 = new Float64Array(nCoef); + var a2 = new Float64Array(nCoef); + + // Generate the filter coefficients when the frequency varies from 116 to 248 Hz using + // the 103 Hz sinusoid. + for (var k = 0; k < nCoef; ++k) { + var freq = f.frequency.value + d[k]; + var c = createBandpassFilter(freq / (sampleRate / 2), f.Q.value, f.gain.value); + b0[k] = c.b0; + b1[k] = c.b1; + b2[k] = c.b2; + a1[k] = c.a1; + a2[k] = c.a2; + } + reference = timeVaryingFilter(b.getChannelData(0), + {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2}); + + compareChannels(actual, reference); + }); + }()); + + // Wait for all tests + Promise.all(testPromises).then(function () { + SimpleTest.finish(); + }, function () { + SimpleTest.finish(); + }); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html new file mode 100644 index 000000000..a3a1484f6 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode Band Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + // The filters we want to test. + var filterParameters = [{cutoff : 0, q : 0, gain : 1 }, + {cutoff : 1, q : 0, gain : 1 }, + {cutoff : 0.5, q : 0, gain : 1 }, + {cutoff : 0.25, q : 1, gain : 1 }, + ]; + + createTestAndRun(context, "bandpass", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html new file mode 100644 index 000000000..1576db1e8 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html @@ -0,0 +1,261 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode All Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { +// Test the frequency response of a biquad filter. We compute the frequency response for a simple +// peaking biquad filter and compare it with the expected frequency response. The actual filter +// used doesn't matter since we're testing getFrequencyResponse and not the actual filter output. +// The filters are extensively tested in other biquad tests. + +var context; + +// The biquad filter node. +var filter; + +// The magnitude response of the biquad filter. +var magResponse; + +// The phase response of the biquad filter. +var phaseResponse; + +// Number of frequency samples to take. +var numberOfFrequencies = 1000; + +// The filter parameters. +var filterCutoff = 1000; // Hz. +var filterQ = 1; +var filterGain = 5; // Decibels. + +// The maximum allowed error in the magnitude response. +var maxAllowedMagError = 5.7e-7; + +// The maximum allowed error in the phase response. +var maxAllowedPhaseError = 4.7e-8; + +// The magnitudes and phases of the reference frequency response. +var magResponse; +var phaseResponse; + +// The magnitudes and phases of the reference frequency response. +var expectedMagnitudes; +var expectedPhases; + +// Convert frequency in Hz to a normalized frequency between 0 to 1 with 1 corresponding to the +// Nyquist frequency. +function normalizedFrequency(freqHz, sampleRate) +{ + var nyquist = sampleRate / 2; + return freqHz / nyquist; +} + +// Get the filter response at a (normalized) frequency |f| for the filter with coefficients |coef|. +function getResponseAt(coef, f) +{ + var b0 = coef.b0; + var b1 = coef.b1; + var b2 = coef.b2; + var a1 = coef.a1; + var a2 = coef.a2; + + // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2) + // + // Compute H(exp(i * pi * f)). No native complex numbers in javascript, so break H(exp(i * pi * // f)) + // in to the real and imaginary parts of the numerator and denominator. Let omega = pi * f. + // Then the numerator is + // + // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) + b2 * sin(2 * omega)) + // + // and the denominator is + // + // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2 * sin(2 * omega)) + // + // Compute the magnitude and phase from the real and imaginary parts. + + var omega = Math.PI * f; + var numeratorReal = b0 + b1 * Math.cos(omega) + b2 * Math.cos(2 * omega); + var numeratorImag = -(b1 * Math.sin(omega) + b2 * Math.sin(2 * omega)); + var denominatorReal = 1 + a1 * Math.cos(omega) + a2 * Math.cos(2 * omega); + var denominatorImag = -(a1 * Math.sin(omega) + a2 * Math.sin(2 * omega)); + + var magnitude = Math.sqrt((numeratorReal * numeratorReal + numeratorImag * numeratorImag) + / (denominatorReal * denominatorReal + denominatorImag * denominatorImag)); + var phase = Math.atan2(numeratorImag, numeratorReal) - Math.atan2(denominatorImag, denominatorReal); + + if (phase >= Math.PI) { + phase -= 2 * Math.PI; + } else if (phase <= -Math.PI) { + phase += 2 * Math.PI; + } + + return {magnitude : magnitude, phase : phase}; +} + +// Compute the reference frequency response for the biquad filter |filter| at the frequency samples +// given by |frequencies|. +function frequencyResponseReference(filter, frequencies) +{ + var sampleRate = filter.context.sampleRate; + var normalizedFreq = normalizedFrequency(filter.frequency.value, sampleRate); + var filterCoefficients = createFilter(filter.type, normalizedFreq, filter.Q.value, filter.gain.value); + + var magnitudes = []; + var phases = []; + + for (var k = 0; k < frequencies.length; ++k) { + var response = getResponseAt(filterCoefficients, normalizedFrequency(frequencies[k], sampleRate)); + magnitudes.push(response.magnitude); + phases.push(response.phase); + } + + return {magnitudes : magnitudes, phases : phases}; +} + +// Compute a set of linearly spaced frequencies. +function createFrequencies(nFrequencies, sampleRate) +{ + var frequencies = new Float32Array(nFrequencies); + var nyquist = sampleRate / 2; + var freqDelta = nyquist / nFrequencies; + + for (var k = 0; k < nFrequencies; ++k) { + frequencies[k] = k * freqDelta; + } + + return frequencies; +} + +function linearToDecibels(x) +{ + if (x) { + return 20 * Math.log(x) / Math.LN10; + } else { + return -1000; + } +} + +// Look through the array and find any NaN or infinity. Returns the index of the first occurence or +// -1 if none. +function findBadNumber(signal) +{ + for (var k = 0; k < signal.length; ++k) { + if (!isValidNumber(signal[k])) { + return k; + } + } + return -1; +} + +// Compute absolute value of the difference between phase angles, taking into account the wrapping +// of phases. +function absolutePhaseDifference(x, y) +{ + var diff = Math.abs(x - y); + + if (diff > Math.PI) { + diff = 2 * Math.PI - diff; + } + return diff; +} + +// Compare the frequency response with our expected response. +function compareResponses(filter, frequencies, magResponse, phaseResponse) +{ + var expectedResponse = frequencyResponseReference(filter, frequencies); + + expectedMagnitudes = expectedResponse.magnitudes; + expectedPhases = expectedResponse.phases; + + var n = magResponse.length; + var success = true; + var badResponse = false; + + var maxMagError = -1; + var maxMagErrorIndex = -1; + + var k; + var hasBadNumber; + + hasBadNumber = findBadNumber(magResponse); + ok (hasBadNumber < 0, "Magnitude response has NaN or infinity at " + hasBadNumber); + + hasBadNumber = findBadNumber(phaseResponse); + ok (hasBadNumber < 0, "Phase response has NaN or infinity at " + hasBadNumber); + + // These aren't testing the implementation itself. Instead, these are sanity checks on the + // reference. Failure here does not imply an error in the implementation. + hasBadNumber = findBadNumber(expectedMagnitudes); + ok (hasBadNumber < 0, "Expected magnitude response has NaN or infinity at " + hasBadNumber); + + hasBadNumber = findBadNumber(expectedPhases); + ok (hasBadNumber < 0, "Expected phase response has NaN or infinity at " + hasBadNumber); + + for (k = 0; k < n; ++k) { + var error = Math.abs(linearToDecibels(magResponse[k]) - linearToDecibels(expectedMagnitudes[k])); + if (error > maxMagError) { + maxMagError = error; + maxMagErrorIndex = k; + } + } + + var message = "Magnitude error (" + maxMagError + " dB)"; + message += " exceeded threshold at " + frequencies[maxMagErrorIndex]; + message += " Hz. Actual: " + linearToDecibels(magResponse[maxMagErrorIndex]); + message += " dB, expected: " + linearToDecibels(expectedMagnitudes[maxMagErrorIndex]) + " dB."; + ok(maxMagError < maxAllowedMagError, message); + + var maxPhaseError = -1; + var maxPhaseErrorIndex = -1; + + for (k = 0; k < n; ++k) { + var error = absolutePhaseDifference(phaseResponse[k], expectedPhases[k]); + if (error > maxPhaseError) { + maxPhaseError = error; + maxPhaseErrorIndex = k; + } + } + + message = "Phase error (radians) (" + maxPhaseError; + message += ") exceeded threshold at " + frequencies[maxPhaseErrorIndex]; + message += " Hz. Actual: " + phaseResponse[maxPhaseErrorIndex]; + message += " expected: " + expectedPhases[maxPhaseErrorIndex]; + + ok(maxPhaseError < maxAllowedPhaseError, message); +} + +context = new AudioContext(); + +filter = context.createBiquadFilter(); + +// Arbitrarily test a peaking filter, but any kind of filter can be tested. +filter.type = "peaking"; +filter.frequency.value = filterCutoff; +filter.Q.value = filterQ; +filter.gain.value = filterGain; + +var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate); +magResponse = new Float32Array(numberOfFrequencies); +phaseResponse = new Float32Array(numberOfFrequencies); + +filter.getFrequencyResponse(frequencies, magResponse, phaseResponse); +compareResponses(filter, frequencies, magResponse, phaseResponse); + +SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html new file mode 100644 index 000000000..cb9aa274c --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode High Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + // The filters we want to test. + var filterParameters = [{cutoff : 0, q : 1, gain : 1 }, + {cutoff : 1, q : 1, gain : 1 }, + {cutoff : 0.25, q : 1, gain : 1 }, + ]; + + createTestAndRun(context, "highpass", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html new file mode 100644 index 000000000..3581459b0 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode High Shelf Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + // The filters we want to test. + var filterParameters = [{cutoff : 0, q : 10, gain : 10 }, + {cutoff : 1, q : 10, gain : 10 }, + {cutoff : 0.25, q : 10, gain : 10 }, + ]; + + createTestAndRun(context, "highshelf", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html new file mode 100644 index 000000000..b0c12558f --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode Low Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + // The filters we want to test. + var filterParameters = [{cutoff : 0, q : 1, gain : 1 }, + {cutoff : 1, q : 1, gain : 1 }, + {cutoff : 0.25, q : 1, gain : 1 }, + {cutoff : 0.25, q : 1, gain : 1, detune : 100 }, + {cutoff : 0.01, q : 1, gain : 1, detune : -200 }, + ]; + createTestAndRun(context, "lowpass", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html new file mode 100644 index 000000000..3c83bfaa3 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode Low Shelf Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + // The filters we want to test. + var filterParameters = [{cutoff : 0, q : 10, gain : 10 }, + {cutoff : 1, q : 10, gain : 10 }, + {cutoff : 0.25, q : 10, gain : 10 }, + ]; + + createTestAndRun(context, "lowshelf", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html new file mode 100644 index 000000000..551410c66 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode Notch Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + var filterParameters = [{cutoff : 0, q : 10, gain : 1 }, + {cutoff : 1, q : 10, gain : 1 }, + {cutoff : .5, q : 0, gain : 1 }, + {cutoff : 0.25, q : 10, gain : 1 }, + ]; + + createTestAndRun(context, "notch", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html b/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html new file mode 100644 index 000000000..33fcc225a --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode Low Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // Create offline audio context. + var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + + // The filters we want to test. + var filterParameters = [{cutoff : 0, q : 10, gain : 10 }, + {cutoff : 1, q : 10, gain : 10 }, + {cutoff : .5, q : 0, gain : 10 }, + {cutoff : 0.25, q : 10, gain : 10 }, + ]; + + createTestAndRun(context, "peaking", filterParameters); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html new file mode 100644 index 000000000..fd02e734f --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode All Pass Filter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="audio-testing.js"></script> +<script src="biquad-filters.js"></script> +<script src="biquad-testing.js"></script> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + // A high sample rate shows the issue more clearly. + var sampleRate = 192000; + // Some short duration because we don't need to run the test for very long. + var testDurationSec = 0.5; + var testDurationFrames = testDurationSec * sampleRate; + + // Amplitude experimentally determined to give a biquad output close to 1. (No attempt was + // made to produce exactly 1; it's not needed.) + var sourceAmplitude = 100; + + // The output of the biquad filter should not change by more than this much between output + // samples. Threshold was determined experimentally. + var glitchThreshold = 0.01292; + + // Test that a Biquad filter doesn't have it's output terminated because the input has gone + // away. Generally, when a source node is finished, it disconnects itself from any downstream + // nodes. This is the correct behavior. Nodes that have no inputs (disconnected) are + // generally assumed to output zeroes. This is also desired behavior. However, biquad + // filters have memory so they should not suddenly output zeroes when the input is + // disconnected. This test checks to see if the output doesn't suddenly change to zero. + var context = new OfflineAudioContext(1, testDurationFrames, sampleRate); + + // Create an impulse source. + var buffer = context.createBuffer(1, 1, context.sampleRate); + buffer.getChannelData(0)[0] = sourceAmplitude; + var source = context.createBufferSource(); + source.buffer = buffer; + + // Create the biquad filter. It doesn't really matter what kind, so the default filter type + // and parameters is fine. Connect the source to it. + var biquad = context.createBiquadFilter(); + source.connect(biquad); + biquad.connect(context.destination); + + source.start(); + + context.startRendering().then(function(result) { + // There should be no large discontinuities in the output + var buffer = result.getChannelData(0); + var maxGlitchIndex = 0; + var maxGlitchValue = 0.0; + for (var i = 1; i < buffer.length; i++) { + var diff = Math.abs(buffer[i-1] - buffer[i]); + if (diff >= glitchThreshold) { + if (diff > maxGlitchValue) { + maxGlitchIndex = i; + maxGlitchValue = diff; + } + } + } + ok(maxGlitchIndex == 0, 'glitches detected in biquad output: maximum glitch at ' + maxGlitchIndex + ' with diff of ' + maxGlitchValue); + SimpleTest.finish(); + }) +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_iirFilterNode.html b/dom/media/webaudio/test/blink/test_iirFilterNode.html new file mode 100644 index 000000000..47f936761 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_iirFilterNode.html @@ -0,0 +1,467 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test IIRFilterNode GetFrequencyResponse</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <script type="text/javascript" src="biquad-filters.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var sampleRate = 48000; + var testDurationSec = 1; + var testFrames = testDurationSec * sampleRate; + + var testPromises = [] + testPromises.push(function () { + // Test that the feedback coefficients are normalized. Do this be creating two + // IIRFilterNodes. One has normalized coefficients, and one doesn't. Compute the + // difference and make sure they're the same. + var context = new OfflineAudioContext(2, testFrames, sampleRate); + + // Use a simple impulse as the source. + var buffer = context.createBuffer(1, 1, sampleRate); + buffer.getChannelData(0)[0] = 1; + var source = context.createBufferSource(); + source.buffer = buffer; + + // Gain node for computing the difference between the filters. + var gain = context.createGain(); + gain.gain.value = -1; + + // The IIR filters. Use a common feedforward array. + var ff = [1]; + + var fb1 = [1, .9]; + + var fb2 = new Float64Array(2); + // Scale the feedback coefficients by an arbitrary factor. + var coefScaleFactor = 2; + for (var k = 0; k < fb2.length; ++k) { + fb2[k] = coefScaleFactor * fb1[k]; + } + + var iir1 = context.createIIRFilter(ff, fb1); + var iir2 = context.createIIRFilter(ff, fb2); + + // Create the graph. The output of iir1 (normalized coefficients) is channel 0, and the + // output of iir2 (unnormalized coefficients), with appropriate scaling, is channel 1. + var merger = context.createChannelMerger(2); + source.connect(iir1); + source.connect(iir2); + iir1.connect(merger, 0, 0); + iir2.connect(gain); + + // The gain for the gain node should be set to compensate for the scaling of the + // coefficients. Since iir2 has scaled the coefficients by coefScaleFactor, the output is + // reduced by the same factor, so adjust the gain to scale the output of iir2 back up. + gain.gain.value = coefScaleFactor; + gain.connect(merger, 0, 1); + + merger.connect(context.destination); + + source.start(); + + // Rock and roll! + + return context.startRendering().then(function (result) { + // Find the max amplitude of the result, which should be near zero. + var iir1Data = result.getChannelData(0); + var iir2Data = result.getChannelData(1); + + // Threshold isn't exactly zero because the arithmetic is done differently between the + // IIRFilterNode and the BiquadFilterNode. + compareChannels(iir1Data, iir2Data); + }); + }()); + + testPromises.push(function () { + // Create a simple 1-zero filter and compare with the expected output. + var context = new OfflineAudioContext(1, testFrames, sampleRate); + + // Use a simple impulse as the source + var buffer = context.createBuffer(1, 1, sampleRate); + buffer.getChannelData(0)[0] = 1; + var source = context.createBufferSource(); + source.buffer = buffer; + + // The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving average. This is + // rather arbitrary; keep it simple. + + var iir = context.createIIRFilter([0.5, 0.5], [1]); + + // Create the graph + source.connect(iir); + iir.connect(context.destination); + + // Rock and roll! + source.start(); + + return context.startRendering().then(function (result) { + var actual = result.getChannelData(0); + var expected = new Float64Array(testFrames); + // The filter is a simple 2-point moving average of an impulse, so the first two values + // are non-zero and the rest are zero. + expected[0] = 0.5; + expected[1] = 0.5; + compareChannels(actual, expected); + }); + }()); + + testPromises.push(function () { + // Create a simple 1-pole filter and compare with the expected output. + + // The filter is y(n) + c*y(n-1)= x(n). The analytical response is (-c)^n, so choose a + // suitable number of frames to run the test for where the output isn't flushed to zero. + var c = 0.9; + var eps = 1e-20; + var duration = Math.floor(Math.log(eps) / Math.log(Math.abs(c))); + var context = new OfflineAudioContext(1, duration, sampleRate); + + // Use a simple impulse as the source + var buffer = context.createBuffer(1, 1, sampleRate); + buffer.getChannelData(0)[0] = 1; + var source = context.createBufferSource(); + source.buffer = buffer; + + var iir = context.createIIRFilter([1], [1, c]); + + // Create the graph + source.connect(iir); + iir.connect(context.destination); + + // Rock and roll! + source.start(); + + return context.startRendering().then(function (result) { + var actual = result.getChannelData(0); + var expected = new Float64Array(actual.length); + + // The filter is a simple 1-pole filter: y(n) = -c*y(n-k)+x(n), with an impulse as the + // input. + expected[0] = 1; + for (k = 1; k < testFrames; ++k) { + expected[k] = -c * expected[k-1]; + } + + compareChannels(actual, expected); + }); + }()); + + // This function creates an IIRFilterNode equivalent to the specified + // BiquadFilterNode and compares the outputs. The + // outputs from the two filters should be virtually identical. + function testWithBiquadFilter(filterType) { + var context = new OfflineAudioContext(2, testFrames, sampleRate); + + // Use a constant (step function) as the source + var buffer = context.createBuffer(1, testFrames, context.sampleRate); + for (var i = 0; i < testFrames; ++i) { + buffer.getChannelData(0)[i] = 1; + } + var source = context.createBufferSource(); + source.buffer = buffer; + + // Create the biquad. Choose some rather arbitrary values for Q and gain for the biquad + // so that the shelf filters aren't identical. + var biquad = context.createBiquadFilter(); + biquad.type = filterType; + biquad.Q.value = 10; + biquad.gain.value = 10; + + // Create the equivalent IIR Filter node by computing the coefficients of the given biquad + // filter type. + var nyquist = sampleRate / 2; + var coef = createFilter(filterType, + biquad.frequency.value / nyquist, + biquad.Q.value, + biquad.gain.value); + + var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]); + + var merger = context.createChannelMerger(2); + // Create the graph + source.connect(biquad); + source.connect(iir); + + biquad.connect(merger, 0, 0); + iir.connect(merger, 0, 1); + + merger.connect(context.destination); + + // Rock and roll! + source.start(); + + return context.startRendering().then(function (result) { + // Find the max amplitude of the result, which should be near zero. + var expected = result.getChannelData(0); + var actual = result.getChannelData(1); + compareChannels(actual, expected); + }); + } + + biquadFilterTypes = ["lowpass", "highpass", "bandpass", "notch", + "allpass", "lowshelf", "highshelf", "peaking"]; + + // Create a set of tasks based on biquadTestConfigs. + for (var i = 0; i < biquadFilterTypes.length; ++i) { + testPromises.push(testWithBiquadFilter(biquadFilterTypes[i])); + } + + testPromises.push(function () { + // Multi-channel test. Create a biquad filter and the equivalent IIR filter. Filter the + // same multichannel signal and compare the results. + var nChannels = 3; + var context = new OfflineAudioContext(nChannels, testFrames, sampleRate); + + // Create a set of oscillators as the multi-channel source. + var source = []; + + for (k = 0; k < nChannels; ++k) { + source[k] = context.createOscillator(); + source[k].type = "sawtooth"; + // The frequency of the oscillator is pretty arbitrary, but each oscillator should have a + // different frequency. + source[k].frequency.value = 100 + k * 100; + } + + var merger = context.createChannelMerger(3); + + var biquad = context.createBiquadFilter(); + + // Create the equivalent IIR Filter node. + var nyquist = sampleRate / 2; + var coef = createFilter(biquad.type, + biquad.frequency.value / nyquist, + biquad.Q.value, + biquad.gain.value); + var fb = [1, coef.a1, coef.a2]; + var ff = [coef.b0, coef.b1, coef.b2]; + + var iir = context.createIIRFilter(ff, fb); + // Gain node to compute the difference between the IIR and biquad filter. + var gain = context.createGain(); + gain.gain.value = -1; + + // Create the graph. + for (k = 0; k < nChannels; ++k) + source[k].connect(merger, 0, k); + + merger.connect(biquad); + merger.connect(iir); + iir.connect(gain); + biquad.connect(context.destination); + gain.connect(context.destination); + + for (k = 0; k < nChannels; ++k) + source[k].start(); + + return context.startRendering().then(function (result) { + var errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5]; + + // Check the difference signal on each channel + for (channel = 0; channel < result.numberOfChannels; ++channel) { + // Find the max amplitude of the result, which should be near zero. + var data = result.getChannelData(channel); + var maxError = data.reduce(function(reducedValue, currentValue) { + return Math.max(reducedValue, Math.abs(currentValue)); + }); + + ok(maxError <= errorThresholds[channel], "Max difference between IIR and Biquad on channel " + channel); + } + }); + }()); + + testPromises.push(function () { + // Apply an IIRFilter to the given input signal. + // + // IIR filter in the time domain is + // + // y[n] = sum(ff[k]*x[n-k], k, 0, M) - sum(fb[k]*y[n-k], k, 1, N) + // + function iirFilter(input, feedforward, feedback) { + // For simplicity, create an x buffer that contains the input, and a y buffer that contains + // the output. Both of these buffers have an initial work space to implement the initial + // memory of the filter. + var workSize = Math.max(feedforward.length, feedback.length); + var x = new Float32Array(input.length + workSize); + + // Float64 because we want to match the implementation that uses doubles to minimize + // roundoff. + var y = new Float64Array(input.length + workSize); + + // Copy the input over. + for (var k = 0; k < input.length; ++k) + x[k + feedforward.length] = input[k]; + + // Run the filter + for (var n = 0; n < input.length; ++n) { + var index = n + workSize; + var yn = 0; + for (var k = 0; k < feedforward.length; ++k) + yn += feedforward[k] * x[index - k]; + for (var k = 0; k < feedback.length; ++k) + yn -= feedback[k] * y[index - k]; + + y[index] = yn; + } + + return y.slice(workSize).map(Math.fround); + } + + // Cascade the two given biquad filters to create one IIR filter. + function cascadeBiquads(f1Coef, f2Coef) { + // The biquad filters are: + // + // f1 = (b10 + b11/z + b12/z^2)/(1 + a11/z + a12/z^2); + // f2 = (b20 + b21/z + b22/z^2)/(1 + a21/z + a22/z^2); + // + // To cascade them, multiply the two transforms together to get a fourth order IIR filter. + + var numProduct = [f1Coef.b0 * f2Coef.b0, + f1Coef.b0 * f2Coef.b1 + f1Coef.b1 * f2Coef.b0, + f1Coef.b0 * f2Coef.b2 + f1Coef.b1 * f2Coef.b1 + f1Coef.b2 * f2Coef.b0, + f1Coef.b1 * f2Coef.b2 + f1Coef.b2 * f2Coef.b1, + f1Coef.b2 * f2Coef.b2 + ]; + + var denProduct = [1, + f2Coef.a1 + f1Coef.a1, + f2Coef.a2 + f1Coef.a1 * f2Coef.a1 + f1Coef.a2, + f1Coef.a1 * f2Coef.a2 + f1Coef.a2 * f2Coef.a1, + f1Coef.a2 * f2Coef.a2 + ]; + + return { + ff: numProduct, + fb: denProduct + } + } + + // Find the magnitude of the root of the quadratic that has the maximum magnitude. + // + // The quadratic is z^2 + a1 * z + a2 and we want the root z that has the largest magnitude. + function largestRootMagnitude(a1, a2) { + var discriminant = a1 * a1 - 4 * a2; + if (discriminant < 0) { + // Complex roots: -a1/2 +/- i*sqrt(-d)/2. Thus the magnitude of each root is the same + // and is sqrt(a1^2/4 + |d|/4) + var d = Math.sqrt(-discriminant); + return Math.hypot(a1 / 2, d / 2); + } else { + // Real roots + var d = Math.sqrt(discriminant); + return Math.max(Math.abs((-a1 + d) / 2), Math.abs((-a1 - d) / 2)); + } + } + + // Cascade 2 lowpass biquad filters and compare that with the equivalent 4th order IIR + // filter. + + var nyquist = sampleRate / 2; + // Compute the coefficients of a lowpass filter. + + // First some preliminary stuff. Compute the coefficients of the biquad. This is used to + // figure out how frames to use in the test. + var biquadType = "lowpass"; + var biquadCutoff = 350; + var biquadQ = 5; + var biquadGain = 1; + + var coef = createFilter(biquadType, + biquadCutoff / nyquist, + biquadQ, + biquadGain); + + // Cascade the biquads together to create an equivalent IIR filter. + var cascade = cascadeBiquads(coef, coef); + + // Since we're cascading two identical biquads, the root of denominator of the IIR filter is + // repeated, so the root of the denominator with the largest magnitude occurs twice. The + // impulse response of the IIR filter will be roughly c*(r*r)^n at time n, where r is the + // root of largest magnitude. This approximation gets better as n increases. We can use + // this to get a rough idea of when the response has died down to a small value. + + // This is the value we will use to determine how many frames to render. Rendering too many + // is a waste of time and also makes it hard to compare the actual result to the expected + // because the magnitudes are so small that they could be mostly round-off noise. + // + // Find magnitude of the root with largest magnitude + var rootMagnitude = largestRootMagnitude(coef.a1, coef.a2); + + // Find n such that |r|^(2*n) <= eps. That is, n = log(eps)/(2*log(r)). Somewhat + // arbitrarily choose eps = 1e-20; + var eps = 1e-20; + var framesForTest = Math.floor(Math.log(eps) / (2 * Math.log(rootMagnitude))); + + // We're ready to create the graph for the test. The offline context has two channels: + // channel 0 is the expected (cascaded biquad) result and channel 1 is the actual IIR filter + // result. + var context = new OfflineAudioContext(2, framesForTest, sampleRate); + + // Use a simple impulse with a large (arbitrary) amplitude as the source + var amplitude = 1; + var buffer = context.createBuffer(1, testFrames, sampleRate); + buffer.getChannelData(0)[0] = amplitude; + var source = context.createBufferSource(); + source.buffer = buffer; + + // Create the two biquad filters. Doesn't really matter what, but for simplicity we choose + // identical lowpass filters with the same parameters. + var biquad1 = context.createBiquadFilter(); + biquad1.type = biquadType; + biquad1.frequency.value = biquadCutoff; + biquad1.Q.value = biquadQ; + + var biquad2 = context.createBiquadFilter(); + biquad2.type = biquadType; + biquad2.frequency.value = biquadCutoff; + biquad2.Q.value = biquadQ; + + var iir = context.createIIRFilter(cascade.ff, cascade.fb); + + // Create the merger to get the signals into multiple channels + var merger = context.createChannelMerger(2); + + // Create the graph, filtering the source through two biquads. + source.connect(biquad1); + biquad1.connect(biquad2); + biquad2.connect(merger, 0, 0); + + source.connect(iir); + iir.connect(merger, 0, 1); + + merger.connect(context.destination); + + // Now filter the source through the IIR filter. + var y = iirFilter(buffer.getChannelData(0), cascade.ff, cascade.fb); + + // Rock and roll! + source.start(); + + return context.startRendering().then(function(result) { + var expected = result.getChannelData(0); + var actual = result.getChannelData(1); + + compareChannels(actual, expected); + + }); + }()); + + // Wait for all tests + Promise.all(testPromises).then(function () { + SimpleTest.finish(); + }, function () { + SimpleTest.finish(); + }); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html b/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html new file mode 100644 index 000000000..cb5cf33ed --- /dev/null +++ b/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test IIRFilterNode GetFrequencyResponse</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <script type="text/javascript" src="biquad-filters.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + // Modified from WebKit/LayoutTests/webaudio/iirfilter-getFrequencyResponse.html + var sampleRate = 48000; + var testDurationSec = 0.01; + + // Compute a set of linearly spaced frequencies. + function createFrequencies(nFrequencies, sampleRate) + { + var frequencies = new Float32Array(nFrequencies); + var nyquist = sampleRate / 2; + var freqDelta = nyquist / nFrequencies; + + for (var k = 0; k < nFrequencies; ++k) { + frequencies[k] = k * freqDelta; + } + + return frequencies; + } + + // Number of frequency samples to take. + var numberOfFrequencies = 1000; + + var context = new OfflineAudioContext(1, testDurationSec * sampleRate, sampleRate); + + var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate); + + // 1-Pole IIR Filter + var iir = context.createIIRFilter([1], [1, -0.9]); + + var iirMag = new Float32Array(numberOfFrequencies); + var iirPhase = new Float32Array(numberOfFrequencies); + var trueMag = new Float32Array(numberOfFrequencies); + var truePhase = new Float32Array(numberOfFrequencies); + + // The IIR filter is + // H(z) = 1/(1 - 0.9*z^(-1)). + // + // The frequency response is + // H(exp(j*w)) = 1/(1 - 0.9*exp(-j*w)). + // + // Thus, the magnitude is + // |H(exp(j*w))| = 1/sqrt(1.81-1.8*cos(w)). + // + // The phase is + // arg(H(exp(j*w)) = atan(0.9*sin(w)/(.9*cos(w)-1)) + + var frequencyScale = Math.PI / (sampleRate / 2); + + for (var k = 0; k < frequencies.length; ++k) { + var omega = frequencyScale * frequencies[k]; + trueMag[k] = 1/Math.sqrt(1.81-1.8*Math.cos(omega)); + truePhase[k] = Math.atan(0.9 * Math.sin(omega) / (0.9 * Math.cos(omega) - 1)); + } + + iir.getFrequencyResponse(frequencies, iirMag, iirPhase); + compareChannels(iirMag, trueMag); + compareChannels(iirPhase, truePhase); + + // Compare IIR and Biquad Filter + // Create an IIR filter equivalent to the biquad filter. Compute the frequency response for both and verify that they are the same. + var biquad = context.createBiquadFilter(); + var coef = createFilter(biquad.type, + biquad.frequency.value / (context.sampleRate / 2), + biquad.Q.value, + biquad.gain.value); + + var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]); + + var biquadMag = new Float32Array(numberOfFrequencies); + var biquadPhase = new Float32Array(numberOfFrequencies); + var iirMag = new Float32Array(numberOfFrequencies); + var iirPhase = new Float32Array(numberOfFrequencies); + + biquad.getFrequencyResponse(frequencies, biquadMag, biquadPhase); + iir.getFrequencyResponse(frequencies, iirMag, iirPhase); + compareChannels(iirMag, biquadMag); + compareChannels(iirPhase, biquadPhase); + + SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/browser.ini b/dom/media/webaudio/test/browser.ini new file mode 100644 index 000000000..60ed969f1 --- /dev/null +++ b/dom/media/webaudio/test/browser.ini @@ -0,0 +1 @@ +[browser_bug1181073.js]
\ No newline at end of file diff --git a/dom/media/webaudio/test/browser_bug1181073.js b/dom/media/webaudio/test/browser_bug1181073.js new file mode 100644 index 000000000..6ee48144c --- /dev/null +++ b/dom/media/webaudio/test/browser_bug1181073.js @@ -0,0 +1,40 @@ +add_task(function*() { + // Make the min_background_timeout_value very high to avoid problems on slow machines + yield new Promise(resolve => SpecialPowers.pushPrefEnv({ + 'set': [['dom.min_background_timeout_value', 3000]] + }, resolve)); + + // Make a new tab, and put it in the background + yield BrowserTestUtils.withNewTab("about:blank", function*(browser) { + yield BrowserTestUtils.withNewTab("about:blank", function*() { + let time = yield ContentTask.spawn(browser, null, function () { + return new Promise(resolve => { + let start = content.performance.now(); + let id = content.window.setInterval(function() { + let end = content.performance.now(); + content.window.clearInterval(id); + resolve(end - start); + }, 0); + }); + }); + + ok(time > 2000, "Interval is throttled with no webaudio (" + time + " ms)"); + + time = yield ContentTask.spawn(browser, null, function () { + return new Promise(resolve => { + // Create an audio context, and save it on the window so it doesn't get GCed + content.window._audioCtx = new content.window.AudioContext(); + + let start = content.performance.now(); + let id = content.window.setInterval(function() { + let end = content.performance.now(); + content.window.clearInterval(id); + resolve(end - start); + }, 0); + }); + }); + + ok(time < 1000, "Interval is not throttled with an audio context present (" + time + " ms)"); + }); + }); +}); diff --git a/dom/media/webaudio/test/corsServer.sjs b/dom/media/webaudio/test/corsServer.sjs new file mode 100644 index 000000000..1804c7862 --- /dev/null +++ b/dom/media/webaudio/test/corsServer.sjs @@ -0,0 +1,25 @@ +function handleRequest(request, response) +{ + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var bis = Components.classes["@mozilla.org/binaryinputstream;1"]. + createInstance(Components.interfaces.nsIBinaryInputStream); + var paths = "tests/dom/media/webaudio/test/small-shot.ogg"; + var split = paths.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + bis.setInputStream(fis); + var bytes = bis.readBytes(bis.available()); + response.setHeader("Content-Type", "video/ogg", false); + response.setHeader("Content-Length", ""+ bytes.length, false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.write(bytes, bytes.length); + response.processAsync(); + response.finish(); + bis.close(); +} diff --git a/dom/media/webaudio/test/invalid.txt b/dom/media/webaudio/test/invalid.txt new file mode 100644 index 000000000..c44840faf --- /dev/null +++ b/dom/media/webaudio/test/invalid.txt @@ -0,0 +1 @@ +Surely this is not an audio file! diff --git a/dom/media/webaudio/test/layouttest-glue.js b/dom/media/webaudio/test/layouttest-glue.js new file mode 100644 index 000000000..db1aa563b --- /dev/null +++ b/dom/media/webaudio/test/layouttest-glue.js @@ -0,0 +1,19 @@ +// Reimplementation of the LayoutTest API from Blink so we can easily port +// WebAudio tests to Simpletest, without touching the internals of the test. + +function testFailed(msg) { + ok(false, msg); +} + +function testPassed(msg) { + ok(true, msg); +} + +function finishJSTest() { + SimpleTest.finish(); +} + +function description(str) { + info(str); +} + diff --git a/dom/media/webaudio/test/mochitest.ini b/dom/media/webaudio/test/mochitest.ini new file mode 100644 index 000000000..4abcce7e3 --- /dev/null +++ b/dom/media/webaudio/test/mochitest.ini @@ -0,0 +1,212 @@ +[DEFAULT] +tags=msg +tags = webaudio +subsuite = media +support-files = + audio-expected.wav + audio-mono-expected-2.wav + audio-mono-expected.wav + audio-quad.wav + audio.ogv + audiovideo.mp4 + audioBufferSourceNodeDetached_worker.js + corsServer.sjs + invalid.txt + layouttest-glue.js + noaudio.webm + small-shot-expected.wav + small-shot-mono-expected.wav + small-shot.ogg + small-shot.mp3 + sweep-300-330-1sec.opus + ting-44.1k-1ch.ogg + ting-44.1k-2ch.ogg + ting-48k-1ch.ogg + ting-48k-2ch.ogg + ting-44.1k-1ch.wav + ting-44.1k-2ch.wav + ting-48k-1ch.wav + ting-48k-2ch.wav + sine-440-10s.opus + webaudio.js + +[test_analyserNode.html] +[test_analyserScale.html] +[test_analyserNodeOutput.html] +[test_analyserNodePassThrough.html] +[test_analyserNodeWithGain.html] +[test_AudioBuffer.html] +[test_audioBufferSourceNode.html] +[test_audioBufferSourceNodeEnded.html] +[test_audioBufferSourceNodeLazyLoopParam.html] +[test_audioBufferSourceNodeLoop.html] +[test_audioBufferSourceNodeLoopStartEnd.html] +[test_audioBufferSourceNodeLoopStartEndSame.html] +[test_audioBufferSourceNodeDetached.html] +skip-if = (toolkit == 'android' && debug) || os == 'win' # bug 1127845, bug 1138468 +[test_audioBufferSourceNodeNoStart.html] +[test_audioBufferSourceNodeNullBuffer.html] +[test_audioBufferSourceNodeOffset.html] +skip-if = (toolkit == 'android') || debug #bug 906752 +[test_audioBufferSourceNodePassThrough.html] +[test_audioBufferSourceNodeRate.html] +[test_AudioContext.html] +[test_AudioContext_disabled.html] +[test_audioContextSuspendResumeClose.html] +tags=capturestream +[test_audioDestinationNode.html] +[test_AudioListener.html] +[test_AudioNodeDevtoolsAPI.html] +[test_audioParamChaining.html] +[test_AudioParamDevtoolsAPI.html] +[test_audioParamExponentialRamp.html] +[test_audioParamGain.html] +[test_audioParamLinearRamp.html] +[test_audioParamSetCurveAtTime.html] +[test_audioParamSetCurveAtTimeTwice.html] +[test_audioParamSetCurveAtTimeZeroDuration.html] +[test_audioParamSetTargetAtTime.html] +[test_audioParamSetTargetAtTimeZeroTimeConstant.html] +[test_audioParamSetValueAtTime.html] +[test_audioParamTimelineDestinationOffset.html] +[test_badConnect.html] +[test_biquadFilterNode.html] +[test_biquadFilterNodePassThrough.html] +[test_biquadFilterNodeWithGain.html] +[test_bug808374.html] +[test_bug827541.html] +[test_bug839753.html] +[test_bug845960.html] +[test_bug856771.html] +[test_bug866570.html] +[test_bug866737.html] +[test_bug867089.html] +[test_bug867104.html] +[test_bug867174.html] +[test_bug867203.html] +[test_bug875221.html] +[test_bug875402.html] +[test_bug894150.html] +[test_bug956489.html] +[test_bug964376.html] +[test_bug966247.html] +tags=capturestream +[test_bug972678.html] +[test_bug1113634.html] +[test_bug1118372.html] +[test_bug1027864.html] +[test_bug1056032.html] +skip-if = toolkit == 'android' # bug 1056706 +[test_bug1255618.html] +[test_bug1267579.html] +[test_channelMergerNode.html] +[test_channelMergerNodeWithVolume.html] +[test_channelSplitterNode.html] +[test_channelSplitterNodeWithVolume.html] +skip-if = (android_version == '18' && debug) # bug 1158417 +[test_convolverNode.html] +[test_convolverNode_mono_mono.html] +[test_convolverNodeChannelCount.html] +[test_convolverNodeDelay.html] +[test_convolverNodeFiniteInfluence.html] +[test_convolverNodePassThrough.html] +[test_convolverNodeWithGain.html] +[test_currentTime.html] +[test_decodeMultichannel.html] +[test_decodeAudioDataPromise.html] +[test_decodeOpusTail.html] +[test_delayNode.html] +[test_delayNodeAtMax.html] +[test_delayNodeChannelChanges.html] +skip-if = toolkit == 'android' # bug 1056706 +[test_delayNodeCycles.html] +[test_delayNodePassThrough.html] +[test_delayNodeSmallMaxDelay.html] +[test_delayNodeTailIncrease.html] +[test_delayNodeTailWithDisconnect.html] +[test_delayNodeTailWithGain.html] +[test_delayNodeTailWithReconnect.html] +[test_delayNodeWithGain.html] +[test_disconnectAll.html] +[test_disconnectAudioParam.html] +[test_disconnectAudioParamFromOutput.html] +[test_disconnectExceptions.html] +[test_disconnectFromAudioNode.html] +[test_disconnectFromAudioNodeAndOutput.html] +[test_disconnectFromAudioNodeAndOutputAndInput.html] +[test_disconnectFromAudioNodeMultipleConnection.html] +[test_disconnectFromOutput.html] +[test_dynamicsCompressorNode.html] +[test_dynamicsCompressorNodePassThrough.html] +[test_dynamicsCompressorNodeWithGain.html] +[test_gainNode.html] +[test_gainNodeInLoop.html] +[test_gainNodePassThrough.html] +[test_iirFilterNodePassThrough.html] +[test_maxChannelCount.html] +[test_mediaDecoding.html] +[test_mediaElementAudioSourceNode.html] +tags=capturestream +[test_mediaElementAudioSourceNodeFidelity.html] +tags=capturestream +[test_mediaElementAudioSourceNodePassThrough.html] +tags=capturestream +skip-if = toolkit == 'android' # bug 1145816 +[test_mediaElementAudioSourceNodeVideo.html] +tags=capturestream +[test_mediaElementAudioSourceNodeCrossOrigin.html] +tags=capturestream +skip-if = toolkit == 'android' # bug 1145816 +[test_mediaStreamAudioDestinationNode.html] +[test_mediaStreamAudioSourceNode.html] +[test_mediaStreamAudioSourceNodeCrossOrigin.html] +tags=capturestream +[test_mediaStreamAudioSourceNodeNoGC.html] +[test_mediaStreamAudioSourceNodePassThrough.html] +[test_mediaStreamAudioSourceNodeResampling.html] +tags=capturestream +[test_mixingRules.html] +skip-if = toolkit == 'android' # bug 1091965 +[test_mozaudiochannel.html] +# Android: bug 1061675; OSX 10.6: bug 1097721 +skip-if = (toolkit == 'android') || (os == 'mac' && os_version == '10.6') +[test_nodeToParamConnection.html] +[test_OfflineAudioContext.html] +[test_offlineDestinationChannelCountLess.html] +[test_offlineDestinationChannelCountMore.html] +[test_oscillatorNode.html] +[test_oscillatorNode2.html] +[test_oscillatorNodeNegativeFrequency.html] +[test_oscillatorNodePassThrough.html] +[test_oscillatorNodeStart.html] +[test_oscillatorTypeChange.html] +[test_pannerNode.html] +[test_pannerNode_equalPower.html] +[test_pannerNodeAbove.html] +[test_pannerNodeAtZeroDistance.html] +[test_pannerNodeChannelCount.html] +[test_pannerNodeHRTFSymmetry.html] +[test_pannerNodeTail.html] +[test_pannerNode_maxDistance.html] +[test_stereoPannerNode.html] +[test_stereoPannerNodePassThrough.html] +[test_periodicWave.html] +[test_periodicWaveDisableNormalization.html] +[test_periodicWaveBandLimiting.html] +[test_ScriptProcessorCollected1.html] +[test_scriptProcessorNode.html] +[test_scriptProcessorNodeChannelCount.html] +[test_scriptProcessorNodePassThrough.html] +[test_scriptProcessorNode_playbackTime1.html] +[test_scriptProcessorNodeZeroInputOutput.html] +[test_scriptProcessorNodeNotConnected.html] +[test_sequentialBufferSourceWithResampling.html] +[test_singleSourceDest.html] +[test_stereoPanningWithGain.html] +[test_waveDecoder.html] +[test_waveShaper.html] +[test_waveShaperGain.html] +[test_waveShaperNoCurve.html] +[test_waveShaperPassThrough.html] +[test_waveShaperInvalidLengthCurve.html] +[test_WebAudioMemoryReporting.html] diff --git a/dom/media/webaudio/test/noaudio.webm b/dom/media/webaudio/test/noaudio.webm Binary files differnew file mode 100644 index 000000000..9207017fb --- /dev/null +++ b/dom/media/webaudio/test/noaudio.webm diff --git a/dom/media/webaudio/test/sine-440-10s.opus b/dom/media/webaudio/test/sine-440-10s.opus Binary files differnew file mode 100644 index 000000000..eb9102016 --- /dev/null +++ b/dom/media/webaudio/test/sine-440-10s.opus diff --git a/dom/media/webaudio/test/small-shot-expected.wav b/dom/media/webaudio/test/small-shot-expected.wav Binary files differnew file mode 100644 index 000000000..2faaa8258 --- /dev/null +++ b/dom/media/webaudio/test/small-shot-expected.wav diff --git a/dom/media/webaudio/test/small-shot-mono-expected.wav b/dom/media/webaudio/test/small-shot-mono-expected.wav Binary files differnew file mode 100644 index 000000000..d4e2647e4 --- /dev/null +++ b/dom/media/webaudio/test/small-shot-mono-expected.wav diff --git a/dom/media/webaudio/test/small-shot.mp3 b/dom/media/webaudio/test/small-shot.mp3 Binary files differnew file mode 100644 index 000000000..f9397a510 --- /dev/null +++ b/dom/media/webaudio/test/small-shot.mp3 diff --git a/dom/media/webaudio/test/small-shot.ogg b/dom/media/webaudio/test/small-shot.ogg Binary files differnew file mode 100644 index 000000000..1a41623f8 --- /dev/null +++ b/dom/media/webaudio/test/small-shot.ogg diff --git a/dom/media/webaudio/test/sweep-300-330-1sec.opus b/dom/media/webaudio/test/sweep-300-330-1sec.opus Binary files differnew file mode 100644 index 000000000..619d1e084 --- /dev/null +++ b/dom/media/webaudio/test/sweep-300-330-1sec.opus diff --git a/dom/media/webaudio/test/test_AudioBuffer.html b/dom/media/webaudio/test/test_AudioBuffer.html new file mode 100644 index 000000000..82bfdd420 --- /dev/null +++ b/dom/media/webaudio/test/test_AudioBuffer.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(2, 2048, context.sampleRate); + SpecialPowers.gc(); // Make sure that our channels are accessible after GC + ok(buffer, "Buffer was allocated successfully"); + is(buffer.sampleRate, context.sampleRate, "Correct sample rate"); + is(buffer.length, 2048, "Correct length"); + ok(Math.abs(buffer.duration - 2048 / context.sampleRate) < 0.0001, "Correct duration"); + is(buffer.numberOfChannels, 2, "Correct number of channels"); + for (var i = 0; i < buffer.numberOfChannels; ++i) { + var buf = buffer.getChannelData(i); + ok(buf, "Buffer index " + i + " exists"); + ok(buf instanceof Float32Array, "Result is a typed array"); + is(buf.length, buffer.length, "Correct length"); + var foundNonZero = false; + for (var j = 0; j < buf.length; ++j) { + if (buf[j] != 0) { + foundNonZero = true; + break; + } + buf[j] = j; + } + ok(!foundNonZero, "Buffer " + i + " should be initialized to 0"); + } + + // Now test copying the channel data out of a normal buffer + var copy = new Float32Array(100); + buffer.copyFromChannel(copy, 0, 1024); + for (var i = 0; i < copy.length; ++i) { + is(copy[i], 1024 + i, "Correct sample"); + } + + // Test copying the channel data out of a playing buffer + var srcNode = context.createBufferSource(); + srcNode.buffer = buffer; + srcNode.start(0); + copy = new Float32Array(100); + buffer.copyFromChannel(copy, 0, 1024); + for (var i = 0; i < copy.length; ++i) { + is(copy[i], 1024 + i, "Correct sample"); + } + + // Test copying to the channel data + var newData = new Float32Array(200); + buffer.copyToChannel(newData, 0, 100); + var changedData = buffer.getChannelData(0); + for (var i = 0; i < changedData.length; ++i) { + if (i < 100 || i >= 300) { + is(changedData[i], i, "Untouched sample"); + } else { + is(changedData[i], 0, "Correct sample"); + } + } + + // Now, detach the array buffer + var worker = new Worker("audioBufferSourceNodeDetached_worker.js"); + var data = buffer.getChannelData(0).buffer; + worker.postMessage(data, [data]); + SpecialPowers.gc(); + + expectException(function() { + buffer.copyFromChannel(copy, 0, 1024); + }, DOMException.INDEX_SIZE_ERR); + + expectException(function() { + buffer.copyToChannel(newData, 0, 100); + }, DOMException.INDEX_SIZE_ERR); + + expectException(function() { + context.createBuffer(2, 2048, 7999); + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + context.createBuffer(2, 2048, 192001); + }, DOMException.INDEX_SIZE_ERR); + context.createBuffer(2, 2048, 8000); // no exception + context.createBuffer(2, 2048, 192000); // no exception + context.createBuffer(32, 2048, 48000); // no exception + // Null length + expectException(function() { + context.createBuffer(2, 0, 48000); + }, DOMException.INDEX_SIZE_ERR); + // Null number of channels + expectException(function() { + context.createBuffer(0, 2048, 48000); + }, DOMException.INDEX_SIZE_ERR); + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_AudioContext.html b/dom/media/webaudio/test/test_AudioContext.html new file mode 100644 index 000000000..0cab4354e --- /dev/null +++ b/dom/media/webaudio/test/test_AudioContext.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ac = new AudioContext(); + ok(ac, "Create a AudioContext object"); + ok(ac instanceof EventTarget, "AudioContexts must be EventTargets"); + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_AudioContext_disabled.html b/dom/media/webaudio/test/test_AudioContext_disabled.html new file mode 100644 index 000000000..03e0775ed --- /dev/null +++ b/dom/media/webaudio/test/test_AudioContext_disabled.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can disable the AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const webaudio_interfaces = [ + "AudioContext", + "OfflineAudioContext", + "AudioContext", + "OfflineAudioCompletionEvent", + "AudioNode", + "AudioDestinationNode", + "AudioParam", + "GainNode", + "DelayNode", + "AudioBuffer", + "AudioBufferSourceNode", + "MediaElementAudioSourceNode", + "ScriptProcessorNode", + "AudioProcessingEvent", + "PannerNode", + "AudioListener", + "StereoPannerNode", + "ConvolverNode", + "AnalyserNode", + "ChannelSplitterNode", + "ChannelMergerNode", + "DynamicsCompressorNode", + "BiquadFilterNode", + "IIRFilterNode", + "WaveShaperNode", + "OscillatorNode", + "PeriodicWave", + "MediaStreamAudioSourceNode", + "MediaStreamAudioDestinationNode" +]; + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": [["dom.webaudio.enabled", false]]}, function() { + webaudio_interfaces.forEach((e) => ok(!window[e], e + " must be disabled when the Web Audio API is disabled")); + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_AudioListener.html b/dom/media/webaudio/test/test_AudioListener.html new file mode 100644 index 000000000..07ad154d7 --- /dev/null +++ b/dom/media/webaudio/test/test_AudioListener.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioContext.listener and the AudioListener interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + ok("listener" in context, "AudioContext.listener should exist"); + ok(Math.abs(context.listener.dopplerFactor - 1.0) < 1e-4, "Correct default doppler factor"); + ok(Math.abs(context.listener.speedOfSound - 343.3) < 1e-4, "Correct default speed of sound value"); + context.listener.dopplerFactor = 0.5; + ok(Math.abs(context.listener.dopplerFactor - 0.5) < 1e-4, "The doppler factor value can be changed"); + context.listener.speedOfSound = 400; + ok(Math.abs(context.listener.speedOfSound - 400) < 1e-4, "The speed of sound can be changed"); + // The values set by the following cannot be read from script, but the + // implementation is simple enough, so we just make sure that nothing throws. + with (context.listener) { + setPosition(1.0, 1.0, 1.0); + setOrientation(1.0, 1.0, 1.0, 1.0, 1.0, 1.0); + setVelocity(0.5, 1.0, 1.5); + } + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html b/dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html new file mode 100644 index 000000000..49f71505d --- /dev/null +++ b/dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the devtool AudioNode API</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + SimpleTest.waitForExplicitFinish(); + + function id(node) { + return SpecialPowers.getPrivilegedProps(node, "id"); + } + + var ac = new AudioContext(); + var ids; + var weak; + (function() { + var src1 = ac.createBufferSource(); + var src2 = ac.createBufferSource(); + ok(id(src2) > id(src1), "The ID should be monotonic"); + ok(id(src1) > id(ac.destination), "The ID of the destination node should be the lowest"); + ids = [id(src1), id(src2)]; + weak = SpecialPowers.Cu.getWeakReference(src1); + is(SpecialPowers.unwrap(weak.get()), src1, "The node should support a weak reference"); + })(); + function observer(subject, topic, data) { + var id = parseInt(data); + var index = ids.indexOf(id); + if (index != -1) { + info("Dropping id " + id + " at index " + index); + ids.splice(index, 1); + if (ids.length == 0) { + SimpleTest.executeSoon(function() { + is(weak.get(), null, "The weak reference must be dropped now"); + SpecialPowers.removeObserver(observer, "webaudio-node-demise"); + SimpleTest.finish(); + }); + } + } + } + SpecialPowers.addObserver(observer, "webaudio-node-demise", false); + + forceCC(); + forceCC(); + + function forceCC() { + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); + } + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html b/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html new file mode 100644 index 000000000..9b59dda8a --- /dev/null +++ b/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the devtool AudioParam API</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + function checkIdAndName(node, name) { + is(SpecialPowers.getPrivilegedProps(node, "id"), + SpecialPowers.getPrivilegedProps(node[name], "parentNodeId"), + "The parent id should be correct"); + is(SpecialPowers.getPrivilegedProps(node[name], "name"), name, + "The name of the AudioParam should be correct."); + } + + var ac = new AudioContext(), + gain = ac.createGain(), + osc = ac.createOscillator(), + del = ac.createDelay(), + source = ac.createBufferSource(), + stereoPanner = ac.createStereoPanner(), + comp = ac.createDynamicsCompressor(), + biquad = ac.createBiquadFilter(); + + checkIdAndName(gain, "gain"); + checkIdAndName(osc, "frequency"); + checkIdAndName(osc, "detune"); + checkIdAndName(del, "delayTime"); + checkIdAndName(source, "playbackRate"); + checkIdAndName(source, "detune"); + checkIdAndName(stereoPanner, "pan"); + checkIdAndName(comp, "threshold"); + checkIdAndName(comp, "knee"); + checkIdAndName(comp, "ratio"); + checkIdAndName(comp, "attack"); + checkIdAndName(comp, "release"); + checkIdAndName(biquad, "frequency"); + checkIdAndName(biquad, "detune"); + checkIdAndName(biquad, "Q"); + checkIdAndName(biquad, "gain"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_OfflineAudioContext.html b/dom/media/webaudio/test/test_OfflineAudioContext.html new file mode 100644 index 000000000..81d3e2313 --- /dev/null +++ b/dom/media/webaudio/test/test_OfflineAudioContext.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test OfflineAudioContext</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var renderedBuffer = null; +var finished = 0; + +function finish() { + finished++; + if (finished == 2) { + SimpleTest.finish(); + } +} + +function setOrCompareRenderedBuffer(aRenderedBuffer) { + if (renderedBuffer) { + is(renderedBuffer, aRenderedBuffer, "Rendered buffers from the event and the promise should be the same"); + finish(); + } else { + renderedBuffer = aRenderedBuffer; + } +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new OfflineAudioContext(2, 100, 22050); + ok(ctx instanceof EventTarget, "OfflineAudioContexts must be EventTargets"); + is(ctx.length, 100, "OfflineAudioContext.length is equal to the value passed to the ctor."); + + var buf = ctx.createBuffer(2, 100, ctx.sampleRate); + for (var i = 0; i < 2; ++i) { + for (var j = 0; j < 100; ++j) { + buf.getChannelData(i)[j] = Math.sin(2 * Math.PI * 200 * j / ctx.sampleRate); + } + } + + expectException(function() { + ctx.createMediaStreamDestination(); + }, DOMException.NOT_SUPPORTED_ERR); + + expectException(function() { + new OfflineAudioContext(2, 100, 0); + }, DOMException.NOT_SUPPORTED_ERR); + expectException(function() { + new OfflineAudioContext(2, 100, -1); + }, DOMException.NOT_SUPPORTED_ERR); + expectException(function() { + new OfflineAudioContext(0, 100, 44100); + }, DOMException.NOT_SUPPORTED_ERR); + new OfflineAudioContext(32, 100, 44100); + expectException(function() { + new OfflineAudioContext(33, 100, 44100); + }, DOMException.NOT_SUPPORTED_ERR); + expectException(function() { + new OfflineAudioContext(2, 0, 44100); + }, DOMException.NOT_SUPPORTED_ERR); + + var src = ctx.createBufferSource(); + src.buffer = buf; + src.start(0); + src.connect(ctx.destination); + + ctx.addEventListener("complete", function(e) { + ok(e instanceof OfflineAudioCompletionEvent, "Correct event received"); + is(e.renderedBuffer.numberOfChannels, 2, "Correct expected number of buffers"); + ok(renderedBuffer != null, "The event should be fired after the promise callback."); + expectNoException(function() { + ctx.startRendering().then(function() { + ok(false, "Promise should not resolve when startRendering is called a second time on an OfflineAudioContext") + finish(); + }).catch(function(err) { + ok(true, "Promise should reject when startRendering is called a second time on an OfflineAudioContext") + finish(); + }); + }); + compareBuffers(e.renderedBuffer, buf); + setOrCompareRenderedBuffer(e.renderedBuffer); + + }, false); + + expectNoException(function() { + ctx.startRendering().then(function(b) { + is(renderedBuffer, null, "The promise callback should be called first."); + setOrCompareRenderedBuffer(b); + }).catch(function (error) { + ok(false, "The promise from OfflineAudioContext.startRendering should never be rejected"); + }); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_ScriptProcessorCollected1.html b/dom/media/webaudio/test/test_ScriptProcessorCollected1.html new file mode 100644 index 000000000..931f995df --- /dev/null +++ b/dom/media/webaudio/test/test_ScriptProcessorCollected1.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ScriptProcessorNode in cycle with no listener is collected</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var observer = function(subject, topic, data) { + var id = parseInt(data); + var index = ids.indexOf(id); + if (index != -1) { + ok(true, "Collected AudioNode id " + id + " at index " + index); + ids.splice(index, 1); + } +} + +SpecialPowers.addObserver(observer, "webaudio-node-demise", false); + +SimpleTest.registerCleanupFunction(function() { + if (observer) { + SpecialPowers.removeObserver(observer, "webaudio-node-demise"); + } +}); + +var ac = new AudioContext(); + +var testProcessor = ac.createScriptProcessor(256, 1, 0); +var delay = ac.createDelay(); +testProcessor.connect(delay); +delay.connect(testProcessor); + +var referenceProcessor = ac.createScriptProcessor(256, 1, 0); +var gain = ac.createGain(); +gain.connect(referenceProcessor); + +var processCount = 0; +testProcessor.onaudioprocess = function(event) { + ++processCount; + switch (processCount) { + case 1: + // Switch to listening to referenceProcessor; + referenceProcessor.onaudioprocess = event.target.onaudioprocess; + referenceProcessor = null; + event.target.onaudioprocess = null; + case 2: + // There are no references to testProcessor and so GC can begin. + SpecialPowers.forceGC(); + break; + case 3: + // Another GC should not be required after testProcessor would have + // received another audioprocess event. + SpecialPowers.forceCC(); + // Expect that webaudio-demise has been queued. + // Queue another event to check. + SimpleTest.executeSoon(function() { + SpecialPowers.removeObserver(observer, "webaudio-node-demise"); + observer = null; + event.target.onaudioprocess = null; + ok(ids.length == 0, "All expected nodes should be collected"); + SimpleTest.finish(); + }); + break; + } +}; + +function id(node) { + return SpecialPowers.getPrivilegedProps(node, "id"); +} + +// Nodes with these ids should be collected. +var ids = [ id(testProcessor), id(delay), id(gain) ]; +testProcessor = null; +delay = null; +gain = null; + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_WebAudioMemoryReporting.html b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html new file mode 100644 index 000000000..c753756e7 --- /dev/null +++ b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Web Audio memory reporting</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var ac = new AudioContext(); +var sp = ac.createScriptProcessor(4096, 1, 1); +sp.connect(ac.destination); + +// Not started so as to test +// https://bugzilla.mozilla.org/show_bug.cgi?id=1225003#c2 +var oac = new OfflineAudioContext(1, 1, 48000); + +var nodeTypes = ["ScriptProcessorNode", "AudioDestinationNode"]; +var objectTypes = ["dom-nodes", "engine-objects", "stream-objects"]; + +var usages = { "explicit/webaudio/audiocontext": 0 }; + +for (var i = 0; i < nodeTypes.length; ++i) { + for (var j = 0; j < objectTypes.length; ++j) { + usages["explicit/webaudio/audio-node/" + + nodeTypes[i] + "/" + objectTypes[j]] = 0; + } +} + +var handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) { + if (aPath in usages) { + usages[aPath] += aAmount; + } +} + +var finished = function () { + ok(true, "Yay didn't crash!"); + for (var resource in usages) { + ok(usages[resource] > 0, "Non-zero usage for " + resource); + }; + SimpleTest.finish(); +} + +SpecialPowers.Cc["@mozilla.org/memory-reporter-manager;1"]. + getService(SpecialPowers.Ci.nsIMemoryReporterManager). + getReports(handleReport, null, finished, null, /* anonymized = */ false); + +// To test bug 1225003, run a failing decodeAudioData() job over a time when +// the tasks from getReports() are expected to run. +ac.decodeAudioData(new ArrayBuffer(4), function(){}, function(){}); +</script> +</html> diff --git a/dom/media/webaudio/test/test_analyserNode.html b/dom/media/webaudio/test/test_analyserNode.html new file mode 100644 index 000000000..7af67a5a5 --- /dev/null +++ b/dom/media/webaudio/test/test_analyserNode.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AnalyserNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + + var context = new AudioContext(); + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var destination = context.destination; + + var source = context.createBufferSource(); + + var analyser = context.createAnalyser(); + + source.buffer = buffer; + + source.connect(analyser); + analyser.connect(destination); + + is(analyser.channelCount, 1, "analyser node has 1 input channels by default"); + is(analyser.channelCountMode, "max", "Correct channelCountMode for the analyser node"); + is(analyser.channelInterpretation, "speakers", "Correct channelCountInterpretation for the analyser node"); + + is(analyser.fftSize, 2048, "Correct default value for fftSize"); + is(analyser.frequencyBinCount, 1024, "Correct default value for frequencyBinCount"); + expectException(function() { + analyser.fftSize = 0; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 1; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 8; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 100; // non-power of two + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 2049; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 4097; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 8193; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 16385; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 32769; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.fftSize = 65536; + }, DOMException.INDEX_SIZE_ERR); + analyser.fftSize = 1024; + is(analyser.frequencyBinCount, 512, "Correct new value for frequencyBinCount"); + + is(analyser.minDecibels, -100, "Correct default value for minDecibels"); + is(analyser.maxDecibels, -30, "Correct default value for maxDecibels"); + expectException(function() { + analyser.minDecibels = -30; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.minDecibels = -29; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.maxDecibels = -100; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.maxDecibels = -101; + }, DOMException.INDEX_SIZE_ERR); + + is(analyser.smoothingTimeConstant, 0.8, "Correct default value for smoothingTimeConstant"); + expectException(function() { + analyser.smoothingTimeConstant = -0.1; + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + analyser.smoothingTimeConstant = 1.1; + }, DOMException.INDEX_SIZE_ERR); + analyser.smoothingTimeConstant = 0; + analyser.smoothingTimeConstant = 1; + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_analyserNodeOutput.html b/dom/media/webaudio/test/test_analyserNodeOutput.html new file mode 100644 index 000000000..e6255fee0 --- /dev/null +++ b/dom/media/webaudio/test/test_analyserNodeOutput.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AnalyserNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var analyser = context.createAnalyser(); + + source.buffer = this.buffer; + + source.connect(analyser); + + source.start(0); + return analyser; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_analyserNodePassThrough.html b/dom/media/webaudio/test/test_analyserNodePassThrough.html new file mode 100644 index 000000000..37d1db510 --- /dev/null +++ b/dom/media/webaudio/test/test_analyserNodePassThrough.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AnalyserNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var analyser = context.createAnalyser(); + + source.buffer = this.buffer; + + source.connect(analyser); + + var analyserWrapped = SpecialPowers.wrap(analyser); + ok("passThrough" in analyserWrapped, "AnalyserNode should support the passThrough API"); + analyserWrapped.passThrough = true; + + source.start(0); + return analyser; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_analyserNodeWithGain.html b/dom/media/webaudio/test/test_analyserNodeWithGain.html new file mode 100644 index 000000000..fa0a2caa7 --- /dev/null +++ b/dom/media/webaudio/test/test_analyserNodeWithGain.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>Test effect of AnalyserNode on GainNode output</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(function() { + // fftSize <= bufferSize so that the time domain data is full of input after + // processing the buffer. + const fftSize = 32; + const bufferSize = 128; + + var context = new OfflineAudioContext(1, bufferSize, 48000); + + var analyser1 = context.createAnalyser(); + analyser1.fftSize = fftSize; + analyser1.connect(context.destination); + var analyser2 = context.createAnalyser(); + analyser2.fftSize = fftSize; + + var gain = context.createGain(); + gain.gain.value = 2.0; + gain.connect(analyser1); + gain.connect(analyser2); + + // Create a DC input to make getFloatTimeDomainData() output consistent at + // any time. + var buffer = context.createBuffer(1, 1, context.sampleRate); + buffer.getChannelData(0)[0] = 1.0 / gain.gain.value; + var source = context.createBufferSource(); + source.buffer = buffer; + source.loop = true; + source.connect(gain); + source.start(); + + return context.startRendering(). + then(function(buffer) { + assert_equals(buffer.getChannelData(0)[0], 1.0, + "analyser1 output"); + + var data = new Float32Array(1); + analyser1.getFloatTimeDomainData(data); + assert_equals(data[0], 1.0, "analyser1 time domain data"); + analyser2.getFloatTimeDomainData(data); + assert_equals(data[0], 1.0, "analyser2 time domain data"); + }); +}); +</script> diff --git a/dom/media/webaudio/test/test_analyserScale.html b/dom/media/webaudio/test/test_analyserScale.html new file mode 100644 index 000000000..3aec8d22b --- /dev/null +++ b/dom/media/webaudio/test/test_analyserScale.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AnalyserNode when the input is scaled </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + + var context = new AudioContext(); + + var gain = context.createGain(); + var analyser = context.createAnalyser(); + var osc = context.createOscillator(); + + + osc.connect(gain); + gain.connect(analyser); + + osc.start(); + + var array = new Uint8Array(analyser.frequencyBinCount); + + function getAnalyserData() { + gain.gain.setValueAtTime(currentGain, context.currentTime); + analyser.getByteTimeDomainData(array); + var inrange = true; + var max = -1; + for (var i = 0; i < array.length; i++) { + if (array[i] > max) { + max = Math.abs(array[i] - 128); + } + } + if (max <= currentGain * 128) { + ok(true, "Analyser got scaled data for " + currentGain); + currentGain = tests.shift(); + if (currentGain == undefined) { + SimpleTest.finish(); + return; + } + } + requestAnimationFrame(getAnalyserData); + } + + var tests = [1.0, 0.5, 0.0]; + var currentGain = tests.shift(); + requestAnimationFrame(getAnalyserData); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNode.html b/dom/media/webaudio/test/test_audioBufferSourceNode.html new file mode 100644 index 000000000..875c96c36 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNode.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.start(0); + source.buffer = buffer; + return source; + }, + createExpectedBuffers: function(context) { + var buffers = []; + var buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + buffer.getChannelData(1)[i] = buffer.getChannelData(0)[i]; + } + buffers.push(buffer); + buffers.push(getEmptyBuffer(context, 2048)); + return buffers; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html b/dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html new file mode 100644 index 000000000..4d06c26ca --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode when an AudioBuffer's getChanneData buffer is detached</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function createGarbage() { + var s = []; + for (var i = 0; i < 10000000; ++i) { + s.push(i); + } + var sum = 0; + for (var i = 0; i < s.length; ++i) { + sum += s[i]; + } + return sum; +} + +var worker = new Worker("audioBufferSourceNodeDetached_worker.js"); + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 10000000, context.sampleRate); + var data = buffer.getChannelData(0); + for (var i = 0; i < data.length; ++i) { + data[i] = (i%100)/100 - 0.5; + } + + // Detach the buffer now + var data = buffer.getChannelData(0).buffer; + worker.postMessage(data, [data]); + // Create garbage and GC to replace the buffer data with garbage + SpecialPowers.gc(); + createGarbage(); + SpecialPowers.gc(); + + var source = context.createBufferSource(); + source.buffer = buffer; + source.start(); + // This should play silence + return source; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html b/dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html new file mode 100644 index 000000000..08616bea6 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ended event on AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + + source.onended = function(e) { + is(e.target, source, "Correct target for the ended event"); + SimpleTest.finish(); + }; + + source.start(0); + source.buffer = buffer; + source.connect(context.destination); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html new file mode 100644 index 000000000..0893cf10b --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + // silence for half of the buffer, ones after that. + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 1024; i < 2048; i++) { + buffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + + // we start at the 1024 frames, we should only have ones. + source.loop = true; + source.loopStart = 1024 / context.sampleRate; + source.loopEnd = 2048 / context.sampleRate; + source.buffer = buffer; + source.start(0, 1024 / context.sampleRate, 1024 / context.sampleRate); + return source; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate); + for (var i = 0; i < 4096; i++) { + expectedBuffer.getChannelData(0)[i] = 1; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html new file mode 100644 index 000000000..79c78dfe0 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode looping</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048 * 4, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + + source.start(0); + source.loop = true; + return source; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048 * 4, context.sampleRate); + for (var i = 0; i < 4; ++i) { + for (var j = 0; j < 2048; ++j) { + expectedBuffer.getChannelData(0)[i * 2048 + j] = Math.sin(440 * 2 * Math.PI * j / context.sampleRate); + } + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html new file mode 100644 index 000000000..6f60762eb --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode looping</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048 * 4, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + source.start(0); + source.loop = true; + source.loopStart = buffer.duration * 0.25; + source.loopEnd = buffer.duration * 0.75; + return source; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048 * 4, context.sampleRate); + for (var i = 0; i < 1536; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + for (var i = 0; i < 6; ++i) { + for (var j = 512; j < 1536; ++j) { + expectedBuffer.getChannelData(0)[1536 + i * 1024 + j - 512] = Math.sin(440 * 2 * Math.PI * j / context.sampleRate); + } + } + for (var j = 7680; j < 2048 * 4; ++j) { + expectedBuffer.getChannelData(0)[j] = Math.sin(440 * 2 * Math.PI * (j - 7168) / context.sampleRate); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html new file mode 100644 index 000000000..eca4bf636 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode looping</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + + source.loop = true; + source.loopStart = source.loopEnd = 1 / context.sampleRate; + source.start(0); + return source; + }, + createExpectedBuffers: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + return buffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html b/dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html new file mode 100644 index 000000000..89340ade8 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode when start() is not called</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + var data = buffer.getChannelData(0); + for (var i = 0; i < data.length; ++i) { + data[i] = (i%100)/100 - 0.5; + } + var source = context.createBufferSource(); + source.buffer = buffer; + return source; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html b/dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html new file mode 100644 index 000000000..6ca771af1 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + source.start(0); + source.buffer = null; + is(source.buffer, null, "Try playing back a null buffer"); + return source; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html b/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html new file mode 100644 index 000000000..b7a16634e --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the offset property on AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var fuzz = 0.3; + +if (navigator.platform.startsWith("Mac")) { + // bug 895720 + fuzz = 0.6; +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var samplesFromSource = 0; + var context = new AudioContext(); + var sp = context.createScriptProcessor(256); + + sp.onaudioprocess = function(e) { + samplesFromSource += e.inputBuffer.length; + } + + var buffer = context.createBuffer(1, context.sampleRate, context.sampleRate); + for (var i = 0; i < context.sampleRate; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + + source.onended = function(e) { + // The timing at which the audioprocess and ended listeners are called can + // change, hence the fuzzy equal here. + var errorRatio = samplesFromSource / (0.5 * context.sampleRate); + ok(errorRatio > (1.0 - fuzz) && errorRatio < (1.0 + fuzz), + "Correct number of samples received (expected: " + + (0.5 * context.sampleRate) + ", actual: " + samplesFromSource + ")."); + SimpleTest.finish(); + }; + + source.buffer = buffer; + source.connect(sp); + source.start(0, 0.5); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html b/dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html new file mode 100644 index 000000000..5088f1637 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + + source.buffer = buffer; + + var srcWrapped = SpecialPowers.wrap(source); + ok("passThrough" in srcWrapped, "AudioBufferSourceNode should support the passThrough API"); + srcWrapped.passThrough = true; + + source.start(0); + return source; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + + return [expectedBuffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html b/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html new file mode 100644 index 000000000..2cdcd7270 --- /dev/null +++ b/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var rate = 44100; +var off = new OfflineAudioContext(1, rate, rate); +var off2 = new OfflineAudioContext(1, rate, rate); + +var source = off.createBufferSource(); +var source2 = off2.createBufferSource(); + +// a buffer of a 440Hz at half the length. If we detune by -1200 or set the +// playbackRate to 0.5, we should get 44100 samples back with a sine at 220Hz. +var buf = off.createBuffer(1, rate / 2, rate); +var bufarray = buf.getChannelData(0); +for (var i = 0; i < bufarray.length; i++) { + bufarray[i] = Math.sin(i * 440 * 2 * Math.PI / rate); +} + +source.buffer = buf; +source.playbackRate.value = 0.5; // 50% slowdown +source.connect(off.destination); +source.start(0); + +source2.buffer = buf; +source2.detune.value = -1200; // one octave -> 50% slowdown +source2.connect(off2.destination); +source2.start(0); + +var finished = 0; +function finish() { + finished++; + if (finished == 2) { + SimpleTest.finish(); + } +} + +off.startRendering().then((renderedPlaybackRate) => { + // we don't care about comparing the value here, we just want to know whether + // the second part is noisy. + var rmsValue = rms(renderedPlaybackRate, 0, 22050); + ok(rmsValue != 0, "Resampling happened (rms of the second part " + rmsValue + ")"); + + off2.startRendering().then((renderedDetune) => { + var rmsValue = rms(renderedDetune, 0, 22050); + ok(rmsValue != 0, "Resampling happened (rms of the second part " + rmsValue + ")"); + // The two buffers should be the same: detune of -1200 is a 50% slowdown + compareBuffers(renderedPlaybackRate, renderedDetune); + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html new file mode 100644 index 000000000..269d5380e --- /dev/null +++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html @@ -0,0 +1,410 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test suspend, resume and close method of the AudioContext</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function tryToCreateNodeOnClosedContext(ctx) { + ok(ctx.state, "closed", "The context is in closed state"); + + [ { name: "createBufferSource" }, + { name: "createMediaStreamDestination", + onOfflineAudioContext: false}, + { name: "createScriptProcessor" }, + { name: "createStereoPanner" }, + { name: "createAnalyser" }, + { name: "createGain" }, + { name: "createDelay" }, + { name: "createBiquadFilter" }, + { name: "createWaveShaper" }, + { name: "createPanner" }, + { name: "createConvolver" }, + { name: "createChannelSplitter" }, + { name: "createChannelMerger" }, + { name: "createDynamicsCompressor" }, + { name: "createOscillator" }, + { name: "createMediaElementSource", + args: [new Audio()], + onOfflineAudioContext: false }, + { name: "createMediaStreamSource", + args: [new Audio().mozCaptureStream()], + onOfflineAudioContext: false } ].forEach(function(e) { + + if (e.onOfflineAudioContext == false && + ctx instanceof OfflineAudioContext) { + return; + } + + expectException(function() { + ctx[e.name].apply(ctx, e.args); + }, DOMException.INVALID_STATE_ERR); + }); +} + +function loadFile(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + callback(xhr.response); + }; + xhr.send(); +} + +// createBuffer, createPeriodicWave and decodeAudioData should work on a context +// that has `state` == "closed" +function tryLegalOpeerationsOnClosedContext(ctx) { + ok(ctx.state, "closed", "The context is in closed state"); + + [ { name: "createBuffer", + args: [1, 44100, 44100] }, + { name: "createPeriodicWave", + args: [new Float32Array(10), new Float32Array(10)] } + ].forEach(function(e) { + expectNoException(function() { + ctx[e.name].apply(ctx, e.args); + }); + }); + loadFile("ting-44.1k-1ch.ogg", function(buf) { + ctx.decodeAudioData(buf).then(function(decodedBuf) { + ok(true, "decodeAudioData on a closed context should work, it did.") + todo(false, "0 " + (ctx instanceof OfflineAudioContext ? "Offline" : "Realtime")); + finish(); + }).catch(function(e){ + ok(false, "decodeAudioData on a closed context should work, it did not"); + finish(); + }); + }); +} + +// Test that MediaStreams that are the output of a suspended AudioContext are +// producing silence +// ac1 produce a sine fed to a MediaStreamAudioDestinationNode +// ac2 is connected to ac1 with a MediaStreamAudioSourceNode, and check that +// there is silence when ac1 is suspended +function testMultiContextOutput() { + var ac1 = new AudioContext(), + ac2 = new AudioContext(); + + ac1.onstatechange = function() { + ac1.onstatechange = null; + + var osc1 = ac1.createOscillator(), + mediaStreamDestination1 = ac1.createMediaStreamDestination(); + + var mediaStreamAudioSourceNode2 = + ac2.createMediaStreamSource(mediaStreamDestination1.stream), + sp2 = ac2.createScriptProcessor(), + silentBuffersInARow = 0; + + + sp2.onaudioprocess = function(e) { + ac1.suspend().then(function() { + is(ac1.state, "suspended", "ac1 is suspended"); + sp2.onaudioprocess = checkSilence; + }); + sp2.onaudioprocess = null; + } + + function checkSilence(e) { + var input = e.inputBuffer.getChannelData(0); + var silent = true; + for (var i = 0; i < input.length; i++) { + if (input[i] != 0.0) { + silent = false; + } + } + + todo(false, "input buffer is " + (silent ? "silent" : "noisy")); + + if (silent) { + silentBuffersInARow++; + if (silentBuffersInARow == 10) { + ok(true, + "MediaStreams produce silence when their input is blocked."); + sp2.onaudioprocess = null; + ac1.close(); + ac2.close(); + todo(false,"1"); + finish(); + } + } else { + is(silentBuffersInARow, 0, + "No non silent buffer inbetween silent buffers."); + } + } + + osc1.connect(mediaStreamDestination1); + + mediaStreamAudioSourceNode2.connect(sp2); + osc1.start(); + } +} + + +// Test that there is no buffering between contexts when connecting a running +// AudioContext to a suspended AudioContext. Our ScriptProcessorNode does some +// buffering internally, so we ensure this by using a very very low frequency +// on a sine, and oberve that the phase has changed by a big enough margin. +function testMultiContextInput() { + var ac1 = new AudioContext(), + ac2 = new AudioContext(); + + ac1.onstatechange = function() { + ac1.onstatechange = null; + + var osc1 = ac1.createOscillator(), + mediaStreamDestination1 = ac1.createMediaStreamDestination(), + sp1 = ac1.createScriptProcessor(); + + var mediaStreamAudioSourceNode2 = + ac2.createMediaStreamSource(mediaStreamDestination1.stream), + sp2 = ac2.createScriptProcessor(), + eventReceived = 0; + + + osc1.frequency.value = 0.0001; + + function checkDiscontinuity(e) { + var inputBuffer = e.inputBuffer.getChannelData(0); + if (eventReceived++ == 3) { + var delta = Math.abs(inputBuffer[1] - sp2.value), + theoreticalIncrement = 2048 * 3 * Math.PI * 2 * osc1.frequency.value / ac1.sampleRate; + ok(delta >= theoreticalIncrement, + "Buffering did not occur when the context was suspended (delta:" + delta + " increment: " + theoreticalIncrement+")"); + ac1.close(); + ac2.close(); + sp1.onaudioprocess = null; + sp2.onaudioprocess = null; + todo(false, "2"); + finish(); + } + } + + sp2.onaudioprocess = function(e) { + var inputBuffer = e.inputBuffer.getChannelData(0); + sp2.value = inputBuffer[inputBuffer.length - 1]; + ac2.suspend().then(function() { + ac2.resume().then(function() { + sp2.onaudioprocess = checkDiscontinuity; + }); + }); + } + + osc1.connect(mediaStreamDestination1); + osc1.connect(sp1); + + mediaStreamAudioSourceNode2.connect(sp2); + osc1.start(); + } +} + +// Test that ScriptProcessorNode's onaudioprocess don't get called while the +// context is suspended/closed. It is possible that we get the handler called +// exactly once after suspend, because the event has already been sent to the +// event loop. +function testScriptProcessNodeSuspended() { + var ac = new AudioContext(); + var sp = ac.createScriptProcessor(); + var remainingIterations = 30; + var afterResume = false; + ac.onstatechange = function() { + ac.onstatechange = null; + sp.onaudioprocess = function() { + ok(ac.state == "running", "If onaudioprocess is called, the context" + + " must be running (was " + ac.state + ", remainingIterations:" + remainingIterations +")"); + remainingIterations--; + if (!afterResume) { + if (remainingIterations == 0) { + ac.suspend().then(function() { + ac.resume().then(function() { + remainingIterations = 30; + afterResume = true; + }); + }); + } + } else { + sp.onaudioprocess = null; + todo(false,"3"); + finish(); + } + } + } + sp.connect(ac.destination); +} + +// Take an AudioContext, make sure it switches to running when the audio starts +// flowing, and then, call suspend, resume and close on it, tracking its state. +function testAudioContext() { + var ac = new AudioContext(); + is(ac.state, "suspended", "AudioContext should start in suspended state."); + var stateTracker = { + previous: ac.state, + // no promise for the initial suspended -> running + initial: { handler: false }, + suspend: { promise: false, handler: false }, + resume: { promise: false, handler: false }, + close: { promise: false, handler: false } + }; + + function initialSuspendToRunning() { + ok(stateTracker.previous == "suspended" && + ac.state == "running", + "AudioContext should switch to \"running\" when the audio hardware is" + + " ready."); + + stateTracker.previous = ac.state; + ac.onstatechange = afterSuspend; + stateTracker.initial.handler = true; + + ac.suspend().then(function() { + ok(!stateTracker.suspend.promise && !stateTracker.suspend.handler, + "Promise should be resolved before the callback, and only once.") + stateTracker.suspend.promise = true; + }); + } + + function afterSuspend() { + ok(stateTracker.previous == "running" && + ac.state == "suspended", + "AudioContext should switch to \"suspend\" when the audio stream is" + + "suspended."); + ok(stateTracker.suspend.promise && !stateTracker.suspend.handler, + "Handler should be called after the callback, and only once"); + + stateTracker.suspend.handler = true; + stateTracker.previous = ac.state; + ac.onstatechange = afterResume; + + ac.resume().then(function() { + ok(!stateTracker.resume.promise && !stateTracker.resume.handler, + "Promise should be called before the callback, and only once"); + stateTracker.resume.promise = true; + }); + } + + function afterResume() { + ok(stateTracker.previous == "suspended" && + ac.state == "running", + "AudioContext should switch to \"running\" when the audio stream resumes."); + + ok(stateTracker.resume.promise && !stateTracker.resume.handler, + "Handler should be called after the callback, and only once"); + + stateTracker.resume.handler = true; + stateTracker.previous = ac.state; + ac.onstatechange = afterClose; + + ac.close().then(function() { + ok(!stateTracker.close.promise && !stateTracker.close.handler, + "Promise should be called before the callback, and only once"); + stateTracker.close.promise = true; + tryToCreateNodeOnClosedContext(ac); + tryLegalOpeerationsOnClosedContext(ac); + }); + } + + function afterClose() { + ok(stateTracker.previous == "running" && + ac.state == "closed", + "AudioContext should switch to \"closed\" when the audio stream is" + + " closed."); + ok(stateTracker.close.promise && !stateTracker.close.handler, + "Handler should be called after the callback, and only once"); + } + + ac.onstatechange = initialSuspendToRunning; +} + +function testOfflineAudioContext() { + var o = new OfflineAudioContext(1, 44100, 44100); + is(o.state, "suspended", "OfflineAudioContext should start in suspended state."); + + expectRejectedPromise(o, "suspend", "NotSupportedError"); + expectRejectedPromise(o, "resume", "NotSupportedError"); + expectRejectedPromise(o, "close", "NotSupportedError"); + + var previousState = o.state, + finishedRendering = false; + function beforeStartRendering() { + ok(previousState == "suspended" && o.state == "running", "onstatechanged" + + "handler is called on state changed, and the new state is running"); + previousState = o.state; + o.onstatechange = onRenderingFinished; + } + + function onRenderingFinished() { + ok(previousState == "running" && o.state == "closed", + "onstatechanged handler is called when rendering finishes, " + + "and the new state is closed"); + ok(finishedRendering, "The Promise that is resolved when the rendering is" + + "done should be resolved earlier than the state change."); + previousState = o.state; + o.onstatechange = afterRenderingFinished; + + tryToCreateNodeOnClosedContext(o); + tryLegalOpeerationsOnClosedContext(o); + } + + function afterRenderingFinished() { + ok(false, "There should be no transition out of the closed state."); + } + + o.onstatechange = beforeStartRendering; + + o.startRendering().then(function(buffer) { + finishedRendering = true; + }); +} + +function testSuspendResumeEventLoop() { + var ac = new AudioContext(); + var source = ac.createBufferSource(); + source.buffer = ac.createBuffer(1, 44100, 44100); + source.onended = function() { + ok(true, "The AudioContext did resume."); + finish(); + } + ac.onstatechange = function() { + ac.onstatechange = null; + + ok(ac.state == "running", "initial state is running"); + ac.suspend(); + source.start(); + ac.resume(); + } +} + +var remaining = 0; +function finish() { + remaining--; + if (remaining == 0) { + SimpleTest.finish(); + } +} + + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var tests = [ + testAudioContext, + testOfflineAudioContext, + testScriptProcessNodeSuspended, + testMultiContextOutput, + testMultiContextInput, + testSuspendResumeEventLoop + ]; + remaining = tests.length; + tests.forEach(function(f) { f() }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioDestinationNode.html b/dom/media/webaudio/test/test_audioDestinationNode.html new file mode 100644 index 000000000..b86c7169d --- /dev/null +++ b/dom/media/webaudio/test/test_audioDestinationNode.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioDestinationNode as EventTarget</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var ac = new AudioContext() +ac.destination.addEventListener("foo", function() { + ok(true, "Event received!"); + SimpleTest.finish(); +}, false); +ac.destination.dispatchEvent(new CustomEvent("foo")); + +</script> +</pre> +</body> +</html> + diff --git a/dom/media/webaudio/test/test_audioParamChaining.html b/dom/media/webaudio/test/test_audioParamChaining.html new file mode 100644 index 000000000..6093e4425 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamChaining.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish() + +function frameToTime(frame, rate) +{ + return frame / rate; +} + +const RATE = 44100; + +var oc = new OfflineAudioContext(1, 44100, RATE); +// This allows us to have a source that is simply a DC offset. +var source = oc.createBufferSource(); +var buf = oc.createBuffer(1, 1, RATE); +buf.getChannelData(0)[0] = 1; +source.loop = true; +source.buffer = buf; + +source.start(0); + +var gain = oc.createGain(); + +source.connect(gain).connect(oc.destination); + +var gain2 = oc.createGain(); +var rv2 = gain2.gain.linearRampToValueAtTime(0.1, 0.5); +ok(rv2 instanceof AudioParam, "linearRampToValueAtTime returns an AudioParam."); +ok(rv2 == gain2.gain, "linearRampToValueAtTime returns the right AudioParam."); + +rv2 = gain2.gain.exponentialRampToValueAtTime(0.01, 1.0); +ok(rv2 instanceof AudioParam, + "exponentialRampToValueAtTime returns an AudioParam."); +ok(rv2 == gain2.gain, + "exponentialRampToValueAtTime returns the right AudioParam."); + +rv2 = gain2.gain.setTargetAtTime(1.0, 2.0, 0.1); +ok(rv2 instanceof AudioParam, "setTargetAtTime returns an AudioParam."); +ok(rv2 == gain2.gain, "setTargetAtTime returns the right AudioParam."); + +var array = new Float32Array(10); +rv2 = gain2.gain.setValueCurveAtTime(array, 10, 11); +ok(rv2 instanceof AudioParam, "setValueCurveAtTime returns an AudioParam."); +ok(rv2 == gain2.gain, "setValueCurveAtTime returns the right AudioParam."); + +// We chain three automation methods, making a gain step. +var rv = gain.gain.setValueAtTime(0, frameToTime(0, RATE)) + .setValueAtTime(0.5, frameToTime(22000, RATE)) + .setValueAtTime(1, frameToTime(44000, RATE)); + +ok(rv instanceof AudioParam, "setValueAtTime returns an AudioParam."); +ok(rv == gain.gain, "setValueAtTime returns the right AudioParam."); + +oc.startRendering().then(function(rendered) { + console.log(rendered.getChannelData(0)); + is(rendered.getChannelData(0)[0], 0, + "The value of the first step is correct."); + is(rendered.getChannelData(0)[22050], 0.5, + "The value of the second step is correct"); + is(rendered.getChannelData(0)[44099], 1, + "The value of the third step is correct."); + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamExponentialRamp.html b/dom/media/webaudio/test/test_audioParamExponentialRamp.html new file mode 100644 index 000000000..e1b1c5142 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamExponentialRamp.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.exponentialRampToValue</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var V0 = 0.1; +var V1 = 0.9; +var T0 = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.setValueAtTime(V0, 0); + gain.gain.exponentialRampToValueAtTime(V1, 2048/context.sampleRate); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var T1 = 2048 / context.sampleRate; + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + var t = i / context.sampleRate; + expectedBuffer.getChannelData(0)[i] = V0 * Math.pow(V1 / V0, (t - T0) / (T1 - T0)); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamGain.html b/dom/media/webaudio/test/test_audioParamGain.html new file mode 100644 index 000000000..b971becce --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamGain.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam with pre-gain </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var ctx = new AudioContext(); +var source = ctx.createOscillator(); +var lfo = ctx.createOscillator(); +var lfoIntensity = ctx.createGain(); +var effect = ctx.createGain(); +var sp = ctx.createScriptProcessor(2048, 1); + +source.frequency.value = 440; +lfo.frequency.value = 2; +// Very low gain, so the LFO should have very little influence +// on the source, its RMS value should be close to the nominal value +// for a sine wave. +lfoIntensity.gain.value = 0.0001; + +lfo.connect(lfoIntensity); +lfoIntensity.connect(effect.gain); +source.connect(effect); +effect.connect(sp); + +sp.onaudioprocess = function(e) { + var buffer = e.inputBuffer.getChannelData(0); + var rms = 0; + for (var i = 0; i < buffer.length; i++) { + rms += buffer[i] * buffer[i]; + } + + rms /= buffer.length; + rms = Math.sqrt(rms); + + // 1 / Math.sqrt(2) is the theoretical RMS value for a sine wave. + ok(fuzzyCompare(rms, 1 / Math.sqrt(2)), + "Gain correctly applied to the AudioParam."); + + ctx = null; + sp.onaudioprocess = null; + lfo.stop(0); + source.stop(0); + + SimpleTest.finish(); +} + +lfo.start(0); +source.start(0); + +</script> +</pre> +</body> diff --git a/dom/media/webaudio/test/test_audioParamLinearRamp.html b/dom/media/webaudio/test/test_audioParamLinearRamp.html new file mode 100644 index 000000000..31f1d80d6 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamLinearRamp.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.linearRampToValue</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var V0 = 0.1; +var V1 = 0.9; +var T0 = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.setValueAtTime(V0, 0); + gain.gain.linearRampToValueAtTime(V1, 2048/context.sampleRate); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var T1 = 2048 / context.sampleRate; + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + var t = i / context.sampleRate; + expectedBuffer.getChannelData(0)[i] = V0 + (V1 - V0) * ((t - T0) / (T1 - T0)); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html b/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html new file mode 100644 index 000000000..bcb655b52 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.linearRampToValue</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var T0 = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createConstantSource(); + + var gain = context.createGain(); + gain.gain.setValueCurveAtTime(this.curve, T0, this.duration); + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + this.duration = 1024 / context.sampleRate; + this.curve = new Float32Array([1.0, 0.5, 0.75, 0.25]); + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + var data = expectedBuffer.getChannelData(0); + var step = 1024 / 3; + for (var i = 0; i < 2048; ++i) { + if (i < step) { + data[i] = 1.0 - 0.5*i/step; + } else if (i < 2*step) { + data[i] = 0.5 + 0.25*(i - step)/step; + } else if (i < 3*step) { + data[i] = 0.75 - 0.5*(i - 2*step)/step; + } else { + data[i] = 0.25; + } + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamSetCurveAtTimeTwice.html b/dom/media/webaudio/test/test_audioParamSetCurveAtTimeTwice.html new file mode 100644 index 000000000..0f976380e --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamSetCurveAtTimeTwice.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.setValueCurveAtTime twice</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + +function linearInterpolate(t0, v0, t1, v1, t) +{ + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); +} + +var T0 = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var curve2 = new Float32Array(100); + for (var i = 0; i < 100; ++i) { + curve2[i] = Math.sin(220 * 6 * Math.PI * i / context.sampleRate); + } + + var source = context.createConstantSource(); + + var gain = context.createGain(); + gain.gain.setValueCurveAtTime(curve2, T0, this.duration/2); + //Set a different curve from the first one + gain.gain.setValueCurveAtTime(this.curve, T0, this.duration); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + this.duration = 1024 / context.sampleRate; + this.curve = new Float32Array(100); + for (var i = 0; i < 100; ++i) { + this.curve[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + step = 1024.0/99.0; + var current = Math.floor(i / step); + var next = current + 1; + if (next < this.curve.length) { + expectedBuffer.getChannelData(0)[i] = linearInterpolate(current*step, this.curve[current], next*step, this.curve[next], i); + } else { + expectedBuffer.getChannelData(0)[i] = this.curve[99]; + } + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamSetCurveAtTimeZeroDuration.html b/dom/media/webaudio/test/test_audioParamSetCurveAtTimeZeroDuration.html new file mode 100644 index 000000000..174c15c6f --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamSetCurveAtTimeZeroDuration.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.linearRampToValue</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var T0 = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.setValueCurveAtTime(this.curve, this.T0, 0); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + this.T0 = 1024 / context.sampleRate; + this.curve = new Float32Array(100); + for (var i = 0; i < 100; ++i) { + this.curve[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 1024; ++i) { + expectedBuffer.getChannelData(0)[i] = 1; + } + for (var i = 1024; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = this.curve[99]; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamSetTargetAtTime.html b/dom/media/webaudio/test/test_audioParamSetTargetAtTime.html new file mode 100644 index 000000000..ccb35ca7b --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamSetTargetAtTime.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.setTargetAtTime</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var V0 = 0.9; +var V1 = 0.1; +var T0 = 0; +var TimeConstant = 10; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.value = V0; + gain.gain.setTargetAtTime(V1, T0, TimeConstant); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var T1 = 2048 / context.sampleRate; + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + var t = i / context.sampleRate; + expectedBuffer.getChannelData(0)[i] = V1 + (V0 - V1) * Math.exp(-(t - T0) / TimeConstant); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html b/dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html new file mode 100644 index 000000000..bad12ca31 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.setTargetAtTime with zero time constant</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var V0 = 0.9; +var V1 = 0.1; +var T0 = 0; +var TimeConstant = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.value = V0; + gain.gain.setTargetAtTime(V1, T0, TimeConstant); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var T1 = 2048 / context.sampleRate; + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + var t = i / context.sampleRate; + expectedBuffer.getChannelData(0)[i] = V1; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamSetValueAtTime.html b/dom/media/webaudio/test/test_audioParamSetValueAtTime.html new file mode 100644 index 000000000..1ab515935 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamSetValueAtTime.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.linearRampToValue</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var V0 = 0.1; +var V1 = 0.9; +var T0 = 0; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.value = 0; + gain.gain.setValueAtTime(V0, 1024/context.sampleRate); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 1024; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = 0.1; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html b/dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html new file mode 100644 index 000000000..510beb3c7 --- /dev/null +++ b/dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam timeline events scheduled after the destination stream has started playback</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.requestFlakyTimeout("This test needs to wait until the AudioDestinationNode's stream's timer starts."); + +var gTest = { + length: 16384, + numberOfChannels: 1, + createGraphAsync: function(context, callback) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + setTimeout(function() { + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + source.start(context.currentTime); + source.stop(context.currentTime + sourceBuffer.duration); + + var gain = context.createGain(); + gain.gain.setValueAtTime(0, context.currentTime); + gain.gain.setTargetAtTime(0, context.currentTime + sourceBuffer.duration, 1); + source.connect(gain); + + callback(gain); + }, 100); + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_badConnect.html b/dom/media/webaudio/test/test_badConnect.html new file mode 100644 index 000000000..b0d7c8f0c --- /dev/null +++ b/dom/media/webaudio/test/test_badConnect.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context1 = new AudioContext(); + var context2 = new AudioContext(); + + var destination1 = context1.destination; + var destination2 = context2.destination; + + isnot(destination1, destination2, "Destination nodes should not be the same"); + isnot(destination1.context, destination2.context, "Destination nodes should not have the same context"); + + var source1 = context1.createBufferSource(); + + expectException(function() { + source1.connect(destination1, 1); + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + source1.connect(destination1, 0, 1); + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + source1.connect(destination2); + }, DOMException.SYNTAX_ERR); + + source1.connect(destination1); + + expectException(function() { + source1.disconnect(1); + }, DOMException.INDEX_SIZE_ERR); + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_biquadFilterNode.html b/dom/media/webaudio/test/test_biquadFilterNode.html new file mode 100644 index 000000000..078f89179 --- /dev/null +++ b/dom/media/webaudio/test/test_biquadFilterNode.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +function near(a, b, msg) { + ok(Math.abs(a - b) < 1e-3, msg); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var destination = context.destination; + + var source = context.createBufferSource(); + + var filter = context.createBiquadFilter(); + + source.buffer = buffer; + + source.connect(filter); + filter.connect(destination); + + // Verify default values + is(filter.type, "lowpass", "Correct default value for type"); + near(filter.frequency.defaultValue, 350, "Correct default value for filter frequency"); + near(filter.detune.defaultValue, 0, "Correct default value for filter detune"); + near(filter.Q.defaultValue, 1, "Correct default value for filter Q"); + near(filter.gain.defaultValue, 0, "Correct default value for filter gain"); + is(filter.channelCount, 2, "Biquad filter node has 2 input channels by default"); + is(filter.channelCountMode, "max", "Correct channelCountMode for the biquad filter node"); + is(filter.channelInterpretation, "speakers", "Correct channelCountInterpretation for the biquad filter node"); + + // Make sure that we can set all of the valid type values + var types = [ + "lowpass", + "highpass", + "bandpass", + "lowshelf", + "highshelf", + "peaking", + "notch", + "allpass", + ]; + for (var i = 0; i < types.length; ++i) { + filter.type = types[i]; + } + + // Make sure getFrequencyResponse handles invalid frequencies properly + var frequencies = new Float32Array([-1.0, context.sampleRate*0.5 - 1.0, context.sampleRate]); + var magResults = new Float32Array(3); + var phaseResults = new Float32Array(3); + filter.getFrequencyResponse(frequencies, magResults, phaseResults); + ok(isNaN(magResults[0]), "Invalid input frequency should give NaN magnitude response"); + ok(!isNaN(magResults[1]), "Valid input frequency should not give NaN magnitude response"); + ok(isNaN(magResults[2]), "Invalid input frequency should give NaN magnitude response"); + ok(isNaN(phaseResults[0]), "Invalid input frquency should give NaN phase response"); + ok(!isNaN(phaseResults[1]), "Valid input frquency should not give NaN phase response"); + ok(isNaN(phaseResults[2]), "Invalid input frquency should give NaN phase response"); + + source.start(0); + SimpleTest.executeSoon(function() { + source.stop(0); + source.disconnect(); + filter.disconnect(); + + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_biquadFilterNodePassThrough.html b/dom/media/webaudio/test/test_biquadFilterNodePassThrough.html new file mode 100644 index 000000000..59fc8ab4f --- /dev/null +++ b/dom/media/webaudio/test/test_biquadFilterNodePassThrough.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var filter = context.createBiquadFilter(); + + source.buffer = this.buffer; + + source.connect(filter); + + var filterWrapped = SpecialPowers.wrap(filter); + ok("passThrough" in filterWrapped, "BiquadFilterNode should support the passThrough API"); + filterWrapped.passThrough = true; + + source.start(0); + return filter; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_biquadFilterNodeWithGain.html b/dom/media/webaudio/test/test_biquadFilterNodeWithGain.html new file mode 100644 index 000000000..390f2cdb0 --- /dev/null +++ b/dom/media/webaudio/test/test_biquadFilterNodeWithGain.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test BiquadFilterNode after a GainNode and tail - Bugs 924286 and 924288</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +const signalLength = 2048; + +var gTest = { + length: signalLength, + numberOfChannels: 1, + createGraph: function(context) { + // Two oscillators scheduled sequentially + var signalDuration = signalLength / context.sampleRate; + var osc1 = context.createOscillator(); + osc1.type = "square"; + osc1.start(0); + osc1.stop(signalDuration / 2); + var osc2 = context.createOscillator(); + osc2.start(signalDuration / 2); + osc2.stop(signalDuration); + + // Comparing a biquad on each source with one on both sources checks that + // the biquad on the first source doesn't shut down early. + var biquad1 = context.createBiquadFilter(); + osc1.connect(biquad1); + var biquad2 = context.createBiquadFilter(); + osc2.connect(biquad2); + + var gain = context.createGain(); + gain.gain.value = -1; + osc1.connect(gain); + osc2.connect(gain); + + var biquadWithGain = context.createBiquadFilter(); + gain.connect(biquadWithGain); + + // The output of biquadWithGain should be the inverse of the sum of the + // outputs of biquad1 and biquad2, so blend them together and expect + // silence. + var blend = context.createGain(); + biquad1.connect(blend); + biquad2.connect(blend); + biquadWithGain.connect(blend); + + return blend; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug1027864.html b/dom/media/webaudio/test/test_bug1027864.html new file mode 100644 index 000000000..0c115d1a0 --- /dev/null +++ b/dom/media/webaudio/test/test_bug1027864.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test bug 1027864</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function observer(subject, topic, data) { + var id = parseInt(data); + var index = ids.indexOf(id); + if (index != -1) { + ok(true, "Dropping id " + id + " at index " + index); + ids.splice(index, 1); + if (ids.length == 0) { + SimpleTest.executeSoon(function() { + SimpleTest.finish(); + }); + } + } +} + +function id(node) { + return SpecialPowers.getPrivilegedProps(node, "id"); +} + +SpecialPowers.addObserver(observer, "webaudio-node-demise", false); + +SimpleTest.registerCleanupFunction(function() { + SpecialPowers.removeObserver(observer, "webaudio-node-demise"); +}); + +var ac = new AudioContext(); +var ids; + +(function() { + var delay = ac.createDelay(); + delay.delayTime.value = 0.03; + + var gain = ac.createGain(); + gain.gain.value = 0.6; + + delay.connect(gain); + gain.connect(delay); + + gain.connect(ac.destination); + + var source = ac.createOscillator(); + + source.connect(gain); + source.start(ac.currentTime); + source.stop(ac.currentTime + 0.1); + + ids = [ id(delay), id(gain), id(source) ]; +})(); + +setInterval(function() { + forceCC(); +}, 200); + +function forceCC() { + SpecialPowers.DOMWindowUtils.cycleCollect(); + SpecialPowers.DOMWindowUtils.garbageCollect(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug1056032.html b/dom/media/webaudio/test/test_bug1056032.html new file mode 100644 index 000000000..98fb159f7 --- /dev/null +++ b/dom/media/webaudio/test/test_bug1056032.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<meta charset=utf-8> +<head> + <title>Test that we can decode an mp3 (bug 1056032)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var filename = "small-shot.mp3"; + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", filename); + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + var context = new AudioContext(); + context.decodeAudioData(xhr.response, function(b) { + ok(true, "We can decode an mp3 using decodeAudioData"); + SimpleTest.finish(); + }, function() { + ok(false, "We should be able to decode an mp3 using decodeAudioData but couldn't"); + SimpleTest.finish(); + }); + }; + xhr.send(null); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug1113634.html b/dom/media/webaudio/test/test_bug1113634.html new file mode 100644 index 000000000..8995589f3 --- /dev/null +++ b/dom/media/webaudio/test/test_bug1113634.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioParam.setTargetAtTime where the target time is the same as the time of a previous event</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var V0 = 0.9; +var V1 = 0.1; +var T0 = 0; +var TimeConstant = 0.1; + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + gain.gain.setValueAtTime(V0, T0); + gain.gain.setTargetAtTime(V1, T0, TimeConstant); + + source.connect(gain); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + var t = i / context.sampleRate; + expectedBuffer.getChannelData(0)[i] = V1 + (V0 - V1) * Math.exp(-(t - T0) / TimeConstant); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug1118372.html b/dom/media/webaudio/test/test_bug1118372.html new file mode 100644 index 000000000..ca3fc6b0d --- /dev/null +++ b/dom/media/webaudio/test/test_bug1118372.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test WaveShaperNode with no curve</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var context = new OfflineAudioContext(1, 2048, 44100); + + var osc=context.createOscillator(); + var gain=context.createGain(); + var shaper=context.createWaveShaper(); + gain.gain.value=0.1; + shaper.curve=new Float32Array([-0.5,-0.5,1,1]); + + osc.connect(gain); + gain.connect(shaper); + shaper.connect(context.destination); + osc.start(0); + + context.startRendering().then(function(buffer) { + var samples = buffer.getChannelData(0); + // the signal should be scaled + var failures = 0; + for (var i = 0; i < 2048; ++i) { + if (samples[i] > 0.5) { + failures = failures + 1; + } + } + ok(failures == 0, "signal should have been rescaled by gain: found " + failures + " points too loud."); + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug1255618.html b/dom/media/webaudio/test/test_bug1255618.html new file mode 100644 index 000000000..246a04438 --- /dev/null +++ b/dom/media/webaudio/test/test_bug1255618.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test sync XHR does not crash unlinked AudioContext</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +SimpleTest.waitForExplicitFinish(); + +const filename = "test_bug1255618.html"; + +function collect_and_fetch() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", filename, false); + var ended = false; + xhr.onloadend = function() { ended = true; } + // Sync XHR will suspend timeouts, which involves any AudioContexts still + // registered with the window. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1255618#c0 + xhr.send(null); + + ok(ended, "No crash during fetch"); + SimpleTest.finish(); +} + +var ac = new AudioContext(); + +ac.onstatechange = function () { + ac.onstatechange = null; + is(ac.state, "running", "statechange to running"); + ac = null; + SimpleTest.executeSoon(collect_and_fetch); +} + +</script> +</body> diff --git a/dom/media/webaudio/test/test_bug1267579.html b/dom/media/webaudio/test/test_bug1267579.html new file mode 100644 index 000000000..62eda14dc --- /dev/null +++ b/dom/media/webaudio/test/test_bug1267579.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that PeriodicWave handles fundamental fequency of zero</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// This is the smallest value that the test framework will accept +const testLength = 256; + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + runTest(); +}); + +var gTest = { + numberOfChannels: 1, + createGraph: function(context) { + var osc = context.createOscillator(); + osc.setPeriodicWave(context. + createPeriodicWave(new Float32Array([0.0, 1.0]), + new Float32Array(2))); + osc.frequency.value = 0.0; + osc.start(); + return osc; + }, + createExpectedBuffers: function(context) { + var buffer = context.createBuffer(1, testLength, context.sampleRate); + + for (var i = 0; i < buffer.length; ++i) { + buffer.getChannelData(0)[i] = 1.0; + } + return buffer; + }, +}; + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug808374.html b/dom/media/webaudio/test/test_bug808374.html new file mode 100644 index 000000000..cc4e02f41 --- /dev/null +++ b/dom/media/webaudio/test/test_bug808374.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 808374</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +try { + var ctx = new AudioContext(); + ctx.createBuffer(0, 1, ctx.sampleRate); +} catch (e) { + ok(true, "The test should not crash during CC"); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug827541.html b/dom/media/webaudio/test/test_bug827541.html new file mode 100644 index 000000000..9940c112e --- /dev/null +++ b/dom/media/webaudio/test/test_bug827541.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Tell the cycle collector about the audio contexts owned by nsGlobalWindow</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + var iframe = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); + document.body.appendChild(iframe); + var frameWin = iframe.contentWindow; + new frameWin.AudioContext(); + document.body.removeChild(iframe); + new frameWin.AudioContext(); + + ok(true, "This test should not leak"); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug839753.html b/dom/media/webaudio/test/test_bug839753.html new file mode 100644 index 000000000..bbab10b25 --- /dev/null +++ b/dom/media/webaudio/test/test_bug839753.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 839753</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +(new AudioContext()).destination.expando = null; +ok(true, "The test should not trigger wrapper cache assertions"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug845960.html b/dom/media/webaudio/test/test_bug845960.html new file mode 100644 index 000000000..4e37f91bf --- /dev/null +++ b/dom/media/webaudio/test/test_bug845960.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 845960</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +(new AudioContext()).decodeAudioData(new ArrayBuffer(0), function() {}); +ok(true, "Should not crash when the optional failure callback is not specified"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug856771.html b/dom/media/webaudio/test/test_bug856771.html new file mode 100644 index 000000000..8a6e622c2 --- /dev/null +++ b/dom/media/webaudio/test/test_bug856771.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 856771</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + + var source = context.createBufferSource(); + source.connect(context.destination); + ok(true, "Nothing should leak"); + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug866570.html b/dom/media/webaudio/test/test_bug866570.html new file mode 100644 index 000000000..0a1feca61 --- /dev/null +++ b/dom/media/webaudio/test/test_bug866570.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 859600</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +(new AudioContext()).foo = null; +ok(true, "The test should not fatally assert"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug866737.html b/dom/media/webaudio/test/test_bug866737.html new file mode 100644 index 000000000..40fcf83fd --- /dev/null +++ b/dom/media/webaudio/test/test_bug866737.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 866737</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var context = new AudioContext(); + +(function() { + var d = context.createDelay(); + var panner = context.createPanner(); + d.connect(panner); + var gain = context.createGain(); + panner.connect(gain); + gain.connect(context.destination); + gain.disconnect(0); +})(); + +SpecialPowers.forceGC(); +SpecialPowers.forceCC(); + +var gain = context.createGain(); +gain.connect(context.destination); +gain.disconnect(0); + +ok(true, "No crashes should happen!"); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug867089.html b/dom/media/webaudio/test/test_bug867089.html new file mode 100644 index 000000000..650676a44 --- /dev/null +++ b/dom/media/webaudio/test/test_bug867089.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 867089</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new AudioContext(); + + // Test invalid playbackRate values for AudioBufferSourceNode. + var source = ctx.createBufferSource(); + var buffer = ctx.createBuffer(2, 2048, 8000); + source.buffer = buffer; + source.playbackRate.value = 0.0; + source.connect(ctx.destination); + source.start(0); + + var source2 = ctx.createBufferSource(); + source2.buffer = buffer; + source2.playbackRate.value = -1.0; + source2.connect(ctx.destination); + source2.start(0); + + var source3 = ctx.createBufferSource(); + source3.buffer = buffer; + source3.playbackRate.value = 3000000.0; + source3.connect(ctx.destination); + source3.start(0); + ok(true, "We did not crash."); + SimpleTest.finish(); +}); + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug867104.html b/dom/media/webaudio/test/test_bug867104.html new file mode 100644 index 000000000..82852ba51 --- /dev/null +++ b/dom/media/webaudio/test/test_bug867104.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 867104</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new AudioContext(); + var source = ctx.createBufferSource(); + var b0 = ctx.createBuffer(32,798,22050); + var b1 = ctx.createBuffer(32,28,22050); + var sp = ctx.createScriptProcessor(0, 2, 0); + source.buffer = b0; + source.connect(sp); + source.start(0); + source.buffer = b1; + sp.onaudioprocess = function() { + ok(true, "We did not crash."); + sp.onaudioprocess = null; + SimpleTest.finish(); + }; +}); + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug867174.html b/dom/media/webaudio/test/test_bug867174.html new file mode 100644 index 000000000..ce88be1b3 --- /dev/null +++ b/dom/media/webaudio/test/test_bug867174.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 867174</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new AudioContext(); + + var source = ctx.createBufferSource(); + var buffer = ctx.createBuffer(2, 2048, 8000); + source.playbackRate.setTargetAtTime(0, 2, 3); + var sp = ctx.createScriptProcessor(); + source.connect(sp); + sp.connect(ctx.destination); + source.start(0); + + sp.onaudioprocess = function(e) { + // Now set the buffer + source.buffer = buffer; + + ok(true, "We did not crash."); + sp.onaudioprocess = null; + SimpleTest.finish(); + }; +}); + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug867203.html b/dom/media/webaudio/test/test_bug867203.html new file mode 100644 index 000000000..0ca31263d --- /dev/null +++ b/dom/media/webaudio/test/test_bug867203.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 867203</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new AudioContext(); + + var panner1 = ctx.createPanner(); + panner1.setVelocity(1, 1, 1); + ctx.listener.setVelocity(1, 1, 1); + (function() { + ctx.createBufferSource().connect(panner1); + })(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + ctx.createPanner(); + + ok(true, "We did not crash."); + SimpleTest.finish(); +}); + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug875221.html b/dom/media/webaudio/test/test_bug875221.html new file mode 100644 index 000000000..16560ae75 --- /dev/null +++ b/dom/media/webaudio/test/test_bug875221.html @@ -0,0 +1,239 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 875221</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("This test is generated by a fuzzer, so we leave these setTimeouts untouched."); + +try { o0 = document.createElement('audio'); } catch(e) { } +try { (document.body || document.documentElement).appendChild(o0); } catch(e) { } +try { o1 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o1.listener.dopplerFactor = 1; } catch(e) { } +try { o2 = o1.createScriptProcessor(); } catch(e) { } +try { o3 = o1.createChannelMerger(4); } catch(e) { } +try { o1.listener.dopplerFactor = 3; } catch(e) { } +try { o1.listener.setPosition(0, 134217728, 64) } catch(e) { } +try { o1.listener.dopplerFactor = 15; } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o4 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { } +try { o4.listener.speedOfSound = 2048; } catch(e) { } +try { o4.listener.setPosition(32768, 1, 1) } catch(e) { } +try { o5 = o1.createChannelSplitter(4); } catch(e) { } +try { o4.listener.setVelocity(4, 1, 0) } catch(e) { } +try { o4.startRendering(); } catch(e) { } +try { o4.startRendering(); } catch(e) { } +try { o4.listener.setPosition(64, 1, 0) } catch(e) { } +try { o1.listener.setOrientation(4194304, 15, 8388608, 15, 1, 1) } catch(e) { } +try { o1.listener.dopplerFactor = 256; } catch(e) { } +try { o6 = o4.createDelay(16); } catch(e) { } +try { o4.startRendering(); } catch(e) { } +try { o4.listener.setOrientation(0, 1, 0, 0, 31, 1073741824) } catch(e) { } +try { o4.listener.speedOfSound = 1; } catch(e) { } +try { o1.listener.speedOfSound = 0; } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o6.connect(o3, 1, 0) } catch(e) { } +try { o1.listener.setPosition(4294967296, 32, 1) } catch(e) { } +try { o1.listener.speedOfSound = 0; } catch(e) { } +try { o1.listener.speedOfSound = 0; } catch(e) { } +try { o1.listener.setVelocity(1, 256, 0) } catch(e) { } +try { o4.startRendering(); } catch(e) { } +try { o3.disconnect() } catch(e) { } +setTimeout("try { o4.startRendering(); } catch(e) { }",50) +try { o4.listener.setOrientation(0, 0, 2048, 128, 16384, 127) } catch(e) { } +try { o4.listener.setVelocity(0, 4, 1) } catch(e) { } +try { o7 = o4.createScriptProcessor(1024, 4, 1); } catch(e) { } +try { o8 = o4.createDynamicsCompressor(); } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +SpecialPowers.forceCC(); +SpecialPowers.forceGC(); +try { o4.listener.setOrientation(8192, 1, 1, 512, 0, 15) } catch(e) { } +setTimeout("try { o7.onaudioprocess = function() {}; } catch(e) { }",50) +try { o1.startRendering(); } catch(e) { } +try { o1.listener.speedOfSound = 1073741824; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o9 = o4.createScriptProcessor(1024, 1, 4); } catch(e) { } +try { o10 = o4.createAnalyser(); } catch(e) { } +try { o4.listener.speedOfSound = 0; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +try { o4.listener.setVelocity(524288, 1, 65536) } catch(e) { } +setTimeout("try { o2.connect(o9); } catch(e) { } setTimeout(done, 0);",1000) +try { o7.connect(o4); } catch(e) { } +try { o1.listener.setVelocity(1, 127, 31) } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +setTimeout("try { o5.disconnect() } catch(e) { }",100) +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o4.startRendering(); } catch(e) { } +setTimeout("try { o1.listener.dopplerFactor = 1; } catch(e) { }",100) +try { o5.disconnect() } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o10.disconnect() } catch(e) { } +try { o1.startRendering(); } catch(e) { } +try { o11 = o1.createGain(); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o4); } catch(e) { } +try { o4.listener.setOrientation(31, 0, 15, 0, 33554432, 1) } catch(e) { } +try { o4.listener.dopplerFactor = 1; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +setTimeout("try { o9.connect(o4); } catch(e) { }",50) +try { o2.connect(o9); } catch(e) { } +setTimeout("try { o9.connect(o1); } catch(e) { }",200) +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +try { o12 = o4.createDynamicsCompressor(); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +try { o9.onaudioprocess = function() {}; } catch(e) { } +try { o1.listener.speedOfSound = 262144; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o2.connect(o9); } catch(e) { } +setTimeout("try { o7.connect(o4); } catch(e) { }",50) +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o13 = o4.createGain(); } catch(e) { } +try { o4.listener.dopplerFactor = 31; } catch(e) { } +try { o11.gain.value = 268435456; } catch(e) { } +try { o1.listener.setOrientation(63, 3, 1, 63, 1, 2147483648) } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o4.listener.setVelocity(1, 0, 1) } catch(e) { } +try { o11.gain.value = 65536; } catch(e) { } +try { o2.connect(o9); } catch(e) { } +setTimeout("try { o7.connect(o4); } catch(e) { }",200) +try { o14 = o4.createDynamicsCompressor(); } catch(e) { } +setTimeout("try { o2.connect(o9); } catch(e) { }",50) +try { o7.connect(o1); } catch(e) { } +try { o15 = o1.createWaveShaper(); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +try { o16 = o1.createWaveShaper(); } catch(e) { } +try { o11.gain.value = 1; } catch(e) { } +try { o1.listener.speedOfSound = 16; } catch(e) { } +try { o4.listener.setVelocity(0, 127, 15) } catch(e) { } +try { o1.listener.setVelocity(0, 2048, 16777216) } catch(e) { } +try { o13.gain.value = 0; } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +try { o17 = document.createElement('audio'); } catch(e) { } +try { (document.body || document.documentElement).appendChild(o0); } catch(e) { } +try { o4.listener.setVelocity(3, 1, 256) } catch(e) { } +try { o11.gain.cancelScheduledValues(1) } catch(e) { } +try { o1.listener.dopplerFactor = 524288; } catch(e) { } +try { o9.onaudioprocess = function() {}; } catch(e) { } +setTimeout("try { o7.connect(o13, 0, 0) } catch(e) { }",50) +try { o1.listener.speedOfSound = 0; } catch(e) { } +try { o10.disconnect() } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o9.connect(o4); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o4); } catch(e) { } +try { o1.listener.speedOfSound = 1; } catch(e) { } +try { o15.disconnect() } catch(e) { } +try { o11.gain.exponentialRampToValueAtTime(0, 15) } catch(e) { } +try { o15.curve = new Float32Array(15); } catch(e) { } +try { o4.listener.setVelocity(1, 1, 1) } catch(e) { } +try { o14.connect(o6, 0, 0) } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +setTimeout("try { o7.connect(o1); } catch(e) { }",100) +try { o4.listener.setVelocity(1, 7, 1) } catch(e) { } +try { o18 = document.createElement('audio'); } catch(e) { } +try { (document.body || document.documentElement).appendChild(o18); } catch(e) { } +try { o19 = o4.createGain(); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +try { o4.listener.dopplerFactor = 0; } catch(e) { } +try { o1.listener.setPosition(256, 16, 1) } catch(e) { } +setTimeout("try { o2.connect(o9); } catch(e) { }",50) +try { o7.connect(o1); } catch(e) { } +try { o4.listener.speedOfSound = 31; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +setTimeout("try { o9.connect(o4); } catch(e) { }",1000) +try { o11.gain.value = 127; } catch(e) { } +try { o7.connect(o7, 0, 0) } catch(e) { } +try { o4.listener.speedOfSound = 63; } catch(e) { } +try { o11.gain.value = 33554432; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o4); } catch(e) { } +try { o4.listener.speedOfSound = 16; } catch(e) { } +try { o4.listener.setVelocity(1048576, 0, 127) } catch(e) { } +try { o1.listener.dopplerFactor = 0; } catch(e) { } +try { o6.connect(o2, 0, 1) } catch(e) { } +try { o5.disconnect() } catch(e) { } +try { o3.disconnect() } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +try { o16.disconnect() } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +try { o9.disconnect() } catch(e) { } +try { o4.listener.speedOfSound = 1; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o11.gain.setValueCurveAtTime(new Float32Array(3), 2048, 3) } catch(e) { } +try { o13.gain.value = 8; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o4); } catch(e) { } +try { o4.listener.setOrientation(1, 2048, 1, 1, 0, 31) } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o1); } catch(e) { } +try { o1.listener.speedOfSound = 256; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o4); } catch(e) { } +try { o4.listener.setVelocity(1, 67108864, 128) } catch(e) { } +setTimeout("try { o1.listener.setVelocity(0, 1, 1) } catch(e) { }",100) +try { o2.connect(o9); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +setTimeout("try { o20 = o1.createBiquadFilter(); } catch(e) { }",200) +try { o13.gain.value = 4096; } catch(e) { } +try { o1.listener.dopplerFactor = 0; } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +try { o2.connect(o9); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +setTimeout("try { o2.connect(o9); } catch(e) { }",200) +try { o7.connect(o1); } catch(e) { } +try { o3.connect(o15, 1, 1) } catch(e) { } +try { o2.connect(o12, 0, 0) } catch(e) { } +try { o19.gain.exponentialRampToValueAtTime(1, 0) } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } + +function done() { + ok(true, "We did not crash."); + SimpleTest.finish(); +} + + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug875402.html b/dom/media/webaudio/test/test_bug875402.html new file mode 100644 index 000000000..2dc347fc1 --- /dev/null +++ b/dom/media/webaudio/test/test_bug875402.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Crashtest for bug 875402</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.requestFlakyTimeout("This test is generated by a fuzzer, so we leave these setTimeouts untouched."); + +try { o1 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { } +try { o2 = o1.createScriptProcessor(); } catch(e) { } +try { o4 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { } +try { o5 = o1.createChannelSplitter(4); } catch(e) { } +try { o7 = o4.createScriptProcessor(1024, 4, 1); } catch(e) { } +SpecialPowers.forceCC(); +SpecialPowers.forceGC(); +try { o1.startRendering(); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o7.connect(o4); } catch(e) { } +try { o9 = o4.createScriptProcessor(1024, 1, 4); } catch(e) { } +try { o2.connect(o7); } catch(e) { } +try { o9.connect(o1); } catch(e) { } +setTimeout("try { o2.connect(o9); } catch(e) { } done();",1000) +try { o7.connect(o4); } catch(e) { } +setTimeout("try { o5.disconnect() } catch(e) { }",100) +try { o2.connect(o9); } catch(e) { } +try { o4.startRendering(); } catch(e) { } +try { o2.connect(o9); } catch(e) { } +setTimeout("try { o7.connect(o4); } catch(e) { }",50) +try { o13 = o4.createGain(); } catch(e) { } +setTimeout("try { o7.connect(o13, 0, 0) } catch(e) { }",50) + +function done() { + ok(true, "We did not crash."); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug894150.html b/dom/media/webaudio/test/test_bug894150.html new file mode 100644 index 000000000..08fd72413 --- /dev/null +++ b/dom/media/webaudio/test/test_bug894150.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> + +var ac = new AudioContext(); +ac.createPanner(); +var listener = ac.listener; +SpecialPowers.forceGC(); +SpecialPowers.forceCC(); +listener.setOrientation(0, 0, -1, 0, 0, 0); + +ok(true, "No crashes should happen!"); + +</script> +</body> diff --git a/dom/media/webaudio/test/test_bug956489.html b/dom/media/webaudio/test/test_bug956489.html new file mode 100644 index 000000000..920889290 --- /dev/null +++ b/dom/media/webaudio/test/test_bug956489.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test when and currentTime are in the same coordinate system</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("This test needs to wait a while for the AudioContext's timer to start."); +addLoadEvent(function() { + var freq = 330; + + var context = new AudioContext(); + + var buffer = context.createBuffer(1, context.sampleRate / freq, context.sampleRate); + for (var i = 0; i < buffer.length; ++i) { + buffer.getChannelData(0)[i] = Math.sin(2 * Math.PI * i / buffer.length); + } + + var source = context.createBufferSource(); + source.loop = true; + source.buffer = buffer; + + setTimeout(function () { + var finished = false; + + source.start(context.currentTime); + var processor = context.createScriptProcessor(256, 1, 1); + processor.onaudioprocess = function (e) { + if (finished) return; + var c = e.inputBuffer.getChannelData(0); + var result = true; + + for (var i = 0; i < buffer.length; ++i) { + if (Math.abs(c[i] - buffer.getChannelData(0)[i]) > 1e-9) { + result = false; + break; + } + } + finished = true; + ok(result, "when and currentTime are in same time coordinate system"); + SimpleTest.finish(); + } + processor.connect(context.destination); + source.connect(processor); + }, 500); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug964376.html b/dom/media/webaudio/test/test_bug964376.html new file mode 100644 index 000000000..1d9af1c1e --- /dev/null +++ b/dom/media/webaudio/test/test_bug964376.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test repeating audio is not distorted</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function gcd(a, b) { + if (b === 0) { + return a; + } + return gcd(b, a % b); +} + +var SAMPLE_PLACEMENT = 128; + +var gTest = { + length: 2048, + numberOfChannels: 1, + + createGraph: function(context) { + var freq = Math.round(context.sampleRate / SAMPLE_PLACEMENT); + var dur = context.sampleRate / gcd(freq, context.sampleRate); + var buffer = context.createBuffer(1, dur, context.sampleRate); + + for (var i = 0; i < context.sampleRate; ++i) { + buffer.getChannelData(0)[i] = Math.sin(freq * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + source.loop = true; + source.playbackRate.setValueAtTime(0.5, SAMPLE_PLACEMENT / context.sampleRate); + source.start(0); + + return source; + }, + + createExpectedBuffers: function(context) { + var freq = Math.round(context.sampleRate / SAMPLE_PLACEMENT); + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + var c = expectedBuffer.getChannelData(0); + for (var i = 0; i < c.length; ++i) { + if (i < SAMPLE_PLACEMENT) { + c[i] = Math.sin(freq * 2 * Math.PI * i / context.sampleRate); + } else { + c[i] = Math.sin(freq / 2 * 2 * Math.PI * (i + SAMPLE_PLACEMENT) / context.sampleRate); + } + } + + return expectedBuffer; + }, +}; + +runTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug966247.html b/dom/media/webaudio/test/test_bug966247.html new file mode 100644 index 000000000..9224ac2d4 --- /dev/null +++ b/dom/media/webaudio/test/test_bug966247.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether an audio file played with a volume set to 0 plays silence</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<audio preload=none src="ting-48k-1ch.ogg" controls> </audio> +<script> + SimpleTest.waitForExplicitFinish(); + + var count = 20; + + function isSilent(b) { + for (var i = 0; i < b.length; b++) { + if (b[i] != 0.0) { + return false; + } + } + return true; + } + + var a = document.getElementsByTagName("audio")[0]; + a.volume = 0.0; + var ac = new AudioContext(); + var measn = ac.createMediaElementSource(a); + var sp = ac.createScriptProcessor(); + + sp.onaudioprocess = function(e) { + var inputBuffer = e.inputBuffer.getChannelData(0); + ok(isSilent(inputBuffer), "The volume is set to 0, so all the elements of the buffer are supposed to be equal to 0.0"); + } + // Connect the MediaElementAudioSourceNode to the ScriptProcessorNode to check + // the audio volume. + measn.connect(sp); + a.play(); + + a.addEventListener("ended", function() { + sp.onaudioprocess = null; + SimpleTest.finish(); + }); + +</script> +</body> +</html> diff --git a/dom/media/webaudio/test/test_bug972678.html b/dom/media/webaudio/test/test_bug972678.html new file mode 100644 index 000000000..d0cb4e419 --- /dev/null +++ b/dom/media/webaudio/test/test_bug972678.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test buffers do not interfere when scheduled in sequence</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var OFFSETS = [0.005, 0.01, 0.02, 0.03]; +var LENGTH = 128; + +var gTest = { + length: 128 * OFFSETS.length, + numberOfChannels: 1, + + createGraph: function(context) { + var gain = context.createGain(); + + // create a repeating sample + var repeatingSample = context.createBuffer(1, 2, context.sampleRate); + var c = repeatingSample.getChannelData(0); + for (var i = 0; i < repeatingSample.length; ++i) { + c[i] = i % 2 == 0 ? 1 : -1; + } + + OFFSETS.forEach(function (offset, offsetIdx) { + // Schedule a set of nodes to repeat the sample. + for (var i = 0; i < LENGTH; i += repeatingSample.length) { + var source = context.createBufferSource(); + source.buffer = repeatingSample; + source.connect(gain); + source.start((offsetIdx * LENGTH + i + offset) / context.sampleRate); + } + + buffer = context.createBuffer(1, LENGTH, context.sampleRate); + c = buffer.getChannelData(0); + for (var i = 0; i < buffer.length; ++i) { + c[i] = i % 2 == 0 ? -1 : 1; + } + + var source = context.createBufferSource(); + source.buffer = buffer; + source.connect(gain); + source.start((offsetIdx * LENGTH + offset) / context.sampleRate); + }); + + return gain; + }, + + createExpectedBuffers: function(context) { + return context.createBuffer(1, gTest.length, context.sampleRate); + }, +}; + +runTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_channelMergerNode.html b/dom/media/webaudio/test/test_channelMergerNode.html new file mode 100644 index 000000000..a76aaa2e8 --- /dev/null +++ b/dom/media/webaudio/test/test_channelMergerNode.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ChannelMergerNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 6, + createGraph: function(context) { + var buffers = []; + for (var j = 0; j < 6; ++j) { + var buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate); + // Second channel is silent + } + buffers.push(buffer); + } + + var merger = context.createChannelMerger(); + is(merger.channelCount, 1, "merger node has 1 input channels"); + is(merger.channelCountMode, "explicit", "Correct channelCountMode for the merger node"); + is(merger.channelInterpretation, "speakers", "Correct channelCountInterpretation for the merger node"); + + for (var i = 0; i < 6; ++i) { + var source = context.createBufferSource(); + source.buffer = buffers[i]; + source.connect(merger, 0, i); + source.start(0); + } + + return merger; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(6, 2048, context.sampleRate); + for (var i = 0; i < 6; ++i) { + for (var j = 0; j < 2048; ++j) { + expectedBuffer.getChannelData(i)[j] = 0.5 * Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate); + } + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html b/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html new file mode 100644 index 000000000..22f0a39cb --- /dev/null +++ b/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ChannelMergerNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 6, + createGraph: function(context) { + var buffers = []; + for (var j = 0; j < 6; ++j) { + var buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate); + // Second channel is silent + } + buffers.push(buffer); + } + + var merger = context.createChannelMerger(); + is(merger.channelCount, 1, "merger node has 1 input channels"); + is(merger.channelCountMode, "explicit", "Correct channelCountMode for the merger node"); + is(merger.channelInterpretation, "speakers", "Correct channelCountInterpretation for the merger node"); + + for (var i = 0; i < 6; ++i) { + var source = context.createBufferSource(); + source.buffer = buffers[i]; + var gain = context.createGain(); + gain.gain.value = 0.5; + source.connect(gain); + gain.connect(merger, 0, i); + source.start(0); + } + + return merger; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(6, 2048, context.sampleRate); + for (var i = 0; i < 6; ++i) { + for (var j = 0; j < 2048; ++j) { + expectedBuffer.getChannelData(i)[j] = 0.5 * 0.5 * Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate); + } + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_channelSplitterNode.html b/dom/media/webaudio/test/test_channelSplitterNode.html new file mode 100644 index 000000000..30cb0028c --- /dev/null +++ b/dom/media/webaudio/test/test_channelSplitterNode.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ChannelSplitterNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// We do not use our generic graph test framework here because +// the splitter node is special in that it creates multiple +// output ports. + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(4, 2048, context.sampleRate); + for (var j = 0; j < 4; ++j) { + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(j)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate); + } + } + var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate); + + var destination = context.destination; + + var source = context.createBufferSource(); + + var splitter = context.createChannelSplitter(); + is(splitter.channelCount, 2, "splitter node has 2 input channels by default"); + is(splitter.channelCountMode, "max", "Correct channelCountMode for the splitter node"); + is(splitter.channelInterpretation, "speakers", "Correct channelCountInterpretation for the splitter node"); + + source.buffer = buffer; + source.connect(splitter); + + var channelsSeen = 0; + function createHandler(i) { + return function(e) { + is(e.inputBuffer.numberOfChannels, 1, "Correct input channel count"); + if (i < 4) { + compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(i)); + } else { + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0)); + } + e.target.onaudioprocess = null; + ++channelsSeen; + + if (channelsSeen == 6) { + SimpleTest.finish(); + } + }; + } + + for (var i = 0; i < 6; ++i) { + var sp = context.createScriptProcessor(2048, 1); + splitter.connect(sp, i); + sp.onaudioprocess = createHandler(i); + sp.connect(destination); + } + + source.start(0); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html b/dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html new file mode 100644 index 000000000..8e16271f3 --- /dev/null +++ b/dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ChannelSplitterNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// We do not use our generic graph test framework here because +// the splitter node is special in that it creates multiple +// output ports. + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(4, 2048, context.sampleRate); + var expectedBuffer = context.createBuffer(4, 2048, context.sampleRate); + for (var j = 0; j < 4; ++j) { + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(j)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate); + expectedBuffer.getChannelData(j)[i] = buffer.getChannelData(j)[i] / 2; + } + } + var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate); + + var destination = context.destination; + + var source = context.createBufferSource(); + + var splitter = context.createChannelSplitter(); + is(splitter.channelCount, 2, "splitter node has 2 input channels by default"); + is(splitter.channelCountMode, "max", "Correct channelCountMode for the splitter node"); + is(splitter.channelInterpretation, "speakers", "Correct channelCountInterpretation for the splitter node"); + + source.buffer = buffer; + var gain = context.createGain(); + gain.gain.value = 0.5; + source.connect(gain); + gain.connect(splitter); + + var channelsSeen = 0; + function createHandler(i) { + return function(e) { + is(e.inputBuffer.numberOfChannels, 1, "Correct input channel count"); + if (i < 4) { + compareBuffers(e.inputBuffer.getChannelData(0), expectedBuffer.getChannelData(i)); + } else { + compareBuffers(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0)); + } + e.target.onaudioprocess = null; + ++channelsSeen; + + if (channelsSeen == 6) { + SimpleTest.finish(); + } + }; + } + + for (var i = 0; i < 6; ++i) { + var sp = context.createScriptProcessor(2048, 1); + splitter.connect(sp, i); + sp.onaudioprocess = createHandler(i); + sp.connect(destination); + } + + source.start(0); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_convolverNode.html b/dom/media/webaudio/test/test_convolverNode.html new file mode 100644 index 000000000..38b58bd9b --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNode.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the ConvolverNode interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var conv = context.createConvolver(); + + is(conv.channelCount, 2, "Convolver node has 2 input channels by default"); + is(conv.channelCountMode, "clamped-max", "Correct channelCountMode for the Convolver node"); + is(conv.channelInterpretation, "speakers", "Correct channelCountInterpretation for the Convolver node"); + + is(conv.buffer, null, "Default buffer value"); + conv.buffer = context.createBuffer(2, 1024, 22050); + is(conv.normalize, true, "Default normalize value"); + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_convolverNodeChannelCount.html b/dom/media/webaudio/test/test_convolverNodeChannelCount.html new file mode 100644 index 000000000..d5b261c81 --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNodeChannelCount.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ConvolverNode channel count</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +const signalLength = 2048; +const responseLength = 1000; +const outputLength = 2048; // < signalLength + responseLength to test bug 910171 + +var gTest = { + length: outputLength, + numberOfChannels: 2, + createGraph: function(context) { + var buffer = context.createBuffer(2, signalLength, context.sampleRate); + for (var i = 0; i < signalLength; ++i) { + var sample = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + // When mixed into a single channel, this produces silence + buffer.getChannelData(0)[i] = sample; + buffer.getChannelData(1)[i] = -sample; + } + + var response = context.createBuffer(2, responseLength, context.sampleRate); + for (var i = 0; i < responseLength; ++i) { + response.getChannelData(0)[i] = i / responseLength; + response.getChannelData(1)[i] = 1 - (i / responseLength); + } + + var convolver = context.createConvolver(); + convolver.buffer = response; + convolver.channelCount = 1; + + expectException(function() { convolver.channelCount = 3; }, + DOMException.NOT_SUPPORTED_ERR); + convolver.channelCountMode = "explicit"; + expectException(function() { convolver.channelCountMode = "max"; }, + DOMException.NOT_SUPPORTED_ERR); + convolver.channelInterpretation = "discrete"; + convolver.channelInterpretation = "speakers"; + + var source = context.createBufferSource(); + source.buffer = buffer; + source.connect(convolver); + source.start(0); + + return convolver; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_convolverNodeDelay.html b/dom/media/webaudio/test/test_convolverNodeDelay.html new file mode 100644 index 000000000..2e8caf802 --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNodeDelay.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<title>Test convolution to delay a triangle pulse</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const sampleRate = 48000; +const LENGTH = 12800; +// tolerate 16-bit math. +const EPSILON = 1.0 / Math.pow(2, 15); + +// Triangle pulse +var sourceBuffer = new OfflineAudioContext(1, 1, sampleRate). + createBuffer(1, 2 * 128, sampleRate); +var channelData = sourceBuffer.getChannelData(0); +for (var i = 0; i < 128; ++i) { + channelData[i] = i/128; + channelData[128 + i] = 1.0 - i/128; +} + +function test_delay_index(delayIndex) { + + var context = new OfflineAudioContext(2, LENGTH, sampleRate); + + var merger = context.createChannelMerger(2); + merger.connect(context.destination); + + var impulse = context.createBuffer(1, delayIndex + 1, sampleRate); + impulse.getChannelData(0)[delayIndex] = 1.0; + var convolver = context.createConvolver(); + convolver.normalize = false; + convolver.buffer = impulse; + convolver.connect(merger, 0, 0); + + var delayTime = delayIndex/sampleRate; + var delay = context.createDelay(delayTime || 1/sampleRate); + delay.delayTime.value = delayTime; + delay.connect(merger, 0, 1); + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + source.connect(convolver); + source.connect(delay); + source.start(0); + + return context.startRendering(). + then((buffer) => { + var convolverOutput = buffer.getChannelData(0); + var delayOutput = buffer.getChannelData(1); + var maxDiff = 0.0; + var maxIndex = 0; + for (var i = 0; i < buffer.length; ++i) { + var diff = Math.abs(convolverOutput[i] - delayOutput[i]); + if (diff > maxDiff) { + maxDiff = diff; + maxIndex = i; + } + } + // The convolver should produce similar output to the delay. + assert_approx_equals(convolverOutput[maxIndex], delayOutput[maxIndex], + EPSILON, "output at " + maxIndex); + }); +} + +// The 5/4 ratio provides sampling across a range of delays and offsets within +// blocks. +for (var delayIndex = 0; + delayIndex < LENGTH; + delayIndex = Math.floor((5 * (delayIndex + 1)) / 4)) { + promise_test(test_delay_index.bind(null, delayIndex), + "Delay " + delayIndex); +} +</script> diff --git a/dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html b/dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html new file mode 100644 index 000000000..1cfb51ce8 --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>Test convolution effect has finite duration</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(function() { + + const responseLength = 256; + // Accept an influence period of twice the responseLength to accept FFT + // implementations. + const tolerancePeriod = 2 * responseLength; + const totalSize = tolerancePeriod + responseLength; + + var context = new OfflineAudioContext(1, totalSize, 48000); + + var responseBuffer = + context.createBuffer(1, responseLength, context.sampleRate); + var responseChannelData = responseBuffer.getChannelData(0); + responseChannelData[0] = 1; + responseChannelData[responseLength - 1] = 1; + var convolver = context.createConvolver(); + convolver.buffer = responseBuffer; + convolver.connect(context.destination); + + var sourceBuffer = context.createBuffer(1, totalSize, context.sampleRate); + sourceBuffer.getChannelData(0)[0] = NaN; + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + source.connect(convolver); + source.start(); + + return context.startRendering(). + then((buffer) => { + var convolverOutput = buffer.getChannelData(0); + // There should be no non-zeros after the tolerance period. + var testIndex = tolerancePeriod; + for (; + testIndex < buffer.length - 1 && convolverOutput[testIndex] == 0; + ++testIndex) { + } + assert_equals(convolverOutput[testIndex], 0, "output at " + testIndex); + }); +}); +</script> diff --git a/dom/media/webaudio/test/test_convolverNodePassThrough.html b/dom/media/webaudio/test/test_convolverNodePassThrough.html new file mode 100644 index 000000000..d5f9ef8ab --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNodePassThrough.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ConvolverNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var convolver = context.createConvolver(); + + source.buffer = this.buffer; + convolver.buffer = this.buffer; + + source.connect(convolver); + + var convolverWrapped = SpecialPowers.wrap(convolver); + ok("passThrough" in convolverWrapped, "ConvolverNode should support the passThrough API"); + convolverWrapped.passThrough = true; + + source.start(0); + return convolver; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_convolverNodeWithGain.html b/dom/media/webaudio/test/test_convolverNodeWithGain.html new file mode 100644 index 000000000..7bbe24089 --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNodeWithGain.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ConvolverNode after a GainNode - Bug 891254 </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +const signalLength = 2048; +const responseLength = 100; +const outputLength = 4096; // > signalLength + responseLength + +var gTest = { + length: outputLength, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, signalLength, context.sampleRate); + for (var i = 0; i < signalLength; ++i) { + buffer.getChannelData(0)[i] = Math.sin(2 * Math.PI * i / signalLength); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + source.start(0); + + var response = context.createBuffer(1, responseLength, context.sampleRate); + for (var i = 0; i < responseLength; ++i) { + response.getChannelData(0)[i] = i / responseLength; + } + + var gain = context.createGain(); + gain.gain.value = -1; + source.connect(gain); + + var convolver1 = context.createConvolver(); + convolver1.buffer = response; + gain.connect(convolver1); + + var convolver2 = context.createConvolver(); + convolver2.buffer = response; + source.connect(convolver2); + + // The output of convolver1 should be the inverse of convolver2, so blend + // them together and expect silence. + var blend = context.createGain(); + convolver1.connect(blend); + convolver2.connect(blend); + + return blend; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_convolverNode_mono_mono.html b/dom/media/webaudio/test/test_convolverNode_mono_mono.html new file mode 100644 index 000000000..f7da2b020 --- /dev/null +++ b/dom/media/webaudio/test/test_convolverNode_mono_mono.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> + +<html> +<head> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="text/javascript" src="webaudio.js"></script> +<script type="text/javascript" src="layouttest-glue.js"></script> +<script type="text/javascript" src="blink/audio-testing.js"></script> +<script type="text/javascript" src="blink/convolution-testing.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + +<div id="description"></div> +<div id="console"></div> + +<script> +description("Tests ConvolverNode processing a mono channel with mono impulse response."); +SimpleTest.waitForExplicitFinish(); + +// 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 OfflineAudioContext(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.start(0); + + context.oncomplete = checkConvolvedResult(trianglePulse); + context.startRendering(); +} + +function finishJSTest() { + SimpleTest.finish(); +} + +runTest(); +successfullyParsed = true; + +</script> + +<script src="../fast/js/resources/js-test-post.js"></script> +</body> +</html> diff --git a/dom/media/webaudio/test/test_currentTime.html b/dom/media/webaudio/test/test_currentTime.html new file mode 100644 index 000000000..bb015e5e2 --- /dev/null +++ b/dom/media/webaudio/test/test_currentTime.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioContext.currentTime</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("This test needs to wait a while for the AudioContext's timer to start."); +addLoadEvent(function() { + var ac = new AudioContext(); + is(ac.currentTime, 0, "AudioContext.currentTime should be 0 initially"); + setTimeout(function() { + ok(ac.currentTime > 0, "AudioContext.currentTime should have increased by now"); + SimpleTest.finish(); + }, 1000); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_decodeAudioDataPromise.html b/dom/media/webaudio/test/test_decodeAudioDataPromise.html new file mode 100644 index 000000000..d07f55936 --- /dev/null +++ b/dom/media/webaudio/test/test_decodeAudioDataPromise.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the decodeAudioData API with Promise</title> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script src="webaudio.js"></script> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + +var finished = 0; + +function finish() { + if (++finished == 2) { + SimpleTest.finish(); + } +} + +var ac = new AudioContext(); +// Test that a the promise is rejected with an invalid source buffer. +expectNoException(function() { + var p = ac.decodeAudioData(" "); + ok(p instanceof Promise, "AudioContext.decodeAudioData should return a Promise"); + p.then(function(data) { + ok(false, "Promise should not resolve with an invalid source buffer."); + finish(); + }).catch(function(e) { + ok(true, "Promise should be rejected with an invalid source buffer."); + ok(e.name == "TypeError", "The error should be TypeError"); + finish(); + }) +}); + +// Test that a the promise is resolved with a valid source buffer. +var xhr = new XMLHttpRequest(); +xhr.open("GET", "ting-44.1k-1ch.ogg", true); +xhr.responseType = "arraybuffer"; +xhr.onload = function() { + var p = ac.decodeAudioData(xhr.response); + ok(p instanceof Promise, "AudioContext.decodeAudioData should return a Promise"); + p.then(function(data) { + ok(data instanceof AudioBuffer, "Promise should resolve, passing an AudioBuffer"); + ok(true, "Promise should resolve with a valid source buffer."); + finish(); + }).catch(function() { + ok(false, "Promise should not be rejected with a valid source buffer."); + finish(); + }); +}; +xhr.send(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_decodeMultichannel.html b/dom/media/webaudio/test/test_decodeMultichannel.html new file mode 100644 index 000000000..0fb2f5b3c --- /dev/null +++ b/dom/media/webaudio/test/test_decodeMultichannel.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<meta charset=utf-8> +<head> + <title>Test that we can decode 4 channel wave file in webaudio, but not in <audio></title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var filename = "audio-quad.wav"; + +SimpleTest.waitForExplicitFinish(); + +function finishTest(a) { + if (a) { + a = null; + SimpleTest.finish(); + } +} + +function decodeUsingAudioElement() { + var a = new Audio(); + a.addEventListener("error", function() { + ok(false, "Error loading metadata"); + finishTest(a); + }); + a.addEventListener("loadedmetadata", function() { + ok(true, "Metadata Loaded"); + finishTest(a); + }); + + a.src = filename; + a.load(); +} + +addLoadEvent(function() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", filename); + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + var context = new AudioContext(); + context.decodeAudioData(xhr.response, function(b) { + ok(true, "Decoding of a wave file with four channels succeded."); + is(b.numberOfChannels, 4, "The AudioBuffer should have 4 channels."); + decodeUsingAudioElement(); + }, function() { + ok(false, "Decoding of a wave file with four channels failed."); + decodeUsingAudioElement(); + }); + }; + xhr.send(null); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_decodeOpusTail.html b/dom/media/webaudio/test/test_decodeOpusTail.html new file mode 100644 index 000000000..b5b53f685 --- /dev/null +++ b/dom/media/webaudio/test/test_decodeOpusTail.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Regression test to check that opus files don't have a tail at the end.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +// This gets a 1 second Opus file and decodes it to a buffer. The opus file is +// decoded at 48kHz, and the OfflineAudioContext is also at 48kHz, no resampling +// is taking place. +fetch('sweep-300-330-1sec.opus') +.then(function(response) { return response.arrayBuffer(); }) +.then(function(buffer) { + var off = new OfflineAudioContext(1, 128, 48000); + off.decodeAudioData(buffer, function(decoded) { + var pcm = decoded.getChannelData(0); + is(pcm.length, 48000, "The length of the decoded file is correct."); + SimpleTest.finish(); + }); +}); + +</script> diff --git a/dom/media/webaudio/test/test_delayNode.html b/dom/media/webaudio/test/test_delayNode.html new file mode 100644 index 000000000..a3e314ef7 --- /dev/null +++ b/dom/media/webaudio/test/test_delayNode.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DelayNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + + var delay = context.createDelay(); + + source.buffer = buffer; + + source.connect(delay); + + ok(delay.delayTime, "The audioparam member must exist"); + is(delay.delayTime.value, 0, "Correct initial value"); + is(delay.delayTime.defaultValue, 0, "Correct default value"); + delay.delayTime.value = 0.5; + is(delay.delayTime.value, 0.5, "Correct initial value"); + is(delay.delayTime.defaultValue, 0, "Correct default value"); + is(delay.channelCount, 2, "delay node has 2 input channels by default"); + is(delay.channelCountMode, "max", "Correct channelCountMode for the delay node"); + is(delay.channelInterpretation, "speakers", "Correct channelCountInterpretation for the delay node"); + + expectException(function() { + context.createDelay(0); + }, DOMException.NOT_SUPPORTED_ERR); + expectException(function() { + context.createDelay(180); + }, DOMException.NOT_SUPPORTED_ERR); + expectTypeError(function() { + context.createDelay(NaN); + }, DOMException.NOT_SUPPORTED_ERR); + expectException(function() { + context.createDelay(-1); + }, DOMException.NOT_SUPPORTED_ERR); + context.createDelay(1); // should not throw + + // Delay the source stream by 2048 frames + delay.delayTime.value = 2048 / context.sampleRate; + + source.start(0); + return delay; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048 * 2, context.sampleRate); + for (var i = 2048; i < 2048 * 2; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i - 2048) / context.sampleRate); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeAtMax.html b/dom/media/webaudio/test/test_delayNodeAtMax.html new file mode 100644 index 000000000..6c7dde3d1 --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeAtMax.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DelayNode with maxDelayTime delay - bug 890528</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +const signalLength = 2048; +const delayLength = 1000; // Not on a block boundary +const outputLength = 4096 // > signalLength + 2 * delayLength; + +function applySignal(buffer, offset) { + for (var i = 0; i < signalLength; ++i) { + buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength); + } +} + +var gTest = { + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, signalLength, context.sampleRate); + applySignal(buffer, 0); + + var source = context.createBufferSource(); + source.buffer = buffer; + + const delayTime = delayLength / context.sampleRate; + var delay = context.createDelay(delayTime); + delay.delayTime.value = delayTime; + + source.connect(delay); + + source.start(0); + return delay; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, outputLength, context.sampleRate); + applySignal(expectedBuffer, delayLength); + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeChannelChanges.html b/dom/media/webaudio/test/test_delayNodeChannelChanges.html new file mode 100644 index 000000000..229bfd069 --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeChannelChanges.html @@ -0,0 +1,98 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>test DelayNode channel count changes</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestCompleteLog(); + +const bufferSize = 4096; + +var ctx; +var testDelay; +var stereoDelay; +var invertor; + +function compareOutputs(callback) { + var processor = ctx.createScriptProcessor(bufferSize, 2, 0); + testDelay.connect(processor); + invertor.connect(processor); + processor.onaudioprocess = + function(e) { + compareBuffers(e.inputBuffer, + ctx.createBuffer(2, bufferSize, ctx.sampleRate)); + e.target.onaudioprocess = null; + callback(); + } +} + +function startTest() { + // And a two-channel signal + var merger = ctx.createChannelMerger(); + merger.connect(testDelay); + merger.connect(stereoDelay); + var oscL = ctx.createOscillator(); + oscL.connect(merger, 0, 0); + oscL.start(0); + var oscR = ctx.createOscillator(); + oscR.type = "sawtooth"; + oscR.connect(merger, 0, 1); + oscR.start(0); + + compareOutputs( + function () { + // Disconnect the two-channel signal and test again + merger.disconnect(); + compareOutputs(SimpleTest.finish); + }); +} + +function prepareTest() { + ctx = new AudioContext(); + + // The output of a test delay node with mono and stereo input will be + // compared with that of separate mono and stereo delay nodes. + const delayTime = 0.3 * bufferSize / ctx.sampleRate; + testDelay = ctx.createDelay(delayTime); + testDelay.delayTime.value = delayTime; + monoDelay = ctx.createDelay(delayTime); + monoDelay.delayTime.value = delayTime; + stereoDelay = ctx.createDelay(delayTime); + stereoDelay.delayTime.value = delayTime; + + // Create a one-channel signal and connect to the delay nodes + var monoOsc = ctx.createOscillator(); + monoOsc.frequency.value = 110; + monoOsc.connect(testDelay); + monoOsc.connect(monoDelay); + monoOsc.start(0); + + // Invert the expected so that mixing with the test will find the difference. + invertor = ctx.createGain(); + invertor.gain.value = -1.0; + monoDelay.connect(invertor); + stereoDelay.connect(invertor); + + // Start the test after the delay nodes have begun processing. + var processor = ctx.createScriptProcessor(bufferSize, 1, 0); + processor.connect(ctx.destination); + + processor.onaudioprocess = + function(e) { + e.target.onaudioprocess = null; + processor.disconnect(); + startTest(); + }; +} +prepareTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeCycles.html b/dom/media/webaudio/test/test_delayNodeCycles.html new file mode 100644 index 000000000..f5f2e6786 --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeCycles.html @@ -0,0 +1,157 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the support of cycles.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const sampleRate = 48000; +const inputLength = 2048; + +addLoadEvent(function() { + function addSine(b) { + for (var i = 0; i < b.length; i++) { + b[i] += Math.sin(440 * 2 * Math.PI * i / sampleRate); + } + } + + function getSineBuffer(ctx) { + var buffer = ctx.createBuffer(1, inputLength, ctx.sampleRate); + addSine(buffer.getChannelData(0)); + return buffer; + } + + function createAndPlayWithCycleAndDelayNode(ctx, delayFrames) { + var source = ctx.createBufferSource(); + source.buffer = getSineBuffer(ctx); + + var gain = ctx.createGain(); + var delay = ctx.createDelay(); + delay.delayTime.value = delayFrames/ctx.sampleRate; + + source.connect(gain); + gain.connect(delay); + delay.connect(ctx.destination); + // cycle + delay.connect(gain); + + source.start(0); + } + + function createAndPlayWithCycleAndNoDelayNode(ctx) { + var source = ctx.createBufferSource(); + source.loop = true; + source.buffer = getSineBuffer(ctx); + + var gain = ctx.createGain(); + var gain2 = ctx.createGain(); + + source.connect(gain); + gain.connect(gain2); + // cycle + gain2.connect(gain); + gain2.connect(ctx.destination); + + source.start(0); + } + + function createAndPlayWithCycleAndNoDelayNodeInCycle(ctx) { + var source = ctx.createBufferSource(); + source.loop = true; + source.buffer = getSineBuffer(ctx); + + var delay = ctx.createDelay(); + var gain = ctx.createGain(); + var gain2 = ctx.createGain(); + + // Their is a cycle, a delay, but the delay is not in the cycle. + source.connect(delay); + delay.connect(gain); + gain.connect(gain2); + // cycle + gain2.connect(gain); + gain2.connect(ctx.destination); + + source.start(0); + } + + var remainingTests = 0; + function finish() { + if (--remainingTests == 0) { + SimpleTest.finish(); + } + } + + function getOfflineContext(oncomplete) { + var ctx = new OfflineAudioContext(1, sampleRate, sampleRate); + ctx.oncomplete = oncomplete; + return ctx; + } + + function checkSilentBuffer(e) { + var buffer = e.renderedBuffer.getChannelData(0); + for (var i = 0; i < buffer.length; i++) { + if (buffer[i] != 0.0) { + ok(false, "buffer should be silent."); + finish(); + return; + } + } + ok(true, "buffer should be silent."); + finish(); + } + + function checkNoisyBuffer(e, aDelayFrames) { + delayFrames = Math.max(128, aDelayFrames); + + var expected = new Float32Array(e.renderedBuffer.length); + for (var i = delayFrames; i < expected.length; i += delayFrames) { + addSine(expected.subarray(i, i + inputLength)); + } + + compareChannels(e.renderedBuffer.getChannelData(0), expected); + finish(); + } + + function expectSilentOutput(f) { + remainingTests++; + var ctx = getOfflineContext(checkSilentBuffer); + f(ctx); + ctx.startRendering(); + } + + function expectNoisyOutput(delayFrames) { + remainingTests++; + var ctx = getOfflineContext(); + ctx.oncomplete = function(e) { checkNoisyBuffer(e, delayFrames); }; + createAndPlayWithCycleAndDelayNode(ctx, delayFrames); + ctx.startRendering(); + } + + // This is trying to make a graph with a cycle and no DelayNode in the graph. + // The cycle subgraph should be muted, in this graph the output should be silent. + expectSilentOutput(createAndPlayWithCycleAndNoDelayNode); + // This is trying to make a graph with a cycle and a DelayNode in the graph, but + // not part of the cycle. + // The cycle subgraph should be muted, in this graph the output should be silent. + expectSilentOutput(createAndPlayWithCycleAndNoDelayNodeInCycle); + // Those are making legal graphs, with at least one DelayNode in the cycle. + // There should be some non-silent output. + expectNoisyOutput(sampleRate/4); + // DelayNode.delayTime will be clamped to 128/ctx.sampleRate. + // There should be some non-silent output. + expectNoisyOutput(0); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodePassThrough.html b/dom/media/webaudio/test/test_delayNodePassThrough.html new file mode 100644 index 000000000..4945ee95c --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodePassThrough.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DelayNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var delay = context.createDelay(); + + source.buffer = this.buffer; + + source.connect(delay); + + delay.delayTime.value = 0.5; + + // Delay the source stream by 2048 frames + delay.delayTime.value = 2048 / context.sampleRate; + + var delayWrapped = SpecialPowers.wrap(delay); + ok("passThrough" in delayWrapped, "DelayNode should support the passThrough API"); + delayWrapped.passThrough = true; + + source.start(0); + return delay; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + var silence = context.createBuffer(1, 2048, context.sampleRate); + + return [this.buffer, silence]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html b/dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html new file mode 100644 index 000000000..b9cee458d --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DelayNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var delay = context.createDelay(0.02); + + source.buffer = this.buffer; + + source.connect(delay); + + source.start(0); + return delay; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + this.buffer = expectedBuffer; + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeTailIncrease.html b/dom/media/webaudio/test/test_delayNodeTailIncrease.html new file mode 100644 index 000000000..751602824 --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeTailIncrease.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test increasing delay of DelayNode after input finishes</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const signalLength = 100; +const bufferSize = 1024; +// Delay should be long enough to allow CC to run +const delayBufferCount = 50; +const delayLength = delayBufferCount * bufferSize + 700; + +var count = 0; + +function applySignal(buffer, offset) { + for (var i = 0; i < signalLength; ++i) { + buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength); + } +} + +function onAudioProcess(e) { + switch(count) { + case 5: + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + break; + case delayBufferCount: + var offset = delayLength - count * bufferSize; + var ctx = e.target.context; + var expected = ctx.createBuffer(1, bufferSize, ctx.sampleRate); + applySignal(expected, offset); + compareBuffers(e.inputBuffer, expected); + SimpleTest.finish(); + } + count++; +} + +function startTest() { + var ctx = new AudioContext(); + var processor = ctx.createScriptProcessor(bufferSize, 1, 0); + processor.onaudioprocess = onAudioProcess; + + // Switch on delay at a time in the future. + var delayDuration = delayLength / ctx.sampleRate; + var delayStartTime = (delayLength - bufferSize) / ctx.sampleRate; + var delay = ctx.createDelay(delayDuration); + delay.delayTime.setValueAtTime(delayDuration, delayStartTime); + delay.connect(processor); + + // Short signal that finishes before switching to long delay + var buffer = ctx.createBuffer(1, signalLength, ctx.sampleRate); + applySignal(buffer, 0); + var source = ctx.createBufferSource(); + source.buffer = buffer; + source.start(); + source.connect(delay); +}; + +startTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html b/dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html new file mode 100644 index 000000000..fa431d61b --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test tail time lifetime of DelayNode after input is disconnected</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// Web Audio doesn't provide a means to precisely time disconnect()s but we +// can test that the output of delay nodes matches the output from their +// sources before they are disconnected. + +SimpleTest.waitForExplicitFinish(); + +const signalLength = 128; +const bufferSize = 4096; +const sourceCount = bufferSize / signalLength; +// Delay should be long enough to allow CC to run +var delayBufferCount = 20; +const delayLength = delayBufferCount * bufferSize; + +var sourceOutput = new Float32Array(bufferSize); +var delayOutputCount = 0; +var sources = []; + +function onDelayOutput(e) { + if (delayOutputCount < delayBufferCount) { + delayOutputCount++; + return; + } + + compareChannels(e.inputBuffer.getChannelData(0), sourceOutput); + e.target.onaudioprocess = null; + SimpleTest.finish(); +} + +function onSourceOutput(e) { + // Record the first buffer + e.inputBuffer.copyFromChannel(sourceOutput, 0); + e.target.onaudioprocess = null; +} + +function disconnectSources() { + for (var i = 0; i < sourceCount; ++i) { + sources[i].disconnect(); + } + + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); +} + +function startTest() { + var ctx = new AudioContext(); + + var sourceProcessor = ctx.createScriptProcessor(bufferSize, 1, 0); + sourceProcessor.onaudioprocess = onSourceOutput; + // Keep audioprocess events going after source disconnect. + sourceProcessor.connect(ctx.destination); + + var delayProcessor = ctx.createScriptProcessor(bufferSize, 1, 0); + delayProcessor.onaudioprocess = onDelayOutput; + + var delayDuration = delayLength / ctx.sampleRate; + for (var i = 0; i < sourceCount; ++i) { + var delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + delay.connect(delayProcessor); + + var source = ctx.createOscillator(); + source.frequency.value = 440 + 10 * i + source.start(i * signalLength / ctx.sampleRate); + source.stop((i + 1) * signalLength / ctx.sampleRate); + source.connect(delay); + source.connect(sourceProcessor); + + sources[i] = source; + } + + // Assuming the above Web Audio operations have already scheduled an event + // to run in stable state and start the graph thread, schedule a subsequent + // event to disconnect the sources, which will remove main thread connection + // references before it knows the graph thread has started using the source + // streams. + SimpleTest.executeSoon(disconnectSources); +}; + +startTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeTailWithGain.html b/dom/media/webaudio/test/test_delayNodeTailWithGain.html new file mode 100644 index 000000000..6994a7f9d --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeTailWithGain.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test tail time lifetime of DelayNode indirectly connected to source</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const signalLength = 130; +const bufferSize = 1024; +// Delay should be long enough to allow CC to run +const delayBufferCount = 50; +const delayLength = delayBufferCount * bufferSize + 700; + +var count = 0; + +function applySignal(buffer, offset) { + for (var i = 0; i < signalLength; ++i) { + buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength); + } +} + +function onAudioProcess(e) { + switch(count) { + case 5: + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + break; + case delayBufferCount: + var offset = delayLength - count * bufferSize; + var ctx = e.target.context; + var expected = ctx.createBuffer(1, bufferSize, ctx.sampleRate); + applySignal(expected, offset); + compareBuffers(e.inputBuffer, expected); + SimpleTest.finish(); + } + count++; +} + +function startTest() { + var ctx = new AudioContext(); + var processor = ctx.createScriptProcessor(bufferSize, 1, 0); + processor.onaudioprocess = onAudioProcess; + + var delayDuration = delayLength / ctx.sampleRate; + var delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + delay.connect(processor); + + var gain = ctx.createGain(); + gain.connect(delay); + + // Short signal that finishes before garbage collection + var buffer = ctx.createBuffer(1, signalLength, ctx.sampleRate); + applySignal(buffer, 0); + var source = ctx.createBufferSource(); + source.buffer = buffer; + source.start(); + source.connect(gain); +}; + +startTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeTailWithReconnect.html b/dom/media/webaudio/test/test_delayNodeTailWithReconnect.html new file mode 100644 index 000000000..6c1cda580 --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeTailWithReconnect.html @@ -0,0 +1,136 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test tail time lifetime of DelayNode after input finishes and new input added</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// The buffer source will start on a block boundary, so keeping the signal +// within one block ensures that it will not cross AudioProcessingEvent buffer +// boundaries. +const signalLength = 128; +const bufferSize = 1024; +// Delay should be long enough to allow CC to run +var delayBufferCount = 50; +var delayBufferOffset; +const delayLength = delayBufferCount * bufferSize; + +var phase = "initial"; +var sourceCount = 0; +var delayCount = 0; +var oscillator; +var delay; +var source; + +function applySignal(buffer, offset) { + for (var i = 0; i < signalLength; ++i) { + buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength); + } +} + +function bufferIsSilent(buffer, out) { + for (var i = 0; i < buffer.length; ++i) { + if (buffer.getChannelData(0)[i] != 0) { + if (out) { + out.soundOffset = i; + } + return false; + } + } + return true; +} + +function onDelayOutput(e) { + switch(phase) { + + case "initial": + // Wait for oscillator sound to exit delay + if (bufferIsSilent(e.inputBuffer)) + break; + + phase = "played oscillator"; + break; + + case "played oscillator": + // First tail time has expired. Start second source and remove references + // to the delay and connected second source. + oscillator.disconnect(); + source.connect(delay); + source.start(); + source = null; + delay = null; + phase = "started second source"; + break; + + case "second tail time": + if (delayCount == delayBufferCount) { + var ctx = e.target.context; + var expected = ctx.createBuffer(1, bufferSize, ctx.sampleRate); + applySignal(expected, delayBufferOffset); + compareBuffers(e.inputBuffer, expected); + e.target.onaudioprocess = null; + SimpleTest.finish(); + } + } + + delayCount++; +} + +function onSourceOutput(e) { + switch(phase) { + case "started second source": + var out = {}; + if (!bufferIsSilent(e.inputBuffer, out)) { + delayBufferCount += sourceCount; + delayBufferOffset = out.soundOffset; + phase = "played second source"; + } + break; + case "played second source": + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + phase = "second tail time"; + e.target.onaudioprocess = null; + } + + sourceCount++; +} + +function startTest() { + var ctx = new AudioContext(); + var delayDuration = delayLength / ctx.sampleRate; + delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + var processor1 = ctx.createScriptProcessor(bufferSize, 1, 0); + delay.connect(processor1); + processor1.onaudioprocess = onDelayOutput; + + // Signal to trigger initial tail time reference + oscillator = ctx.createOscillator(); + oscillator.start(0); + oscillator.stop(100/ctx.sampleRate); + oscillator.connect(delay); + + // Short signal, not started yet, with a ScriptProcessor to detect when it + // starts. It should finish before garbage collection. + var buffer = ctx.createBuffer(1, signalLength, ctx.sampleRate); + applySignal(buffer, 0); + source = ctx.createBufferSource(); + source.buffer = buffer; + var processor2 = ctx.createScriptProcessor(bufferSize, 1, 0); + source.connect(processor2); + processor2.onaudioprocess = onSourceOutput; +}; + +startTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_delayNodeWithGain.html b/dom/media/webaudio/test/test_delayNodeWithGain.html new file mode 100644 index 000000000..768bea77c --- /dev/null +++ b/dom/media/webaudio/test/test_delayNodeWithGain.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DelayNode with a GainNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + + var delay = context.createDelay(); + + source.buffer = buffer; + + var gain = context.createGain(); + gain.gain.value = 0.5; + + source.connect(gain); + gain.connect(delay); + + // Delay the source stream by 2048 frames + delay.delayTime.value = 2048 / context.sampleRate; + + source.start(0); + return delay; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048 * 2, context.sampleRate); + for (var i = 2048; i < 2048 * 2; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i - 2048) / context.sampleRate) / 2; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectAll.html b/dom/media/webaudio/test/test_disconnectAll.html new file mode 100644 index 000000000..9d3af066e --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectAll.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 256, context.sampleRate); + var data = sourceBuffer.getChannelData(0); + for (var j = 0; j < data.length; j++) { + data[j] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain1 = context.createGain(); + var gain2 = context.createGain(); + var gain3 = context.createGain(); + var merger = context.createChannelMerger(3); + + source.connect(gain1); + source.connect(gain2); + source.connect(gain3); + gain1.connect(merger); + gain2.connect(merger); + gain3.connect(merger); + source.start(); + + source.disconnect(); + + return merger; + } + }; + + runTest(); + </script> + </pre> + </body> +</html>
\ No newline at end of file diff --git a/dom/media/webaudio/test/test_disconnectAudioParam.html b/dom/media/webaudio/test/test_disconnectAudioParam.html new file mode 100644 index 000000000..1f4e79c56 --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectAudioParam.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioParam</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 256, context.sampleRate); + var data = sourceBuffer.getChannelData(0); + for (var j = 0; j < data.length; j++) { + data[j] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var half = context.createGain(); + var gain1 = context.createGain(); + var gain2 = context.createGain(); + + half.gain.value = 0.5; + + source.connect(gain1); + gain1.connect(gain2); + source.connect(half); + + half.connect(gain1.gain); + half.connect(gain2.gain); + + half.disconnect(gain2.gain); + + source.start(); + + return gain2; + }, + createExpectedBuffers: function(context) { + expectedBuffer = context.createBuffer(1, 256, context.sampleRate); + for (var i = 0; i < 256; ++i) { + expectedBuffer.getChannelData(0)[i] = 1.5; + } + + return expectedBuffer; + } + }; + + runTest(); + </script> + </pre> + </body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html b/dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html new file mode 100644 index 000000000..a08ffa53b --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioParam</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 2, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(2, 256, context.sampleRate); + for (var i = 1; i <= 2; i++) { + var data = sourceBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = i; + } + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var half = context.createGain(); + var gain1 = context.createGain(); + var gain2 = context.createGain(); + var splitter = context.createChannelSplitter(2); + + half.gain.value = 0.5; + + source.connect(gain1); + gain1.connect(gain2); + source.connect(half); + half.connect(splitter); + splitter.connect(gain1.gain, 0); + splitter.connect(gain2.gain, 1); + + splitter.disconnect(gain2.gain, 1); + + source.start(); + + return gain2; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(2, 256, context.sampleRate); + for (var i = 1; i <= 2; i++) { + var data = expectedBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = (i == 1) ? 1.5 : 3.0; + } + } + + return expectedBuffer; + } + }; + + runTest(); + </script> + </pre> + </body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectExceptions.html b/dom/media/webaudio/test/test_disconnectExceptions.html new file mode 100644 index 000000000..ceba972c9 --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectExceptions.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var ctx = new AudioContext(); + var sourceBuffer = ctx.createBuffer(2, 256, ctx.sampleRate); + for (var i = 1; i <= 2; i++) { + var data = sourceBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = i; + } + } + + var source = ctx.createBufferSource(); + source.buffer = sourceBuffer; + + var gain1 = ctx.createGain(); + var splitter = ctx.createChannelSplitter(2); + var merger = ctx.createChannelMerger(2); + var gain2 = ctx.createGain(); + var gain3 = ctx.createGain(); + + gain1.connect(splitter); + splitter.connect(gain2, 0); + splitter.connect(gain3, 1); + splitter.connect(merger, 0, 0); + splitter.connect(merger, 1, 1); + gain2.connect(gain3); + gain3.connect(ctx.destination); + merger.connect(ctx.destination); + + expectException(function() { + splitter.disconnect(2); + }, DOMException.INDEX_SIZE_ERR); + + expectNoException(function() { + splitter.disconnect(1); + splitter.disconnect(1); + }); + + expectException(function() { + gain1.disconnect(gain2); + }, DOMException.INVALID_ACCESS_ERR); + + expectException(function() { + gain1.disconnect(gain3); + ok(false, 'Should get InvalidAccessError exception'); + }, DOMException.INVALID_ACCESS_ERR); + + expectException(function() { + splitter.disconnect(gain2, 2); + }, DOMException.INDEX_SIZE_ERR); + + expectException(function() { + splitter.disconnect(gain1, 0); + }, DOMException.INVALID_ACCESS_ERR); + + expectException(function() { + splitter.disconnect(gain3, 0, 0); + }, DOMException.INVALID_ACCESS_ERR); + + expectException(function() { + splitter.disconnect(merger, 3, 0); + }, DOMException.INDEX_SIZE_ERR); + </script> + </pre> + </body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNode.html b/dom/media/webaudio/test/test_disconnectFromAudioNode.html new file mode 100644 index 000000000..931195146 --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectFromAudioNode.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 256, context.sampleRate); + var data = sourceBuffer.getChannelData(0); + for (var j = 0; j < data.length; j++) { + data[j] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain1 = context.createGain(); + var gain2 = context.createGain(); + var gain3 = context.createGain(); + + source.connect(gain1); + source.connect(gain2); + + gain1.connect(gain3); + gain2.connect(gain3); + + source.start(); + + source.disconnect(gain2); + + return gain3; + }, + createExpectedBuffers: function(context) { + expectedBuffer = context.createBuffer(1, 256, context.sampleRate); + for (var i = 0; i < 256; ++i) { + expectedBuffer.getChannelData(0)[i] = 1.0; + } + + return expectedBuffer; + } + }; + + runTest(); + </script> + </pre> + </body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html new file mode 100644 index 000000000..5c4e3ee5d --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 2, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(2, 256, context.sampleRate); + for (var i = 1; i <= 2; i++) { + var data = sourceBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = i; + } + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var splitter = context.createChannelSplitter(2); + var gain1 = context.createGain(); + var gain2 = context.createGain(); + var merger = context.createChannelMerger(2); + + source.connect(splitter); + splitter.connect(gain1, 0); + splitter.connect(gain2, 0); + splitter.connect(gain2, 1); + gain1.connect(merger, 0, 1); + gain2.connect(merger, 0, 1); + source.start(); + + splitter.disconnect(gain2, 0); + + return merger; + }, + createExpectedBuffers: function(context) { + expectedBuffer = context.createBuffer(2, 256, context.sampleRate); + for (var i = 0; i < 256; ++i) { + expectedBuffer.getChannelData(0)[i] = 0; + expectedBuffer.getChannelData(1)[i] = 3; + } + + return expectedBuffer; + } + }; + + runTest(); + </script> + </pre> + </body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html new file mode 100644 index 000000000..6526cf65b --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 3, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(3, 256, context.sampleRate); + for (var i = 1; i <= 3; i++) { + var data = sourceBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = i; + } + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var splitter = context.createChannelSplitter(3); + var merger = context.createChannelMerger(3); + + source.connect(splitter); + splitter.connect(merger, 0, 0); + splitter.connect(merger, 1, 1); + splitter.connect(merger, 2, 2); + source.start(); + + splitter.disconnect(merger, 2, 2); + + return merger; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(3, 256, context.sampleRate); + for (var i = 1; i <= 3; i++) { + var data = expectedBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = (i == 3) ? 0 : i; + } + } + + return expectedBuffer; + } + }; + + runTest(); + </script> + </pre> + </body> +</html>
\ No newline at end of file diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html b/dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html new file mode 100644 index 000000000..746b7ba93 --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> + <head> + <title> + Test whether we can disconnect all outbound connection of an AudioNode + </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 2, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 256, context.sampleRate); + var data = sourceBuffer.getChannelData(0); + for (var j = 0; j < data.length; j++) { + data[j] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var merger = context.createChannelMerger(2); + var gain = context.createGain(); + + source.connect(merger, 0, 0); + source.connect(gain); + source.connect(merger, 0, 1); + + source.disconnect(merger); + + source.start(); + + return merger; + }, + createExpectedBuffers: function(context) { + expectedBuffer = context.createBuffer(2, 256, context.sampleRate); + for (var channel = 0; channel < 2; channel++) { + for (var i = 0; i < 256; ++i) { + expectedBuffer.getChannelData(0)[i] = 0; + } + } + + return expectedBuffer; + } + }; + + runTest(); + </script> + </pre> + </body> +</html> diff --git a/dom/media/webaudio/test/test_disconnectFromOutput.html b/dom/media/webaudio/test/test_disconnectFromOutput.html new file mode 100644 index 000000000..8a6daf5c7 --- /dev/null +++ b/dom/media/webaudio/test/test_disconnectFromOutput.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test whether we can disconnect an AudioNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <pre id="test"> + <script class="testbody" type="text/javascript"> + var gTest = { + length: 256, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(3, 256, context.sampleRate); + for (var i = 1; i <= 3; i++) { + var data = sourceBuffer.getChannelData(i-1); + for (var j = 0; j < data.length; j++) { + data[j] = i; + } + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var splitter = context.createChannelSplitter(3); + var sum = context.createGain(); + + source.connect(splitter); + splitter.connect(sum, 0); + splitter.connect(sum, 1); + splitter.connect(sum, 2); + source.start(); + + splitter.disconnect(1); + + return sum; + }, + createExpectedBuffers: function(context) { + expectedBuffer = context.createBuffer(1, 256, context.sampleRate); + for (var i = 0; i < 256; ++i) { + expectedBuffer.getChannelData(0)[i] = 4; + } + + return expectedBuffer; + }, + }; + + runTest(); + </script> + </pre> + </body> +</html>
\ No newline at end of file diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNode.html b/dom/media/webaudio/test/test_dynamicsCompressorNode.html new file mode 100644 index 000000000..052b27671 --- /dev/null +++ b/dom/media/webaudio/test/test_dynamicsCompressorNode.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DynamicsCompressorNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function near(a, b, msg) { + ok(Math.abs(a - b) < 1e-4, msg); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + + var osc = context.createOscillator(); + var sp = context.createScriptProcessor(); + + var compressor = context.createDynamicsCompressor(); + + osc.connect(compressor); + osc.connect(sp); + compressor.connect(context.destination); + + is(compressor.channelCount, 2, "compressor node has 2 input channels by default"); + is(compressor.channelCountMode, "explicit", "Correct channelCountMode for the compressor node"); + is(compressor.channelInterpretation, "speakers", "Correct channelCountInterpretation for the compressor node"); + + // Verify default values + with (compressor) { + ok(threshold instanceof AudioParam, "treshold is an AudioParam"); + near(threshold.defaultValue, -24, "Correct default value for threshold"); + ok(knee instanceof AudioParam, "knee is an AudioParam"); + near(knee.defaultValue, 30, "Correct default value for knee"); + ok(ratio instanceof AudioParam, "knee is an AudioParam"); + near(ratio.defaultValue, 12, "Correct default value for ratio"); + is(typeof reduction, "number", "reduction is a number"); + near(reduction, 0, "Correct default value for reduction"); + ok(attack instanceof AudioParam, "attack is an AudioParam"); + near(attack.defaultValue, 0.003, "Correct default value for attack"); + ok(release instanceof AudioParam, "release is an AudioParam"); + near(release.defaultValue, 0.25, "Correct default value for release"); + } + + compressor.threshold.value = -80; + + osc.start(); + var iteration = 0; + sp.onaudioprocess = function(e) { + if (iteration > 10) { + ok(compressor.reduction < 0, + "Feeding a full-scale sine to a compressor should result in an db" + + "reduction."); + sp.onaudioprocess = null; + osc.stop(0); + + SimpleTest.finish(); + } + iteration++; + } +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html b/dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html new file mode 100644 index 000000000..1be838a4e --- /dev/null +++ b/dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test DynamicsCompressorNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var compressor = context.createDynamicsCompressor(); + + source.buffer = this.buffer; + + source.connect(compressor); + + var compressorWrapped = SpecialPowers.wrap(compressor); + ok("passThrough" in compressorWrapped, "DynamicsCompressorNode should support the passThrough API"); + compressorWrapped.passThrough = true; + + source.start(0); + return compressor; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html b/dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html new file mode 100644 index 000000000..2e4b38ea5 --- /dev/null +++ b/dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> + <title>Test DynamicsCompressor with Gain</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var samplerate = 44100; + var context = new OfflineAudioContext(1, samplerate/100, samplerate); + + var osc = context.createOscillator(); + osc.frequency.value = 2400; + + var gain = context.createGain(); + gain.gain.value = 1.5; + + // These numbers are borrowed from the example code on MDN + // https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode + var compressor = context.createDynamicsCompressor(); + compressor.threshold.value = -50; + compressor.knee.value = 40; + compressor.ratio.value = 12; + compressor.reduction.value = -20; + compressor.attack.value = 0; + compressor.release.value = 0.25; + + osc.connect(gain); + gain.connect(compressor); + compressor.connect(context.destination); + osc.start(); + + context.startRendering().then(buffer => { + var peak = Math.max(...buffer.getChannelData(0)); + console.log(peak); + // These values are experimentally determined. Without dynamics compression + // the peak should be just under 1.5. We also check for a minimum value + // to make sure we are not getting all zeros. + ok(peak >= 0.2 && peak < 1.0, "Peak value should be greater than 0.25 and less than 1.0"); + SimpleTest.finish(); + }); +}); +</script> +<pre> +</pre> +</body> diff --git a/dom/media/webaudio/test/test_gainNode.html b/dom/media/webaudio/test/test_gainNode.html new file mode 100644 index 000000000..41b19fda0 --- /dev/null +++ b/dom/media/webaudio/test/test_gainNode.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test GainNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + + var gain = context.createGain(); + + source.buffer = buffer; + + source.connect(gain); + + ok(gain.gain, "The audioparam member must exist"); + is(gain.gain.value, 1.0, "Correct initial value"); + is(gain.gain.defaultValue, 1.0, "Correct default value"); + gain.gain.value = 0.5; + is(gain.gain.value, 0.5, "Correct initial value"); + is(gain.gain.defaultValue, 1.0, "Correct default value"); + is(gain.channelCount, 2, "gain node has 2 input channels by default"); + is(gain.channelCountMode, "max", "Correct channelCountMode for the gain node"); + is(gain.channelInterpretation, "speakers", "Correct channelCountInterpretation for the gain node"); + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate) / 2; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_gainNodeInLoop.html b/dom/media/webaudio/test/test_gainNodeInLoop.html new file mode 100644 index 000000000..6b32cbcfa --- /dev/null +++ b/dom/media/webaudio/test/test_gainNodeInLoop.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test GainNode in presence of loops</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + source.loop = true; + source.start(0); + source.stop(sourceBuffer.duration * 2); + + var gain = context.createGain(); + // Adjust the gain in a way that we don't just end up modifying AudioChunk::mVolume + gain.gain.setValueAtTime(0.5, 0); + source.connect(gain); + return gain; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate); + for (var i = 0; i < 4096; ++i) { + expectedBuffer.getChannelData(0)[i] = 0.5; + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_gainNodePassThrough.html b/dom/media/webaudio/test/test_gainNodePassThrough.html new file mode 100644 index 000000000..2a7cd6bf4 --- /dev/null +++ b/dom/media/webaudio/test/test_gainNodePassThrough.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test GainNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var gain = context.createGain(); + + source.buffer = this.buffer; + + source.connect(gain); + + gain.gain.value = 0.5; + + var gainWrapped = SpecialPowers.wrap(gain); + ok("passThrough" in gainWrapped, "GainNode should support the passThrough API"); + gainWrapped.passThrough = true; + + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_iirFilterNodePassThrough.html b/dom/media/webaudio/test/test_iirFilterNodePassThrough.html new file mode 100644 index 000000000..7773a5b82 --- /dev/null +++ b/dom/media/webaudio/test/test_iirFilterNodePassThrough.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test IIRFilterNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var filter = context.createIIRFilter([0.5, 0.5], [1.0]); + + source.buffer = this.buffer; + + source.connect(filter); + + var filterWrapped = SpecialPowers.wrap(filter); + ok("passThrough" in filterWrapped, "BiquadFilterNode should support the passThrough API"); + filterWrapped.passThrough = true; + + source.start(0); + return filter; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_maxChannelCount.html b/dom/media/webaudio/test/test_maxChannelCount.html new file mode 100644 index 000000000..319e2bf1e --- /dev/null +++ b/dom/media/webaudio/test/test_maxChannelCount.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the AudioContext.destination interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// Work around bug 911777 +SpecialPowers.forceGC(); +SpecialPowers.forceCC(); + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ac = new AudioContext(); + ok(ac.destination.maxChannelCount > 0, "We can query the maximum number of channels"); + + var oac = new OfflineAudioContext(2, 1024, 48000); + ok(oac.destination.maxChannelCount, 2, "This OfflineAudioContext should have 2 max channels."); + + oac = new OfflineAudioContext(6, 1024, 48000); + ok(oac.destination.maxChannelCount, 6, "This OfflineAudioContext should have 6 max channels."); + + expectException(function() { + oac.destination.channelCount = oac.destination.channelCount + 1; + }, DOMException.INDEX_SIZE_ERR); + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaDecoding.html b/dom/media/webaudio/test/test_mediaDecoding.html new file mode 100644 index 000000000..07e18162b --- /dev/null +++ b/dom/media/webaudio/test/test_mediaDecoding.html @@ -0,0 +1,367 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the decodeAudioData API and Resampling</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script type="text/javascript"> + +// These routines have been copied verbatim from WebKit, and are used in order +// to convert a memory buffer into a wave buffer. +function writeString(s, a, offset) { + for (var i = 0; i < s.length; ++i) { + a[offset + i] = s.charCodeAt(i); + } +} + +function writeInt16(n, a, offset) { + n = Math.floor(n); + + var b1 = n & 255; + var b2 = (n >> 8) & 255; + + a[offset + 0] = b1; + a[offset + 1] = b2; +} + +function writeInt32(n, a, offset) { + n = Math.floor(n); + var b1 = n & 255; + var b2 = (n >> 8) & 255; + var b3 = (n >> 16) & 255; + var b4 = (n >> 24) & 255; + + a[offset + 0] = b1; + a[offset + 1] = b2; + a[offset + 2] = b3; + a[offset + 3] = b4; +} + +function writeAudioBuffer(audioBuffer, a, offset) { + var n = audioBuffer.length; + var channels = audioBuffer.numberOfChannels; + + for (var i = 0; i < n; ++i) { + for (var k = 0; k < channels; ++k) { + var buffer = audioBuffer.getChannelData(k); + var sample = buffer[i] * 32768.0; + + // Clip samples to the limitations of 16-bit. + // If we don't do this then we'll get nasty wrap-around distortion. + if (sample < -32768) + sample = -32768; + if (sample > 32767) + sample = 32767; + + writeInt16(sample, a, offset); + offset += 2; + } + } +} + +function createWaveFileData(audioBuffer) { + var frameLength = audioBuffer.length; + var numberOfChannels = audioBuffer.numberOfChannels; + var sampleRate = audioBuffer.sampleRate; + var bitsPerSample = 16; + var byteRate = sampleRate * numberOfChannels * bitsPerSample/8; + var blockAlign = numberOfChannels * bitsPerSample/8; + var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio + var headerByteLength = 44; + var totalLength = headerByteLength + wavDataByteLength; + + var waveFileData = new Uint8Array(totalLength); + + var subChunk1Size = 16; // for linear PCM + var subChunk2Size = wavDataByteLength; + var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size); + + writeString("RIFF", waveFileData, 0); + writeInt32(chunkSize, waveFileData, 4); + writeString("WAVE", waveFileData, 8); + writeString("fmt ", waveFileData, 12); + + writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4) + writeInt16(1, waveFileData, 20); // AudioFormat (2) + writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2) + writeInt32(sampleRate, waveFileData, 24); // SampleRate (4) + writeInt32(byteRate, waveFileData, 28); // ByteRate (4) + writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2) + writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4) + + writeString("data", waveFileData, 36); + writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4) + + // Write actual audio data starting at offset 44. + writeAudioBuffer(audioBuffer, waveFileData, 44); + + return waveFileData; +} + +</script> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// fuzzTolerance and fuzzToleranceMobile are used to determine fuzziness +// thresholds. They're needed to make sure that we can deal with neglibible +// differences in the binary buffer caused as a result of resampling the +// audio. fuzzToleranceMobile is typically larger on mobile platforms since +// we do fixed-point resampling as opposed to floating-point resampling on +// those platforms. +var files = [ + // An ogg file, 44.1khz, mono + { + url: "ting-44.1k-1ch.ogg", + valid: true, + expectedUrl: "ting-44.1k-1ch.wav", + numberOfChannels: 1, + frames: 30592, + sampleRate: 44100, + duration: 0.693, + fuzzTolerance: 5, + fuzzToleranceMobile: 1284 + }, + // An ogg file, 44.1khz, stereo + { + url: "ting-44.1k-2ch.ogg", + valid: true, + expectedUrl: "ting-44.1k-2ch.wav", + numberOfChannels: 2, + frames: 30592, + sampleRate: 44100, + duration: 0.693, + fuzzTolerance: 6, + fuzzToleranceMobile: 2544 + }, + // An ogg file, 48khz, mono + { + url: "ting-48k-1ch.ogg", + valid: true, + expectedUrl: "ting-48k-1ch.wav", + numberOfChannels: 1, + frames: 33297, + sampleRate: 48000, + duration: 0.693, + fuzzTolerance: 5, + fuzzToleranceMobile: 1388 + }, + // An ogg file, 48khz, stereo + { + url: "ting-48k-2ch.ogg", + valid: true, + expectedUrl: "ting-48k-2ch.wav", + numberOfChannels: 2, + frames: 33297, + sampleRate: 48000, + duration: 0.693, + fuzzTolerance: 14, + fuzzToleranceMobile: 2752 + }, + // Make sure decoding a wave file results in the same buffer (for both the + // resampling and non-resampling cases) + { + url: "ting-44.1k-1ch.wav", + valid: true, + expectedUrl: "ting-44.1k-1ch.wav", + numberOfChannels: 1, + frames: 30592, + sampleRate: 44100, + duration: 0.693, + fuzzTolerance: 0, + fuzzToleranceMobile: 0 + }, + { + url: "ting-48k-1ch.wav", + valid: true, + expectedUrl: "ting-48k-1ch.wav", + numberOfChannels: 1, + frames: 33297, + sampleRate: 48000, + duration: 0.693, + fuzzTolerance: 0, + fuzzToleranceMobile: 0 + }, + // // A wave file + // //{ url: "24bit-44khz.wav", valid: true, expectedUrl: "24bit-44khz-expected.wav" }, + // A non-audio file + { url: "invalid.txt", valid: false, sampleRate: 44100 }, + // A webm file with no audio + { url: "noaudio.webm", valid: false, sampleRate: 48000 }, + // A video ogg file with audio + { + url: "audio.ogv", + valid: true, + expectedUrl: "audio-expected.wav", + numberOfChannels: 2, + sampleRate: 44100, + frames: 47680, + duration: 1.0807, + fuzzTolerance: 106, + fuzzToleranceMobile: 3482 + } +]; + +// Returns true if the memory buffers are less different that |fuzz| bytes +function fuzzyMemcmp(buf1, buf2, fuzz) { + var result = true; + var difference = 0; + is(buf1.length, buf2.length, "same length"); + for (var i = 0; i < buf1.length; ++i) { + if (Math.abs(buf1[i] - buf2[i])) { + ++difference; + } + } + if (difference > fuzz) { + ok(false, "Expected at most " + fuzz + " bytes difference, found " + difference + " bytes"); + } + return difference <= fuzz; +} + +function getFuzzTolerance(test) { + var kIsMobile = + navigator.userAgent.indexOf("Mobile") != -1 || // b2g + navigator.userAgent.indexOf("Android") != -1; // android + return kIsMobile ? test.fuzzToleranceMobile : test.fuzzTolerance; +} + +function bufferIsSilent(buffer) { + for (var i = 0; i < buffer.length; ++i) { + if (buffer.getChannelData(0)[i] != 0) { + return false; + } + } + return true; +} + +function checkAudioBuffer(buffer, test) { + if (buffer.numberOfChannels != test.numberOfChannels) { + is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels"); + return; + } + ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration"); + if (Math.abs(buffer.duration - test.duration) >= 1e-3) { + ok(false, "got: " + buffer.duration + ", expected: " + test.duration); + } + is(buffer.sampleRate, test.sampleRate, "Correct sample rate"); + is(buffer.length, test.frames, "Correct length"); + + var wave = createWaveFileData(buffer); + ok(fuzzyMemcmp(wave, test.expectedWaveData, getFuzzTolerance(test)), "Received expected decoded data"); +} + +function checkResampledBuffer(buffer, test, callback) { + if (buffer.numberOfChannels != test.numberOfChannels) { + is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels"); + return; + } + ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration"); + if (Math.abs(buffer.duration - test.duration) >= 1e-3) { + ok(false, "got: " + buffer.duration + ", expected: " + test.duration); + } + // Take into account the resampling when checking the size + var expectedLength = test.frames * buffer.sampleRate / test.sampleRate; + ok(Math.abs(buffer.length - expectedLength) < 1.0, "Correct length", "got " + buffer.length + ", expected about " + expectedLength); + + // Playback the buffer in the original context, to resample back to the + // original rate and compare with the decoded buffer without resampling. + cx = test.nativeContext; + var expected = cx.createBufferSource(); + expected.buffer = test.expectedBuffer; + expected.start(); + var inverse = cx.createGain(); + inverse.gain.value = -1; + expected.connect(inverse); + inverse.connect(cx.destination); + var resampled = cx.createBufferSource(); + resampled.buffer = buffer; + resampled.start(); + // This stop should do nothing, but it tests for bug 937475 + resampled.stop(test.frames / cx.sampleRate); + resampled.connect(cx.destination); + cx.oncomplete = function(e) { + ok(!bufferIsSilent(e.renderedBuffer), "Expect buffer not silent"); + // Resampling will lose the highest frequency components, so we should + // pass the difference through a low pass filter. However, either the + // input files don't have significant high frequency components or the + // tolerance in compareBuffers() is too high to detect them. + compareBuffers(e.renderedBuffer, + cx.createBuffer(test.numberOfChannels, + test.frames, test.sampleRate)); + callback(); + } + cx.startRendering(); +} + +function runResampling(test, response, callback) { + var sampleRate = test.sampleRate == 44100 ? 48000 : 44100; + var cx = new OfflineAudioContext(1, 1, sampleRate); + cx.decodeAudioData(response, function onSuccess(asyncResult) { + is(asyncResult.sampleRate, sampleRate, "Correct sample rate"); + + checkResampledBuffer(asyncResult, test, callback); + }, function onFailure() { + ok(false, "Expected successful decode with resample"); + callback(); + }); +} + +function runTest(test, response, callback) { + // We need to copy the array here, because decodeAudioData will detach the + // array's buffer. + var compressedAudio = response.slice(0); + var expectCallback = false; + var cx = new OfflineAudioContext(test.numberOfChannels || 1, + test.frames || 1, test.sampleRate); + cx.decodeAudioData(response, function onSuccess(asyncResult) { + ok(expectCallback, "Success callback should fire asynchronously"); + ok(test.valid, "Did expect success for test " + test.url); + + checkAudioBuffer(asyncResult, test); + + test.expectedBuffer = asyncResult; + test.nativeContext = cx; + runResampling(test, compressedAudio, callback); + }, function onFailure() { + ok(expectCallback, "Failure callback should fire asynchronously"); + ok(!test.valid, "Did expect failure for test " + test.url); + callback(); + }); + expectCallback = true; +} + +function loadTest(test, callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", test.url, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + var getExpected = new XMLHttpRequest(); + getExpected.open("GET", test.expectedUrl, true); + getExpected.responseType = "arraybuffer"; + getExpected.onload = function() { + test.expectedWaveData = new Uint8Array(getExpected.response); + runTest(test, xhr.response, callback); + }; + getExpected.send(); + }; + xhr.send(); +} + +function loadNextTest() { + if (files.length) { + loadTest(files.shift(), loadNextTest); + } else { + SimpleTest.finish(); + } +} + +loadNextTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNode.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNode.html new file mode 100644 index 000000000..3e196735f --- /dev/null +++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNode.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaElementAudioSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function test() { + var audio = new Audio("small-shot.ogg"); + var context = new AudioContext(); + var expectedMinNonzeroSampleCount; + var expectedMaxNonzeroSampleCount; + var nonzeroSampleCount = 0; + var complete = false; + var iterationCount = 0; + + // This test ensures we receive at least expectedSampleCount nonzero samples + function processSamples(e) { + if (complete) { + return; + } + + if (iterationCount == 0) { + // Don't start playing the audio until the AudioContext stuff is connected + // and running. + audio.play(); + } + ++iterationCount; + + var buf = e.inputBuffer.getChannelData(0); + var nonzeroSamplesThisBuffer = 0; + for (var i = 0; i < buf.length; ++i) { + if (buf[i] != 0) { + ++nonzeroSamplesThisBuffer; + } + } + nonzeroSampleCount += nonzeroSamplesThisBuffer; + is(e.inputBuffer.numberOfChannels, 1, + "Checking data channel count (nonzeroSamplesThisBuffer=" + + nonzeroSamplesThisBuffer + ")"); + ok(nonzeroSampleCount <= expectedMaxNonzeroSampleCount, + "Too many nonzero samples (got " + nonzeroSampleCount + ", expected max " + expectedMaxNonzeroSampleCount + ")"); + if (nonzeroSampleCount >= expectedMinNonzeroSampleCount && + nonzeroSamplesThisBuffer == 0) { + ok(true, + "Check received enough nonzero samples (got " + nonzeroSampleCount + ", expected min " + expectedMinNonzeroSampleCount + ")"); + SimpleTest.finish(); + complete = true; + } + } + + audio.onloadedmetadata = function() { + var node = context.createMediaElementSource(audio); + var sp = context.createScriptProcessor(2048, 1); + node.connect(sp); + // Use a fuzz factor of 100 to account for samples that just happen to be zero + expectedMinNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) - 100; + expectedMaxNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) + 500; + sp.onaudioprocess = processSamples; + }; +} + +SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html new file mode 100644 index 000000000..7e03b7079 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaStreamAudioSourceNode doesn't get data from cross-origin media resources</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +// Turn off the authentication dialog blocking for this test. +SpecialPowers.setIntPref("network.auth.subresource-http-auth-allow", 2) + +var tests = [ + // Not the same origin no CORS asked for, should have silence + { url: "http://example.org:80/tests/dom/media/webaudio/test/small-shot.ogg", + cors: null, + expectSilence: true }, + // Same origin, should have sound + { url: "small-shot.ogg", + cors: null, + expectSilence: false }, + // Cross-origin but we asked for CORS and the server answered with the right + // header, should have + { url: "http://example.org:80/tests/dom/media/webaudio/test/corsServer.sjs", + cors: "anonymous", + expectSilence: false } +]; + +var testsRemaining = tests.length; + +tests.forEach(function(e) { + e.ac = new AudioContext(); + var a = new Audio(); + if (e.cors) { + a.crossOrigin = e.cors; + } + a.src = e.url; + document.body.appendChild(a); + + a.onloadedmetadata = () => { + // Wait for "loadedmetadata" before capturing since tracks are then known + // directly. If we set up the capture before "loadedmetadata" we + // (internally) have to wait an extra async jump for tracks to become known + // to main thread, before setting up audio data forwarding to the node. + // As that happens, the audio resource may have already ended on slow test + // machines, causing failures. + a.onloadedmetadata = null; + var measn = e.ac.createMediaElementSource(a); + var sp = e.ac.createScriptProcessor(2048, 1); + sp.seenSound = false; + sp.onaudioprocess = checkBufferSilent; + + measn.connect(sp); + a.play(); + }; + + function checkFinished(sp) { + if (a.ended) { + sp.onaudioprocess = null; + var not = e.expectSilence ? "" : "not"; + is(e.expectSilence, !sp.seenSound, + "Buffer is " + not + " silent as expected, for " + + e.url + " (cors: " + e.cors + ")"); + if (--testsRemaining == 0) { + SimpleTest.finish(); + } + } + } + + function checkBufferSilent(e) { + var inputArrayBuffer = e.inputBuffer.getChannelData(0); + var silent = true; + for (var i = 0; i < inputArrayBuffer.length; i++) { + if (inputArrayBuffer[i] != 0.0) { + silent = false; + break; + } + } + // It is acceptable to find a full buffer of silence here, even if we expect + // sound, because Gecko's looping on media elements is not seamless and we + // can underrun. We are looking for at least one buffer of non-silent data. + e.target.seenSound = !silent || e.target.seenSound; + checkFinished(e.target); + return silent; + } +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html new file mode 100644 index 000000000..8d3b0ed46 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html @@ -0,0 +1,128 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaStreamAudioSourceNode doesn't get data from cross-origin media resources</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function binIndexForFrequency(frequency, analyser) { + return 1 + Math.round(frequency * + analyser.fftSize / + analyser.context.sampleRate); +} + +function debugCanvas(analyser) { + var cvs = document.createElement("canvas"); + document.body.appendChild(cvs); + + // Easy: 1px per bin + cvs.width = analyser.frequencyBinCount; + cvs.height = 256; + cvs.style.border = "1px solid red"; + + var c = cvs.getContext('2d'); + var buf = new Uint8Array(analyser.frequencyBinCount); + + function render() { + c.clearRect(0, 0, cvs.width, cvs.height); + analyser.getByteFrequencyData(buf); + for (var i = 0; i < buf.length; i++) { + c.fillRect(i, (256 - (buf[i])), 1, 256); + } + requestAnimationFrame(render); + } + requestAnimationFrame(render); +} + + +function checkFrequency(an) { + an.getFloatFrequencyData(frequencyArray); + // We should have no energy when checking the data largely outside the index + // for 440Hz (the frequency of the sine wave), start checking an octave above, + // the Opus compression can add some harmonics to the pure since wave. + var index = binIndexForFrequency(880, an); + var underTreshold = true; + for (var i = index; i < frequencyArray.length; i++) { + // Let some slack, there might be some noise here because of int -> float + // conversion or the Opus encoding. + if (frequencyArray[i] > an.minDecibels + 40) { + return false; + } + } + + // On the other hand, we should find a peak at 440Hz. Our sine wave is not + // attenuated, we're expecting the peak to reach 0dBFs. + index = binIndexForFrequency(440, an); + info("energy at 440: " + frequencyArray[index] + ", threshold " + (an.maxDecibels - 10)); + if (frequencyArray[index] < (an.maxDecibels - 10)) { + return false; + } + + return true; +} + +var audioElement = new Audio(); +audioElement.src = 'sine-440-10s.opus' +audioElement.loop = true; +var ac = new AudioContext(); +var mediaElementSource = ac.createMediaElementSource(audioElement); +var an = ac.createAnalyser(); +frequencyArray = new Float32Array(an.frequencyBinCount); + +// Uncomment this to check what the analyser is doing. +// debugCanvas(an); + +mediaElementSource.connect(an) + +audioElement.play(); +// We want to check the we have the expected audio for at least two loop of +// the HTMLMediaElement, piped into an AudioContext. The file is ten seconds, +// and we use the default FFT size. +var lastCurrentTime = 0; +var loopCount = 0; +audioElement.onplaying = function() { + audioElement.ontimeupdate = function() { + // We don't run the analysis when close to loop point or at the + // beginning, since looping is not seamless, there could be an + // unpredictable amount of silence + var rv = checkFrequency(an); + info("currentTime: " + audioElement.currentTime); + if (audioElement.currentTime < 4 || + audioElement.currentTIme > 8){ + return; + } + if (!rv) { + ok(false, "Found unexpected noise during analysis."); + audioElement.ontimeupdate = null; + audioElement.onplaying = null; + ac.close(); + audioElement.src = ''; + SimpleTest.finish() + return; + } + ok(true, "Found correct audio signal during analysis"); + info(lastCurrentTime + " " + audioElement.currentTime); + if (lastCurrentTime > audioElement.currentTime) { + info("loopCount: " + loopCount); + if (loopCount > 1) { + audioElement.ontimeupdate = null; + audioElement.onplaying = null; + ac.close(); + audioElement.src = ''; + SimpleTest.finish(); + } + lastCurrentTime = audioElement.currentTime; + loopCount++; + } else { + lastCurrentTime = audioElement.currentTime; + } + } +} + +</script> diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html new file mode 100644 index 000000000..1bb0ad9ec --- /dev/null +++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaElementAudioSourceNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function test() { + var audio = new Audio("small-shot.ogg"); + var context = new AudioContext(); + var node = context.createMediaElementSource(audio); + var sp = context.createScriptProcessor(2048, 1); + node.connect(sp); + var nonzeroSampleCount = 0; + var complete = false; + var iterationCount = 0; + + var srcWrapped = SpecialPowers.wrap(node); + ok("passThrough" in srcWrapped, "MediaElementAudioSourceNode should support the passThrough API"); + srcWrapped.passThrough = true; + + // This test ensures we receive at least expectedSampleCount nonzero samples + function processSamples(e) { + if (complete) { + return; + } + + if (iterationCount == 0) { + // Don't start playing the audio until the AudioContext stuff is connected + // and running. + audio.play(); + } + ++iterationCount; + + var buf = e.inputBuffer.getChannelData(0); + var nonzeroSamplesThisBuffer = 0; + for (var i = 0; i < buf.length; ++i) { + if (buf[i] != 0) { + ++nonzeroSamplesThisBuffer; + } + } + nonzeroSampleCount += nonzeroSamplesThisBuffer; + if (iterationCount == 10) { + is(nonzeroSampleCount, 0, "The input must be silence"); + SimpleTest.finish(); + complete = true; + } + } + + audio.oncanplaythrough = function() { + sp.onaudioprocess = processSamples; + }; +} + +SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html new file mode 100644 index 000000000..ad0b355b1 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaElementAudioSourceNode before "loadedmetadata"</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var video = document.createElement("video"); +function test() { + video.src = "audiovideo.mp4"; + + var context = new AudioContext(); + var complete = false; + + video.onended = () => { + if (complete) { + return; + } + + complete = true; + ok(false, "Video ended without any samples seen"); + SimpleTest.finish(); + }; + + video.ontimeupdate = () => { + info("Timeupdate: " + video.currentTime); + }; + + var node = context.createMediaElementSource(video); + var sp = context.createScriptProcessor(2048, 1); + node.connect(sp); + + // This test ensures we receive some nonzero samples when we capture to + // WebAudio before "loadedmetadata". + sp.onaudioprocess = e => { + if (complete) { + return; + } + + var buf = e.inputBuffer.getChannelData(0); + for (var i = 0; i < buf.length; ++i) { + if (buf[i] != 0) { + complete = true; + ok(true, "Got non-zero samples"); + SimpleTest.finish(); + return; + } + } + }; + + video.play(); +} + +if (video.canPlayType("video/mp4")) { + test(); +} else { + ok(true, "MP4 not supported. Skipping."); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html new file mode 100644 index 000000000..5aa1a7910 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test MediaStreamAudioDestinationNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<audio id="audioelem"></audio> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("This test uses a live media element so it needs to wait for the media stack to do some work."); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createBufferSource(); + source.buffer = buffer; + + var dest = context.createMediaStreamDestination(); + source.connect(dest); + + var elem = document.getElementById('audioelem'); + elem.srcObject = dest.stream; + elem.onloadedmetadata = function() { + ok(true, "got metadata event"); + setTimeout(function() { + is(elem.played.length, 1, "should have a played interval"); + is(elem.played.start(0), 0, "should have played immediately"); + isnot(elem.played.end(0), 0, "should have played for a non-zero interval"); + + // This will end the media element. + dest.stream.getTracks()[0].stop(); + }, 2000); + }; + elem.onended = function() { + ok(true, "media element ended after destination track.stop()"); + SimpleTest.finish(); + }; + + source.start(0); + elem.play(); +}); +</script> diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html new file mode 100644 index 000000000..85d96d3e8 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaStreamAudioSourceNode processing is correct</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function createBuffer(context) { + var buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + buffer.getChannelData(1)[i] = -buffer.getChannelData(0)[i]; + } + return buffer; +} + +var gTest = { + length: 2048, + skipOfflineContextTests: true, + createGraph: function(context) { + var sourceGraph = new AudioContext(); + var source = sourceGraph.createBufferSource(); + source.buffer = createBuffer(context); + var dest = sourceGraph.createMediaStreamDestination(); + source.connect(dest); + source.start(0); + + var mediaStreamSource = context.createMediaStreamSource(dest.stream); + // channelCount and channelCountMode should have no effect + mediaStreamSource.channelCount = 1; + mediaStreamSource.channelCountMode = "explicit"; + return mediaStreamSource; + }, + createExpectedBuffers: function(context) { + return createBuffer(context); + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html new file mode 100644 index 000000000..f3cc0334a --- /dev/null +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaStreamAudioSourceNode doesn't get data from cross-origin media resources</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +var audio = new Audio("http://example.org:80/tests/dom/media/webaudio/test/small-shot.ogg"); +var context = new AudioContext(); +var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded()); +var sp = context.createScriptProcessor(2048, 1); +node.connect(sp); +var nonzeroSampleCount = 0; +var complete = false; +var iterationCount = 0; + +// This test ensures we receive at least expectedSampleCount nonzero samples +function processSamples(e) { + if (complete) { + return; + } + + if (iterationCount == 0) { + // Don't start playing the audio until the AudioContext stuff is connected + // and running. + audio.play(); + } + ++iterationCount; + + var buf = e.inputBuffer.getChannelData(0); + var nonzeroSamplesThisBuffer = 0; + for (var i = 0; i < buf.length; ++i) { + if (buf[i] != 0) { + ++nonzeroSamplesThisBuffer; + } + } + is(nonzeroSamplesThisBuffer, 0, + "Checking all samples are zero"); + if (iterationCount >= 20) { + SimpleTest.finish(); + complete = true; + } +} + +audio.oncanplaythrough = function() { + sp.onaudioprocess = processSamples; +}; +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html new file mode 100644 index 000000000..7a9b6c4a6 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test that MediaStreamAudioSourceNode and its input MediaStream stays alive while there are active tracks</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("gUM and WebAudio data is async to main thread. " + + "We need a timeout to see that something does " + + "NOT happen to data."); + +var context = new AudioContext(); +var analyser = context.createAnalyser(); + +function wait(millis, resolveWithThis) { + return new Promise(resolve => setTimeout(() => resolve(resolveWithThis), millis)); +} + +function binIndexForFrequency(frequency) { + return 1 + Math.round(frequency * analyser.fftSize / context.sampleRate); +} + +function waitForAudio(analysisFunction, cancelPromise) { + var data = new Uint8Array(analyser.frequencyBinCount); + var cancelled = false; + var cancelledMsg = ""; + cancelPromise.then(msg => { + cancelled = true; + cancelledMsg = msg; + }); + return new Promise((resolve, reject) => { + var loop = () => { + analyser.getByteFrequencyData(data); + if (cancelled) { + reject(new Error("waitForAudio cancelled: " + cancelledMsg)); + return; + } + if (analysisFunction(data)) { + resolve(); + return; + } + requestAnimationFrame(loop); + }; + loop(); + }); +} + +navigator.mediaDevices.getUserMedia({audio: true, fake: true}) + .then(stream => { + stream.onended = () => ended = true; + let source = context.createMediaStreamSource(stream); + source.connect(analyser); + analyser.connect(context.destination); + }) + .then(() => { + ok(true, "Waiting for audio to pass through the analyser") + return waitForAudio(arr => arr[binIndexForFrequency(1000)] > 200, + wait(60000, "Timeout waiting for audio")); + }) + .then(() => { + ok(true, "Audio was detected by the analyser. Forcing CC."); + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + + info("Checking that GC didn't destroy the stream or source node"); + return waitForAudio(arr => arr[binIndexForFrequency(1000)] < 50, + wait(5000, "Timeout waiting for GC (timeout OK)")) + .then(() => Promise.reject("Audio stopped unexpectedly"), + () => Promise.resolve()); + }) + .then(() => { + ok(true, "Audio is still flowing"); + SimpleTest.finish(); + }) + .catch(e => { + ok(false, "Error executing test: " + e + (e.stack ? "\n" + e.stack : "")); + SimpleTest.finish(); + }); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html new file mode 100644 index 000000000..d2c22600a --- /dev/null +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaStreamAudioSourceNode passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function createBuffer(context, delay) { + var buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048 - delay; ++i) { + buffer.getChannelData(0)[i + delay] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + buffer.getChannelData(1)[i + delay] = -buffer.getChannelData(0)[i + delay]; + } + return buffer; +} + +var gTest = { + length: 2048, + skipOfflineContextTests: true, + createGraph: function(context) { + var sourceGraph = new AudioContext(); + var source = sourceGraph.createBufferSource(); + source.buffer = createBuffer(context, 0); + var dest = sourceGraph.createMediaStreamDestination(); + source.connect(dest); + source.start(0); + + var mediaStreamSource = context.createMediaStreamSource(dest.stream); + // channelCount and channelCountMode should have no effect + mediaStreamSource.channelCount = 1; + mediaStreamSource.channelCountMode = "explicit"; + + var srcWrapped = SpecialPowers.wrap(mediaStreamSource); + ok("passThrough" in srcWrapped, "MediaStreamAudioSourceNode should support the passThrough API"); + srcWrapped.passThrough = true; + + return mediaStreamSource; + }, + createExpectedBuffers: function(context) { + return context.createBuffer(2, 2048, context.sampleRate); + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html new file mode 100644 index 000000000..4a4f03c53 --- /dev/null +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<meta charset="utf-8"> +<head> + <title>Test MediaStreamAudioSourceNode processing is correct</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +function test() { + var audio = new Audio("small-shot.ogg"); + var context = new AudioContext(); + var expectedMinNonzeroSampleCount; + var expectedMaxNonzeroSampleCount; + var nonzeroSampleCount = 0; + var complete = false; + var iterationCount = 0; + + // This test ensures we receive at least expectedSampleCount nonzero samples + function processSamples(e) { + if (complete) { + return; + } + + if (iterationCount == 0) { + // Don't start playing the audio until the AudioContext stuff is connected + // and running. + audio.play(); + } + ++iterationCount; + + var buf = e.inputBuffer.getChannelData(0); + var nonzeroSamplesThisBuffer = 0; + for (var i = 0; i < buf.length; ++i) { + if (buf[i] != 0) { + ++nonzeroSamplesThisBuffer; + } + } + nonzeroSampleCount += nonzeroSamplesThisBuffer; + is(e.inputBuffer.numberOfChannels, 1, + "Checking data channel count (nonzeroSamplesThisBuffer=" + + nonzeroSamplesThisBuffer + ")"); + ok(nonzeroSampleCount <= expectedMaxNonzeroSampleCount, + "Too many nonzero samples (got " + nonzeroSampleCount + ", expected max " + expectedMaxNonzeroSampleCount + ")"); + if (nonzeroSampleCount >= expectedMinNonzeroSampleCount && + nonzeroSamplesThisBuffer == 0) { + ok(true, + "Check received enough nonzero samples (got " + nonzeroSampleCount + ", expected min " + expectedMinNonzeroSampleCount + ")"); + SimpleTest.finish(); + complete = true; + } + } + + audio.onloadedmetadata = function() { + var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded()); + var sp = context.createScriptProcessor(2048, 1, 0); + node.connect(sp); + // Use a fuzz factor of 100 to account for samples that just happen to be zero + expectedMinNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) - 100; + expectedMaxNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) + 500; + sp.onaudioprocess = processSamples; + }; +} + +SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_mixingRules.html b/dom/media/webaudio/test/test_mixingRules.html new file mode 100644 index 000000000..0bdcff87e --- /dev/null +++ b/dom/media/webaudio/test/test_mixingRules.html @@ -0,0 +1,401 @@ +<!DOCTYPE html> +<html> +<head> + <title>Testcase for AudioNode channel up-mix/down-mix rules</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + +<script> + +// This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html + +var context = null; +var sp = null; +var renderNumberOfChannels = 8; +var singleTestFrameLength = 8; +var testBuffers; + +// A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases. +// Each element in the list is a string, with the number of connections corresponding to the length of the string, +// and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output). +// For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively. +var connectionsList = []; +for (var i = 1; i <= 8; ++i) { + connectionsList.push(i.toString()); + for (var j = 1; j <= 8; ++j) { + connectionsList.push(i.toString() + j.toString()); + } +} + +// A list of mixing rules, each of which will be tested against all of the connections in connectionsList. +var mixingRulesList = [ + {channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"}, + {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, + {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, + {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, + {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, + {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, + {channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"}, + {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"}, + {channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"}, + {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, + {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, + {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, + {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, + {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"}, + {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"}, + {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"}, + {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"}, + {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"}, + {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "discrete"}, + {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"}, +]; + +var numberOfTests = mixingRulesList.length * connectionsList.length; + +// Create an n-channel buffer, with all sample data zero except for a shifted impulse. +// The impulse position depends on the channel index. +// For example, for a 4-channel buffer: +// channel0: 1 0 0 0 0 0 0 0 +// channel1: 0 1 0 0 0 0 0 0 +// channel2: 0 0 1 0 0 0 0 0 +// channel3: 0 0 0 1 0 0 0 0 +function createTestBuffer(numberOfChannels) { + var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate); + for (var i = 0; i < numberOfChannels; ++i) { + var data = buffer.getChannelData(i); + data[i] = 1; + } + return buffer; +} + +// Discrete channel interpretation mixing: +// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix +// up-mix by filling channels until they run out then ignore remaining dest channels. +// down-mix by filling as many channels as possible, then dropping remaining source channels. +function discreteSum(sourceBuffer, destBuffer) { + if (sourceBuffer.length != destBuffer.length) { + is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length"); + } + + var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels); + var length = sourceBuffer.length; + + for (var c = 0; c < numberOfChannels; ++c) { + var source = sourceBuffer.getChannelData(c); + var dest = destBuffer.getChannelData(c); + for (var i = 0; i < length; ++i) { + dest[i] += source[i]; + } + } +} + +// Speaker channel interpretation mixing: +// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix +function speakersSum(sourceBuffer, destBuffer) +{ + var numberOfSourceChannels = sourceBuffer.numberOfChannels; + var numberOfDestinationChannels = destBuffer.numberOfChannels; + var length = destBuffer.length; + + if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) || + (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) { + // Handle mono -> stereo/Quad case (summing mono channel into both left and right). + var source = sourceBuffer.getChannelData(0); + var destL = destBuffer.getChannelData(0); + var destR = destBuffer.getChannelData(1); + + for (var i = 0; i < length; ++i) { + destL[i] += source[i]; + destR[i] += source[i]; + } + } else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) || + (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) { + // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right). + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var destL = destBuffer.getChannelData(0); + var destR = destBuffer.getChannelData(1); + + for (var i = 0; i < length; ++i) { + destL[i] += sourceL[i]; + destR[i] += sourceR[i]; + } + } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) { + // Handle stereo -> mono case. output += 0.5 * (input.L + input.R). + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var dest = destBuffer.getChannelData(0); + + for (var i = 0; i < length; ++i) { + dest[i] += 0.5 * (sourceL[i] + sourceR[i]); + } + } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) { + // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR). + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var sourceSL = sourceBuffer.getChannelData(2); + var sourceSR = sourceBuffer.getChannelData(3); + var dest = destBuffer.getChannelData(0); + + for (var i = 0; i < length; ++i) { + dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]); + } + } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) { + // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL), + // outputRight += 0.5 * (input.R + input.SR). + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var sourceSL = sourceBuffer.getChannelData(2); + var sourceSR = sourceBuffer.getChannelData(3); + var destL = destBuffer.getChannelData(0); + var destR = destBuffer.getChannelData(1); + + for (var i = 0; i < length; ++i) { + destL[i] += 0.5 * (sourceL[i] + sourceSL[i]); + destR[i] += 0.5 * (sourceR[i] + sourceSR[i]); + } + } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) { + // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR) + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var sourceSL = sourceBuffer.getChannelData(2); + var sourceSR = sourceBuffer.getChannelData(3); + var destL = destBuffer.getChannelData(0); + var destR = destBuffer.getChannelData(1); + var destSL = destBuffer.getChannelData(4); + var destSR = destBuffer.getChannelData(5); + + for (var i = 0; i < length; ++i) { + destL[i] += sourceL[i]; + destR[i] += sourceR[i]; + destSL[i] += sourceSL[i]; + destSR[i] += sourceSR[i]; + } + } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) { + // Handle mono -> 5.1 case, sum mono channel into center. + var source = sourceBuffer.getChannelData(0); + var dest = destBuffer.getChannelData(2); + + for (var i = 0; i < length; ++i) { + dest[i] += source[i]; + } + } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) { + // Handle 5.1 -> mono. + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var sourceC = sourceBuffer.getChannelData(2); + // skip LFE for now, according to current spec. + var sourceSL = sourceBuffer.getChannelData(4); + var sourceSR = sourceBuffer.getChannelData(5); + var dest = destBuffer.getChannelData(0); + + for (var i = 0; i < length; ++i) { + dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]); + } + } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) { + // Handle 5.1 -> stereo. + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var sourceC = sourceBuffer.getChannelData(2); + // skip LFE for now, according to current spec. + var sourceSL = sourceBuffer.getChannelData(4); + var sourceSR = sourceBuffer.getChannelData(5); + var destL = destBuffer.getChannelData(0); + var destR = destBuffer.getChannelData(1); + + for (var i = 0; i < length; ++i) { + destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]); + destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]); + } + } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) { + // Handle 5.1 -> Quad. + var sourceL = sourceBuffer.getChannelData(0); + var sourceR = sourceBuffer.getChannelData(1); + var sourceC = sourceBuffer.getChannelData(2); + // skip LFE for now, according to current spec. + var sourceSL = sourceBuffer.getChannelData(4); + var sourceSR = sourceBuffer.getChannelData(5); + var destL = destBuffer.getChannelData(0); + var destR = destBuffer.getChannelData(1); + var destSL = destBuffer.getChannelData(2); + var destSR = destBuffer.getChannelData(3); + + for (var i = 0; i < length; ++i) { + destL[i] += sourceL[i] + 0.7071 * sourceC[i]; + destR[i] += sourceR[i] + 0.7071 * sourceC[i]; + destSL[i] += sourceSL[i]; + destSR[i] += sourceSR[i]; + } + } else { + // Fallback for unknown combinations. + discreteSum(sourceBuffer, destBuffer); + } +} + +function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) { + var mixNode = context.createGain(); + mixNode.channelCount = channelCount; + mixNode.channelCountMode = channelCountMode; + mixNode.channelInterpretation = channelInterpretation; + mixNode.connect(sp); + + for (var i = 0; i < connections.length; ++i) { + var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); + + var source = context.createBufferSource(); + // Get a buffer with the right number of channels, converting from 1-based to 0-based index. + var buffer = testBuffers[connectionNumberOfChannels - 1]; + source.buffer = buffer; + source.connect(mixNode); + + // Start at the right offset. + var sampleFrameOffset = testNumber * singleTestFrameLength; + var time = sampleFrameOffset / context.sampleRate; + source.start(time); + } +} + +function computeNumberOfChannels(connections, channelCount, channelCountMode) { + if (channelCountMode == "explicit") + return channelCount; + + var computedNumberOfChannels = 1; // Must have at least one channel. + + // Compute "computedNumberOfChannels" based on all the connections. + for (var i = 0; i < connections.length; ++i) { + var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); + computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels); + } + + if (channelCountMode == "clamped-max") + computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount); + + return computedNumberOfChannels; +} + +function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) { + var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode); + + // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels. + var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate); + + // Mix all of the connections into the destination buffer. + for (var i = 0; i < connections.length; ++i) { + var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0); + var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index + + if (channelInterpretation == "speakers") { + speakersSum(sourceBuffer, destBuffer); + } else if (channelInterpretation == "discrete") { + discreteSum(sourceBuffer, destBuffer); + } else { + ok(false, "Invalid channel interpretation!"); + } + } + + // Validate that destBuffer matches the rendered output. + // We need to check the rendered output at a specific sample-frame-offset corresponding + // to the specific test case we're checking for based on testNumber. + + var sampleFrameOffset = testNumber * singleTestFrameLength; + for (var c = 0; c < renderNumberOfChannels; ++c) { + var renderedData = renderedBuffer.getChannelData(c); + for (var frame = 0; frame < singleTestFrameLength; ++frame) { + var renderedValue = renderedData[frame + sampleFrameOffset]; + + var expectedValue = 0; + if (c < destBuffer.numberOfChannels) { + var expectedData = destBuffer.getChannelData(c); + expectedValue = expectedData[frame]; + } + + if (Math.abs(renderedValue - expectedValue) > 1e-4) { + var s = "connections: " + connections + ", " + channelCountMode; + + // channelCount is ignored in "max" mode. + if (channelCountMode == "clamped-max" || channelCountMode == "explicit") { + s += "(" + channelCount + ")"; + } + + s += ", " + channelInterpretation + ". "; + + var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame; + is(renderedValue, expectedValue, message); + } + } + } +} + +function checkResult(event) { + var buffer = event.inputBuffer; + + // Sanity check result. + ok(buffer.length != numberOfTests * singleTestFrameLength || + buffer.numberOfChannels != renderNumberOfChannels, "Sanity check"); + + // Check all the tests. + var testNumber = 0; + for (var m = 0; m < mixingRulesList.length; ++m) { + var mixingRules = mixingRulesList[m]; + for (var i = 0; i < connectionsList.length; ++i, ++testNumber) { + checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation); + } + } + + sp.onaudioprocess = null; + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +function runTest() { + // Create 8-channel offline audio context. + // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8. + var totalFrameLength = numberOfTests * singleTestFrameLength; + context = new AudioContext(); + var nextPowerOfTwo = 256; + while (nextPowerOfTwo < totalFrameLength) { + nextPowerOfTwo *= 2; + } + sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels); + + // Set destination to discrete mixing. + sp.channelCount = renderNumberOfChannels; + sp.channelCountMode = "explicit"; + sp.channelInterpretation = "discrete"; + + // Create test buffers from 1 to 8 channels. + testBuffers = new Array(); + for (var i = 0; i < renderNumberOfChannels; ++i) { + testBuffers[i] = createTestBuffer(i + 1); + } + + // Schedule all the tests. + var testNumber = 0; + for (var m = 0; m < mixingRulesList.length; ++m) { + var mixingRules = mixingRulesList[m]; + for (var i = 0; i < connectionsList.length; ++i, ++testNumber) { + scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation); + } + } + + // Render then check results. + sp.onaudioprocess = checkResult; +} + +runTest(); + +</script> + +</body> +</html> diff --git a/dom/media/webaudio/test/test_mozaudiochannel.html b/dom/media/webaudio/test/test_mozaudiochannel.html new file mode 100644 index 000000000..6ba14347b --- /dev/null +++ b/dom/media/webaudio/test/test_mozaudiochannel.html @@ -0,0 +1,151 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for mozaudiochannel</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<pre id="test"> +<script type="application/javascript"> + +function test_basic() { + var ac = new AudioContext(); + ok(ac, "AudioContext created"); + + // Default + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + // Unpermitted channels + ac = new AudioContext("content"); + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + ac = new AudioContext("notification"); + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + ac = new AudioContext("alarm"); + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + ac = new AudioContext("telephony"); + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + ac = new AudioContext("ringer"); + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + ac = new AudioContext("publicnotification"); + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + runTest(); +} + +function test_permission(aChannel) { + var ac = new AudioContext(); + ok(ac, "AudioContext created"); + + is(ac.mozAudioChannelType, "normal", "Default ac channel == 'normal'"); + + var channel = SpecialPowers.wrap(ac).testAudioChannelInAudioNodeStream(); + is(channel, "normal", "AudioNodeStream is using the correct default audio channel."); + + SpecialPowers.pushPermissions( + [{ "type": "audio-channel-" + aChannel, "allow": true, "context": document }], + function() { + var ac = new AudioContext(aChannel); + is(ac.mozAudioChannelType, aChannel, "Default ac channel == '" + aChannel + "'"); + + var channel = SpecialPowers.wrap(ac).testAudioChannelInAudioNodeStream(); + is(channel, aChannel, "AudioNodeStream is using the correct new audio channel."); + + runTest(); + } + ); +} + +function test_preferences(aChannel) { + SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", aChannel ]]}, + function() { + SpecialPowers.pushPermissions( + [{ "type": "audio-channel-" + aChannel, "allow": false, "context": document }], + function() { + var ac = new AudioContext(aChannel); + ok(ac, "AudioContext created"); + is(ac.mozAudioChannelType, aChannel, "Default ac channel == '" + aChannel + "'"); + + var channel = SpecialPowers.wrap(ac).testAudioChannelInAudioNodeStream(); + is(channel, aChannel, "AudioNodeStream is using the correct audio channel."); + + runTest(); + } + ); + } + ); +} + +function test_wrong_preferences() { + SpecialPowers.pushPrefEnv({"set": [["media.defaultAudioChannel", 'foobar' ]]}, + function() { + var ac = new AudioContext(); + ok(ac, "AudioContext created"); + is(ac.mozAudioChannelType, 'normal', "Default ac channel == 'normal'"); + runTest(); + } + ); +} + +function test_testAudioChannelInAudioNodeStream() { + var ac = new AudioContext(); + ok(ac, "AudioContext created"); + + var status = false; + try { + ac.testAudioChannelInAudioNodeStream(); + } catch(e) { + status = true; + } + + ok(status, "testAudioChannelInAudioNodeStream() should not exist in content."); + runTest(); +} + +var tests = [ + test_basic, + + function() { test_permission("content"); }, + function() { test_permission("notification"); }, + function() { test_permission("alarm"); }, + function() { test_permission("telephony"); }, + function() { test_permission("ringer"); }, + function() { test_permission("publicnotification"); }, + + function() { test_preferences("content"); }, + function() { test_preferences("notification"); }, + function() { test_preferences("alarm"); }, + function() { test_preferences("telephony"); }, + function() { test_preferences("ringer"); }, + function() { test_preferences("publicnotification"); }, + + test_wrong_preferences, + + test_testAudioChannelInAudioNodeStream, +]; + +function runTest() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelAPI", true ]]}, runTest); +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestLongerTimeout(5); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_nodeToParamConnection.html b/dom/media/webaudio/test/test_nodeToParamConnection.html new file mode 100644 index 000000000..4525923db --- /dev/null +++ b/dom/media/webaudio/test/test_nodeToParamConnection.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test connecting an AudioNode to an AudioParam</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + createGraph: function(context) { + var sourceBuffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + sourceBuffer.getChannelData(0)[i] = 1; + sourceBuffer.getChannelData(1)[i] = -1; + } + + var destination = context.destination; + + var paramSource = context.createBufferSource(); + paramSource.buffer = this.buffer; + + var source = context.createBufferSource(); + source.buffer = sourceBuffer; + + var gain = context.createGain(); + + paramSource.connect(gain.gain); + source.connect(gain); + + paramSource.start(0); + source.start(0); + return gain; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + for (var j = 0; j < 2; ++j) { + this.buffer.getChannelData(j)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate); + } + } + var expectedBuffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = 1 + (this.buffer.getChannelData(0)[i] + this.buffer.getChannelData(1)[i]) / 2; + expectedBuffer.getChannelData(1)[i] = -(1 + (this.buffer.getChannelData(0)[i] + this.buffer.getChannelData(1)[i]) / 2); + } + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html b/dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html new file mode 100644 index 000000000..675106697 --- /dev/null +++ b/dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test OfflineAudioContext with a channel count less than the specified number</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new OfflineAudioContext(2, 100, 22050); + + var buf = ctx.createBuffer(6, 100, ctx.sampleRate); + for (var i = 0; i < 6; ++i) { + for (var j = 0; j < 100; ++j) { + buf.getChannelData(i)[j] = Math.sin(2 * Math.PI * 200 * j / ctx.sampleRate); + } + } + + var src = ctx.createBufferSource(); + src.buffer = buf; + src.start(0); + src.connect(ctx.destination); + ctx.destination.channelCountMode = "max"; + ctx.startRendering(); + ctx.oncomplete = function(e) { + is(e.renderedBuffer.numberOfChannels, 2, "Correct expected number of buffers"); + compareChannels(e.renderedBuffer.getChannelData(0), buf.getChannelData(0)); + compareChannels(e.renderedBuffer.getChannelData(1), buf.getChannelData(1)); + + SimpleTest.finish(); + }; +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html b/dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html new file mode 100644 index 000000000..7c7d5c8e5 --- /dev/null +++ b/dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test OfflineAudioContext with a channel count less than the specified number</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var ctx = new OfflineAudioContext(6, 100, 22050); + + var buf = ctx.createBuffer(2, 100, ctx.sampleRate); + for (var i = 0; i < 2; ++i) { + for (var j = 0; j < 100; ++j) { + buf.getChannelData(i)[j] = Math.sin(2 * Math.PI * 200 * j / ctx.sampleRate); + } + } + var emptyBuffer = ctx.createBuffer(1, 100, ctx.sampleRate); + + var src = ctx.createBufferSource(); + src.buffer = buf; + src.start(0); + src.connect(ctx.destination); + ctx.destination.channelCountMode = "max"; + ctx.startRendering(); + ctx.oncomplete = function(e) { + is(e.renderedBuffer.numberOfChannels, 6, "Correct expected number of buffers"); + compareChannels(e.renderedBuffer.getChannelData(0), buf.getChannelData(0)); + compareChannels(e.renderedBuffer.getChannelData(1), buf.getChannelData(1)); + for (var i = 2; i < 6; ++i) { + compareChannels(e.renderedBuffer.getChannelData(i), emptyBuffer.getChannelData(0)); + } + + SimpleTest.finish(); + }; +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_oscillatorNode.html b/dom/media/webaudio/test/test_oscillatorNode.html new file mode 100644 index 000000000..5eb488574 --- /dev/null +++ b/dom/media/webaudio/test/test_oscillatorNode.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the OscillatorNode interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + + var context = new AudioContext(); + var osc = context.createOscillator(); + + is(osc.channelCount, 2, "Oscillator node has 2 input channels by default"); + is(osc.channelCountMode, "max", "Correct channelCountMode for the Oscillator node"); + is(osc.channelInterpretation, "speakers", "Correct channelCountInterpretation for the Oscillator node"); + is(osc.type, "sine", "Correct default type"); + expectException(function() { + osc.type = "custom"; + }, DOMException.INVALID_STATE_ERR); + is(osc.type, "sine", "Cannot set the type to custom"); + is(osc.frequency.value, 440, "Correct default frequency value"); + is(osc.detune.value, 0, "Correct default detine value"); + + // Make sure that we can set all of the valid type values + var types = [ + "sine", + "square", + "sawtooth", + "triangle", + ]; + for (var i = 0; i < types.length; ++i) { + osc.type = types[i]; + } + + // Verify setPeriodicWave() + var real = new Float32Array([1.0, 0.5, 0.25, 0.125]); + var imag = new Float32Array([1.0, 0.7, -1.0, 0.5]); + osc.setPeriodicWave(context.createPeriodicWave(real, imag)); + is(osc.type, "custom", "Failed to set custom waveform"); + + expectNoException(function() { + osc.start(); + }); + expectNoException(function() { + osc.stop(); + }); + + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_oscillatorNode2.html b/dom/media/webaudio/test/test_oscillatorNode2.html new file mode 100644 index 000000000..1ddae937c --- /dev/null +++ b/dom/media/webaudio/test/test_oscillatorNode2.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test OscillatorNode lifetime and sine phase</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +const signalLength = 2048; + +function createOscillator(context) { + var osc = context.createOscillator(); + osc.start(0); + osc.stop(signalLength/context.sampleRate); + return osc; +} + +function connectUnreferencedOscillator(context, destination) { + var osc = createOscillator(context); + osc.connect(destination); +} + +var gTest = { + length: signalLength, + numberOfChannels: 1, + createGraph: function(context) { + var blend = context.createGain(); + + connectUnreferencedOscillator(context, blend); + // Test that the unreferenced oscillator remains alive until it has finished. + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + + // Create another sine wave oscillator in negative time, which should + // cancel when mixed with the unreferenced oscillator. + var oscillator = createOscillator(context); + oscillator.frequency.value = -440; + oscillator.connect(blend); + + return blend; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html b/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html new file mode 100644 index 000000000..8acc025da --- /dev/null +++ b/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the OscillatorNode when the frequency is negative</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + + var types = ["sine", + "square", + "sawtooth", + "triangle"]; + + var finished = 0; + function finish() { + if (++finished == types.length) { + SimpleTest.finish(); + } + } + + types.forEach(function(t) { + var context = new OfflineAudioContext(1, 256, 44100); + var osc = context.createOscillator(); + + osc.frequency.value = -440; + osc.type = t; + + osc.connect(context.destination); + osc.start(); + context.startRendering().then(function(buffer) { + var samples = buffer.getChannelData(0); + // This samples the wave form in the middle of the first period, the value + // should be negative. + ok(samples[Math.floor(44100 / 440 / 4)] < 0., "Phase should be inverted when using a " + t + " waveform"); + finish(); + }); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_oscillatorNodePassThrough.html b/dom/media/webaudio/test/test_oscillatorNodePassThrough.html new file mode 100644 index 000000000..c732bb273 --- /dev/null +++ b/dom/media/webaudio/test/test_oscillatorNodePassThrough.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test Oscillator with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var source = context.createOscillator(); + + var srcWrapped = SpecialPowers.wrap(source); + ok("passThrough" in srcWrapped, "OscillatorNode should support the passThrough API"); + srcWrapped.passThrough = true; + + source.start(0); + return source; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + + return [expectedBuffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_oscillatorNodeStart.html b/dom/media/webaudio/test/test_oscillatorNodeStart.html new file mode 100644 index 000000000..c43219c99 --- /dev/null +++ b/dom/media/webaudio/test/test_oscillatorNodeStart.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the OscillatorNode interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + + var context = new AudioContext(); + var osc = context.createOscillator(); + var sp = context.createScriptProcessor(0, 1, 0); + + osc.connect(sp); + + sp.onaudioprocess = function (e) { + var input = e.inputBuffer.getChannelData(0); + var isSilent = true; + for (var i = 0; i < input.length; i++) { + if (input[i] != 0.0) { + isSilent = false; + } + } + sp.onaudioprocess = null; + ok(isSilent, "OscillatorNode should be silent before calling start."); + SimpleTest.finish(); + } +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_oscillatorTypeChange.html b/dom/media/webaudio/test/test_oscillatorTypeChange.html new file mode 100644 index 000000000..aaf311a0c --- /dev/null +++ b/dom/media/webaudio/test/test_oscillatorTypeChange.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test OscillatorNode type change after it has started and triangle phase</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const bufferSize = 1024; + +function startTest() { + var ctx = new AudioContext(); + + var oscillator1 = ctx.createOscillator(); + oscillator1.connect(ctx.destination); + oscillator1.start(0); + + // Assuming the above Web Audio operations have already scheduled an event + // to run in stable state and start the graph thread, schedule a subsequent + // event to change the type of oscillator1. + SimpleTest.executeSoon(function() { + oscillator1.type = "triangle"; + + // Another triangle wave with -1 gain should cancel the first. This is + // starting at the same time as the type change, assuming that the phase + // is reset on type change. A negative frequency should achieve the same + // as the -1 gain but for bug 916285. + var oscillator2 = ctx.createOscillator(); + oscillator2.type = "triangle"; + oscillator2.start(0); + + var processor = ctx.createScriptProcessor(bufferSize, 1, 0); + oscillator1.connect(processor); + var gain = ctx.createGain(); + gain.gain.value = -1; + gain.connect(processor); + oscillator2.connect(gain); + + processor.onaudioprocess = function(e) { + compareChannels(e.inputBuffer.getChannelData(0), + new Float32Array(bufferSize)); + e.target.onaudioprocess = null; + SimpleTest.finish(); + } + }); +}; + +startTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNode.html b/dom/media/webaudio/test/test_pannerNode.html new file mode 100644 index 000000000..374ad3421 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNode.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PannerNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function near(a, b, msg) { + ok(Math.abs(a - b) < 1e-4, msg); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var destination = context.destination; + + var source = context.createBufferSource(); + + var panner = context.createPanner(); + + source.buffer = buffer; + + source.connect(panner); + panner.connect(destination); + + // Verify default values + is(panner.panningModel, "equalpower", "Correct default value for panning model"); + is(panner.distanceModel, "inverse", "Correct default value for distance model"); + near(panner.refDistance, 1, "Correct default value for ref distance"); + near(panner.maxDistance, 10000, "Correct default value for max distance"); + near(panner.rolloffFactor, 1, "Correct default value for rolloff factor"); + near(panner.coneInnerAngle, 360, "Correct default value for cone inner angle"); + near(panner.coneOuterAngle, 360, "Correct default value for cone outer angle"); + near(panner.coneOuterGain, 0, "Correct default value for cone outer gain"); + is(panner.channelCount, 2, "panner node has 2 input channels by default"); + is(panner.channelCountMode, "clamped-max", "Correct channelCountMode for the panner node"); + is(panner.channelInterpretation, "speakers", "Correct channelCountInterpretation for the panner node"); + + panner.setPosition(1, 1, 1); + near(panner.positionX.value, 1, "setPosition sets AudioParam properly"); + near(panner.positionY.value, 1, "setPosition sets AudioParam properly"); + near(panner.positionZ.value, 1, "setPosition sets AudioParam properly"); + + panner.setOrientation(0, 1, 0); + near(panner.orientationX.value, 0, "setOrientation sets AudioParam properly"); + near(panner.orientationY.value, 1, "setOrientation sets AudioParam properly"); + near(panner.orientationZ.value, 0, "setOrientation sets AudioParam properly"); + + panner.setVelocity(1, 1, 1); + + source.start(0); + SimpleTest.executeSoon(function() { + source.stop(0); + source.disconnect(); + panner.disconnect(); + + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNodeAbove.html b/dom/media/webaudio/test/test_pannerNodeAbove.html new file mode 100644 index 000000000..6bab394e6 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodeAbove.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PannerNode directly above</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +var gTest = { + numberOfChannels: 2, + createGraph: function(context) { + // An up vector will be made perpendicular to the front vector, in the + // front-up plane. + context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0.1, 1000, 0); + // Linearly dependent vectors are ignored. + context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307); + var panner = context.createPanner(); + panner.positionX.value = 2; // directly above + panner.rolloffFactor = 0; // no distance gain + panner.panningModel = "equalpower"; // no effect when directly above + + var source = context.createBufferSource(); + source.buffer = this.buffer; + source.connect(panner); + source.start(0); + + return panner; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + // Different signals in left and right buffers + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + expectedBuffer.getChannelData(1)[i] = Math.sin(220 * 2 * Math.PI * i / context.sampleRate); + } + this.buffer = expectedBuffer; + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html b/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html new file mode 100644 index 000000000..21abd2b60 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PannerNode produces output even when the even when the distance is from the listener is zero</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var BUF_SIZE = 128; + +var types = [ + "equalpower", + "HRTF" +] + +var finished = types.length; + +function finish() { + if (!--finished) { + SimpleTest.finish(); + } +} + +function test(type) { + var ac = new OfflineAudioContext(1, BUF_SIZE, 44100); + + // A sine to be used to fill the buffers + function sine(t) { + return Math.sin(440 * 2 * Math.PI * t / ac.sampleRate); + } + + var monoBuffer = ac.createBuffer(1, BUF_SIZE, ac.sampleRate); + for (var i = 0; i < BUF_SIZE; ++i) { + monoBuffer.getChannelData(0)[i] = sine(i); + } + + var monoSource = ac.createBufferSource(); + monoSource.buffer = monoBuffer; + monoSource.start(0); + + var panner = ac.createPanner(); + panner.distanceModel = "linear"; + panner.refDistance = 1; + panner.positionX.value = 0; + panner.positionY.value = 0; + panner.positionZ.value = 0; + monoSource.connect(panner); + + var panner2 = ac.createPanner(); + panner2.distanceModel = "inverse"; + panner2.refDistance = 1; + panner2.positionX.value = 0; + panner2.positionY.value = 0; + panner2.positionZ.value = 0; + panner.connect(panner2); + + var panner3 = ac.createPanner(); + panner3.distanceModel = "exponential"; + panner3.refDistance = 1; + panner3.positionX.value = 0; + panner3.positionY.value = 0; + panner3.positionZ.value = 0; + panner2.connect(panner3); + + panner3.connect(ac.destination); + + ac.startRendering().then(function(buffer) { + compareBuffers(buffer, monoBuffer); + finish(); + }); +} + +addLoadEvent(function() { + types.forEach(test); +}); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNodeChannelCount.html b/dom/media/webaudio/test/test_pannerNodeChannelCount.html new file mode 100644 index 000000000..63a52ea0c --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodeChannelCount.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PannerNode directly above</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 2, + createGraph: function(context) { + var buffer = context.createBuffer(2, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + var sample = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + // When mixed into a single channel, this produces silence + buffer.getChannelData(0)[i] = sample; + buffer.getChannelData(1)[i] = -sample; + } + + var panner = context.createPanner(); + panner.positionX.value = 1; + panner.positionY.value = 2; + panner.positionZ.value = 3; + panner.channelCount = 1; + expectException(function() { panner.channelCount = 3; }, + DOMException.NOT_SUPPORTED_ERR); + panner.channelCountMode = "explicit"; + expectException(function() { panner.channelCountMode = "max"; }, + DOMException.NOT_SUPPORTED_ERR); + panner.channelInterpretation = "discrete"; + panner.channelInterpretation = "speakers"; + + var source = context.createBufferSource(); + source.buffer = buffer; + source.connect(panner); + source.start(0); + + return panner; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html b/dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html new file mode 100644 index 000000000..c5312d042 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test left/right symmetry and block-offset invariance of HRTF panner</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const blockSize = 128; +const bufferSize = 4096; // > HRTF panner latency + +var ctx = new AudioContext(); + +function isChannelSilent(channel) { + for (var i = 0; i < channel.length; ++i) { + if (channel[i] != 0.0) { + return false; + } + } + return true; +} + +function startTest() { + var leftPanner = ctx.createPanner(); + var rightPanner = ctx.createPanner(); + leftPanner.type = "HRTF"; + rightPanner.type = "HRTF"; + leftPanner.positionX.value = -1; + rightPanner.positionX.value = 1; + + // Test that PannerNode processes the signal consistently irrespective of + // the offset in the processing block. This is done by inserting a delay of + // less than a block size before one panner. + const delayTime = 0.7 * blockSize / ctx.sampleRate; + var leftDelay = ctx.createDelay(delayTime); + leftDelay.delayTime.value = delayTime; + leftDelay.connect(leftPanner); + // and compensating for the delay after the other. + var rightDelay = ctx.createDelay(delayTime); + rightDelay.delayTime.value = delayTime; + rightPanner.connect(rightDelay); + + // Feed the panners with a signal having some harmonics to fill the spectrum. + var oscillator = ctx.createOscillator(); + oscillator.frequency.value = 110; + oscillator.type = "sawtooth"; + oscillator.connect(leftDelay); + oscillator.connect(rightPanner); + oscillator.start(0); + + // Switch the channels on one panner output, and it should match the other. + var splitter = ctx.createChannelSplitter(); + leftPanner.connect(splitter); + var merger = ctx.createChannelMerger(); + splitter.connect(merger, 0, 1); + splitter.connect(merger, 1, 0); + + // Invert one signal so that mixing with the other will find the difference. + var gain = ctx.createGain(); + gain.gain.value = -1.0; + merger.connect(gain); + + var processor = ctx.createScriptProcessor(bufferSize, 2, 0); + gain.connect(processor); + rightDelay.connect(processor); + processor.onaudioprocess = + function(e) { + compareBuffers(e.inputBuffer, + ctx.createBuffer(2, bufferSize, ctx.sampleRate)); + e.target.onaudioprocess = null; + SimpleTest.finish(); + } +} + +function prepareTest() { + // A PannerNode will produce no output until it has loaded its HRIR + // database. Wait for this to load before starting the test. + var processor = ctx.createScriptProcessor(bufferSize, 2, 0); + var panner = ctx.createPanner(); + panner.connect(processor); + var oscillator = ctx.createOscillator(); + oscillator.connect(panner); + oscillator.start(0); + + processor.onaudioprocess = + function(e) { + if (isChannelSilent(e.inputBuffer.getChannelData(0))) + return; + + oscillator.stop(0); + panner.disconnect(); + e.target.onaudioprocess = null; + startTest(); + }; +} +prepareTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNodePassThrough.html b/dom/media/webaudio/test/test_pannerNodePassThrough.html new file mode 100644 index 000000000..ab1f4b46f --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodePassThrough.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PannerNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var panner = context.createPanner(); + + source.buffer = this.buffer; + + source.connect(panner); + + context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0.1, 1000, 0); + context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307); + panner.positionX = 2; + panner.rolloffFactor = 0; + panner.panningModel = "equalpower"; + + var pannerWrapped = SpecialPowers.wrap(panner); + ok("passThrough" in pannerWrapped, "PannerNode should support the passThrough API"); + pannerWrapped.passThrough = true; + + source.start(0); + return panner; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNodeTail.html b/dom/media/webaudio/test/test_pannerNodeTail.html new file mode 100644 index 000000000..5fff52797 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNodeTail.html @@ -0,0 +1,232 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test tail time lifetime of PannerNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// This tests that a PannerNode does not release its reference before +// it finishes emitting sound. +// +// The PannerNode tail time is short, so, when a PannerNode is destroyed on +// the main thread, it is unlikely to notify the graph thread before the tail +// time expires. However, by adding DelayNodes downstream from the +// PannerNodes, the graph thread can have enough time to notice that a +// DelayNode has been destroyed. +// +// In the current implementation, DelayNodes will take a tail-time reference +// immediately when they receive the first block of sound from an upstream +// node, so this test connects the downstream DelayNodes while the upstream +// nodes are finishing, and then runs GC (on the main thread) before the +// DelayNodes receive any input (on the graph thread). +// +// Web Audio doesn't provide a means to precisely time connect()s but we can +// test that the output of delay nodes matches the output from a reference +// PannerNode that we know will not be GCed. +// +// Another set of delay nodes is added upstream to ensure that the source node +// has removed its self-reference after dispatching its "ended" event. + +SimpleTest.waitForExplicitFinish(); + +const blockSize = 128; +// bufferSize should be long enough that to allow an audioprocess event to be +// sent to the main thread and a connect message to return to the graph +// thread. +const bufferSize = 4096; +const pannerCount = bufferSize / blockSize; +// sourceDelayBufferCount should be long enough to allow the source node +// onended to finish and remove the source self-reference. +const sourceDelayBufferCount = 3; +var gotEnded = false; +// ccDelayLength should be long enough to allow CC to run +var ccDelayBufferCount = 20; +const ccDelayLength = ccDelayBufferCount * bufferSize; + +var ctx; +var testPanners = []; +var referencePanner; +var referenceProcessCount = 0; +var referenceOutput = [new Float32Array(bufferSize), + new Float32Array(bufferSize)]; +var testProcessor; +var testProcessCount = 0; + +function isChannelSilent(channel) { + for (var i = 0; i < channel.length; ++i) { + if (channel[i] != 0.0) { + return false; + } + } + return true; +} + +function onReferenceOutput(e) { + switch(referenceProcessCount) { + + case sourceDelayBufferCount - 1: + // The panners are about to finish. + if (!gotEnded) { + todo(false, "Source hasn't ended. Increase sourceDelayBufferCount?"); + } + + // Connect each PannerNode output to a downstream DelayNode, + // and connect ScriptProcessors to compare test and reference panners. + var delayDuration = ccDelayLength / ctx.sampleRate; + for (var i = 0; i < pannerCount; ++i) { + var delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + delay.connect(testProcessor); + testPanners[i].connect(delay); + } + testProcessor = null; + testPanners = null; + + // The panning effect is linear so only one reference panner is required. + // This also checks that the individual panners don't chop their output + // too soon. + referencePanner.connect(e.target); + + // Assuming the above operations have already scheduled an event to run in + // stable state and ask the graph thread to make connections, schedule a + // subsequent event to run cycle collection, which should not collect + // panners that are still producing sound. + SimpleTest.executeSoon(function() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + }); + + break; + + case sourceDelayBufferCount: + // Record this buffer during which PannerNode outputs were connected. + for (var i = 0; i < 2; ++i) { + e.inputBuffer.copyFromChannel(referenceOutput[i], i); + } + e.target.onaudioprocess = null; + e.target.disconnect(); + + // If the buffer is silent, there is probably not much point just + // increasing the buffer size, because, with the buffer size already + // significantly larger than panner tail time, it demonstrates that the + // lag between threads is much greater than the tail time. + if (isChannelSilent(referenceOutput[0])) { + todo(false, "Connections not detected."); + } + } + + referenceProcessCount++; +} + +function onTestOutput(e) { + if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) { + testProcessCount++; + return; + } + + for (var i = 0; i < 2; ++i) { + compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]); + } + e.target.onaudioprocess = null; + e.target.disconnect(); + SimpleTest.finish(); +} + +function startTest() { + // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp + // and 512 is fftSize() at 48 kHz. + const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512; + + // Create some PannerNodes downstream from DelayNodes with delays long + // enough for their source to finish, dispatch its "ended" event + // and release its playing reference. The DelayNodes should expire their + // tail-time references before the PannerNodes and so only the PannerNode + // lifetimes depends on their tail-time references. Many DelayNodes are + // created and timed to finish at different times so that one PannerNode + // will be finishing the block processed immediately after the connect is + // received. + var source = ctx.createBufferSource(); + // Just short of blockSize here to avoid rounding into the next block + var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate); + for (var i = 0; i < buffer.length; ++i) { + buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length); + } + source.buffer = buffer; + source.start(0); + source.onended = function(e) { + gotEnded = true; + }; + + // Time the first test panner to finish just before downstream DelayNodes + // are about the be connected. Note that DelayNode lifetime depends on + // maxDelayTime so set that equal to the delay. + var delayDuration = + (sourceDelayBufferCount * bufferSize + - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate; + + for (var i = 0; i < pannerCount; ++i) { + var delay = ctx.createDelay(delayDuration); + delay.delayTime.value = delayDuration; + source.connect(delay); + delay.connect(referencePanner) + + var panner = ctx.createPanner(); + panner.type = "HRTF"; + delay.connect(panner); + testPanners[i] = panner; + + delayDuration += blockSize / ctx.sampleRate; + } + + // Create a ScriptProcessor now to use as a timer to trigger connection of + // downstream nodes. It will also be used to record reference output. + var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0); + referenceProcessor.onaudioprocess = onReferenceOutput; + // Start audioprocess events before source delays are connected. + referenceProcessor.connect(ctx.destination); + + // The test ScriptProcessor will record output of testPanners. + // Create it now so that it is synchronized with the referenceProcessor. + testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0); + testProcessor.onaudioprocess = onTestOutput; + // Start audioprocess events before source delays are connected. + testProcessor.connect(ctx.destination); +} + +function prepareTest() { + ctx = new AudioContext(); + // Place the listener to the side of the origin, where the panners are + // positioned, to maximize delay in one ear. + ctx.listener.setPosition(1,0,0); + + // A PannerNode will produce no output until it has loaded its HRIR + // database. Wait for this to load before starting the test. + var processor = ctx.createScriptProcessor(bufferSize, 2, 0); + referencePanner = ctx.createPanner(); + referencePanner.type = "HRTF"; + referencePanner.connect(processor); + var oscillator = ctx.createOscillator(); + oscillator.connect(referencePanner); + oscillator.start(0); + + processor.onaudioprocess = function(e) { + if (isChannelSilent(e.inputBuffer.getChannelData(0))) + return; + + oscillator.stop(0); + oscillator.disconnect(); + referencePanner.disconnect(); + e.target.onaudioprocess = null; + SimpleTest.executeSoon(startTest); + }; +} +prepareTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNode_equalPower.html b/dom/media/webaudio/test/test_pannerNode_equalPower.html new file mode 100644 index 000000000..14e9f2153 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNode_equalPower.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test PannerNode</title> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="text/javascript" src="webaudio.js"></script> +<script type="text/javascript" src="layouttest-glue.js"></script> +<script type="text/javascript" src="blink/audio-testing.js"></script> +<script type="text/javascript" src="blink/panner-model-testing.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + function checkFinished() { + SimpleTest.finish(); + } + var ctx = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate); + createTestAndRun(ctx, nodesToCreate, 2, checkFinished); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_pannerNode_maxDistance.html b/dom/media/webaudio/test/test_pannerNode_maxDistance.html new file mode 100644 index 000000000..faca136b3 --- /dev/null +++ b/dom/media/webaudio/test/test_pannerNode_maxDistance.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PannerNode outputs silence when the distance is greater than maxDist</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var types = [ + "equalpower", + "HRTF" +] + +var finished = types.length; + +function finish() { + if (!--finished) { + SimpleTest.finish(); + } +} + +function test(type) { + var ac = new OfflineAudioContext(1, 128, 44100); + var osc = ac.createOscillator(); + var panner = ac.createPanner(); + + panner.distanceModel = "linear"; + panner.maxDistance = 100; + panner.positionY.value = 200; + ac.listener.setPosition(0, 0, 0); + + osc.connect(panner); + panner.connect(ac.destination); + + osc.start(); + + ac.startRendering().then(function(buffer) { + var silence = true; + var array = buffer.getChannelData(0); + for (var i = 0; i < buffer.length; i++) { + if (array[i] != 0) { + ok(false, "Found noise in the buffer."); + silence = false; + } + } + ok(silence, "The buffer is silent."); + finish(); + }); +} + + +addLoadEvent(function() { + types.forEach(test); +}); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_periodicWave.html b/dom/media/webaudio/test/test_periodicWave.html new file mode 100644 index 000000000..3ed440748 --- /dev/null +++ b/dom/media/webaudio/test/test_periodicWave.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test the PeriodicWave interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// real and imag are used in separate PeriodicWaves to make their peak values +// easy to determine. +const realMax = 99; +var real = new Float32Array(realMax + 1); +real[1] = 2.0; // fundamental +real[realMax] = 3.0; +const realPeak = real[1] + real[realMax]; +const realFundamental = 19.0; +var imag = new Float32Array(4); +imag[0] = 6.0; // should be ignored. +imag[3] = 0.5; +const imagPeak = imag[3]; +const imagFundamental = 551.0; + +const testLength = 4096; + +addLoadEvent(function() { + var ac = new AudioContext(); + ac.createPeriodicWave(new Float32Array(4096), new Float32Array(4096)); + expectException(function() { + ac.createPeriodicWave(new Float32Array(512), imag); + }, DOMException.NOT_SUPPORTED_ERR); + expectException(function() { + ac.createPeriodicWave(new Float32Array(0), new Float32Array(0)); + }, DOMException.NOT_SUPPORTED_ERR); + expectNoException(function() { + ac.createPeriodicWave(new Float32Array(4097), new Float32Array(4097)); + }); + + runTest(); +}); + +var gTest = { + createGraph: function(context) { + var merger = context.createChannelMerger(); + + var osc0 = context.createOscillator(); + var osc1 = context.createOscillator(); + + osc0.setPeriodicWave(context. + createPeriodicWave(real, + new Float32Array(real.length))); + osc1.setPeriodicWave(context. + createPeriodicWave(new Float32Array(imag.length), + imag)); + + osc0.frequency.value = realFundamental; + osc1.frequency.value = imagFundamental; + + osc0.start(); + osc1.start(); + + osc0.connect(merger, 0, 0); + osc1.connect(merger, 0, 1); + + return merger; + }, + createExpectedBuffers: function(context) { + var buffer = context.createBuffer(2, testLength, context.sampleRate); + + for (var i = 0; i < buffer.length; ++i) { + + buffer.getChannelData(0)[i] = 1.0 / realPeak * + (real[1] * Math.cos(2 * Math.PI * realFundamental * i / + context.sampleRate) + + real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i / + context.sampleRate)); + + buffer.getChannelData(1)[i] = 1.0 / imagPeak * + imag[3] * Math.sin(2 * Math.PI * 3 * imagFundamental * i / + context.sampleRate); + } + return buffer; + }, +}; + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_periodicWaveBandLimiting.html b/dom/media/webaudio/test/test_periodicWaveBandLimiting.html new file mode 100644 index 000000000..70fbb09e2 --- /dev/null +++ b/dom/media/webaudio/test/test_periodicWaveBandLimiting.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<title>Test effect of band limiting on PeriodicWave signals</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const sampleRate = 48000; +const bufferSize = 12800; +const epsilon = 0.01; + +// "All implementations must support arrays up to at least 8192", but the +// linear interpolation of the current implementation distorts the higher +// frequency components too much to pass this test. +const frequencyIndexMax = 200; + +// A set of oscillators are created near the Nyquist frequency. +// These are factors giving each oscillator frequency relative to the Nyquist. +// The first is an octave below Nyquist and the last is just above. +const OCTAVE_BELOW = 0; +const HALF_BELOW = 1; +const NEAR_BELOW = 2; +const ABOVE = 3; +const oscillatorFactors = [0.5, Math.sqrt(0.5), 0.99, 1.01]; +const oscillatorCount = oscillatorFactors.length; + +// Return magnitude relative to unit sine wave +function magnitude(array) { + var mag = 0 + for (var i = 0; i < array.length; ++i) { + sample = array[i]; + mag += sample * sample; + } + return Math.sqrt(2 * mag / array.length); +} + +function test_frequency_index(frequencyIndex) { + + var context = + new OfflineAudioContext(oscillatorCount, bufferSize, sampleRate); + + var merger = context.createChannelMerger(oscillatorCount); + merger.connect(context.destination); + + var real = new Float32Array(frequencyIndex + 1); + real[frequencyIndex] = 1; + var image = new Float32Array(real.length); + var wave = context.createPeriodicWave(real, image); + + for (var i = 0; i < oscillatorCount; ++i) { + var oscillator = context.createOscillator(); + oscillator.frequency.value = + oscillatorFactors[i] * sampleRate / (2 * frequencyIndex); + oscillator.connect(merger, 0, i); + oscillator.setPeriodicWave(wave); + oscillator.start(0); + } + + return context.startRendering(). + then((buffer) => { + assert_equals(buffer.numberOfChannels, oscillatorCount); + var magnitudes = []; + for (var i = 0; i < oscillatorCount; ++i) { + magnitudes[i] = magnitude(buffer.getChannelData(i)); + } + // Unaffected by band-limiting one octave below Nyquist. + assert_approx_equals(magnitudes[OCTAVE_BELOW], 1, epsilon, + "magnitude with frequency octave below Nyquist"); + // Still at least half the amplitude at half octave below Nyquist. + assert_greater_than(magnitudes[HALF_BELOW], 0.5 * (1 - epsilon), + "magnitude with frequency half octave below Nyquist"); + // Approaching zero or zero near Nyquist. + assert_less_than(magnitudes[NEAR_BELOW], 0.1, + "magnitude with frequency near Nyquist"); + assert_equals(magnitudes[ABOVE], 0, + "magnitude with frequency above Nyquist"); + }); +} + +// The 5/4 ratio with rounding up provides sampling across a range of +// octaves and offsets within octaves. +for (var frequencyIndex = 1; + frequencyIndex < frequencyIndexMax; + frequencyIndex = Math.floor((5 * frequencyIndex + 3) / 4)) { + promise_test(test_frequency_index.bind(null, frequencyIndex), + "Frequency " + frequencyIndex); +} +</script> diff --git a/dom/media/webaudio/test/test_periodicWaveDisableNormalization.html b/dom/media/webaudio/test/test_periodicWaveDisableNormalization.html new file mode 100644 index 000000000..fb924c475 --- /dev/null +++ b/dom/media/webaudio/test/test_periodicWaveDisableNormalization.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test PeriodicWave disableNormalization Parameter</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// We create PerodicWave instances containing two tones and compare it to +// buffers created directly in JavaScript by adding the two waves together. +// Two of the PeriodicWaves are normalized, the other is not. This test is +// a modification of test_periodicWave.html. +// +// These constants are borrowed from test_periodicWave.html and modified +// so that the realPeak (which is the normalization factor) will be small +// enough that the errors are within the bounds for the test. +const realMax = 99; +var real = new Float32Array(realMax + 1); +real[1] = 2.0; // fundamental +real[realMax] = 0.25; + +const realPeak = real[1] + real[realMax]; +const realFundamental = 19.0; + +const testLength = 4096; + +addLoadEvent(function() { + runTest(); +}); + +var gTest = { + createGraph: function(context) { + var merger = context.createChannelMerger(); + + var osc0 = context.createOscillator(); + var osc1 = context.createOscillator(); + var osc2 = context.createOscillator(); + + osc0.setPeriodicWave(context. + createPeriodicWave(real, + new Float32Array(real.length), + {disableNormalization: false})); + osc1.setPeriodicWave(context. + createPeriodicWave(real, + new Float32Array(real.length))); + osc2.setPeriodicWave(context. + createPeriodicWave(real, + new Float32Array(real.length), + {disableNormalization: true})); + + osc0.frequency.value = realFundamental; + osc1.frequency.value = realFundamental; + osc2.frequency.value = realFundamental; + + osc0.start(); + osc1.start(); + osc2.start(); + + osc0.connect(merger, 0, 0); + osc1.connect(merger, 0, 1); + osc2.connect(merger, 0, 2); + + return merger; + }, + createExpectedBuffers: function(context) { + var buffer = context.createBuffer(3, testLength, context.sampleRate); + + for (var i = 0; i < buffer.length; ++i) { + + buffer.getChannelData(0)[i] = 1.0 / realPeak * + (real[1] * Math.cos(2 * Math.PI * realFundamental * i / + context.sampleRate) + + real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i / + context.sampleRate)); + + buffer.getChannelData(1)[i] = buffer.getChannelData(0)[i]; + + // TODO: We need to scale by a factor of two to make the results work + // out here. This seems suspicious, see Bug 1266737. + buffer.getChannelData(2)[i] = 2.0 * + (real[1] * Math.cos(2 * Math.PI * realFundamental * i / + context.sampleRate) + + real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i / + context.sampleRate)); + } + return buffer; + }, + 'numberOfChannels': 3, +}; + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_scriptProcessorNode.html b/dom/media/webaudio/test/test_scriptProcessorNode.html new file mode 100644 index 000000000..7cfb3d96e --- /dev/null +++ b/dom/media/webaudio/test/test_scriptProcessorNode.html @@ -0,0 +1,132 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ScriptProcessorNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// We do not use our generic graph test framework here because +// the testing logic here is sort of complicated, and would +// not be easy to map to OfflineAudioContext, as ScriptProcessorNodes +// can experience delays. + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = null; + + var sourceSP = context.createScriptProcessor(2048); + sourceSP.addEventListener("audioprocess", function(e) { + // generate the audio + for (var i = 0; i < 2048; ++i) { + // Make sure our first sample won't be zero + e.outputBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i + 1) / context.sampleRate); + e.outputBuffer.getChannelData(1)[i] = Math.sin(880 * 2 * Math.PI * (i + 1) / context.sampleRate); + } + // Remember our generated audio + buffer = e.outputBuffer; + + sourceSP.removeEventListener("audioprocess", arguments.callee); + }, false); + + expectException(function() { + context.createScriptProcessor(1); + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + context.createScriptProcessor(2); + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + context.createScriptProcessor(128); + }, DOMException.INDEX_SIZE_ERR); + expectException(function() { + context.createScriptProcessor(255); + }, DOMException.INDEX_SIZE_ERR); + + is(sourceSP.channelCount, 2, "script processor node has 2 input channels by default"); + is(sourceSP.channelCountMode, "explicit", "Correct channelCountMode for the script processor node"); + is(sourceSP.channelInterpretation, "speakers", "Correct channelCountInterpretation for the script processor node"); + + function findFirstNonZeroSample(buffer) { + for (var i = 0; i < buffer.length; ++i) { + if (buffer.getChannelData(0)[i] != 0) { + return i; + } + } + return buffer.length; + } + + var sp = context.createScriptProcessor(2048); + sourceSP.connect(sp); + sp.connect(context.destination); + var lastPlaybackTime = 0; + + var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate); + + function checkAudioProcessingEvent(e) { + is(e.target, sp, "Correct event target"); + ok(e.playbackTime > lastPlaybackTime, "playbackTime correctly set"); + lastPlaybackTime = e.playbackTime; + is(e.inputBuffer.numberOfChannels, 2, "Correct number of channels for the input buffer"); + is(e.inputBuffer.length, 2048, "Correct length for the input buffer"); + is(e.inputBuffer.sampleRate, context.sampleRate, "Correct sample rate for the input buffer"); + is(e.outputBuffer.numberOfChannels, 2, "Correct number of channels for the output buffer"); + is(e.outputBuffer.length, 2048, "Correct length for the output buffer"); + is(e.outputBuffer.sampleRate, context.sampleRate, "Correct sample rate for the output buffer"); + + compareChannels(e.outputBuffer.getChannelData(0), emptyBuffer.getChannelData(0)); + compareChannels(e.outputBuffer.getChannelData(1), emptyBuffer.getChannelData(0)); + } + + sp.onaudioprocess = function(e) { + isnot(buffer, null, "The audioprocess handler for sourceSP must be run at this point"); + checkAudioProcessingEvent(e); + + // Because of the initial latency added by the second script processor node, + // we will never see any generated audio frames in the first callback. + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0)); + compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0)); + + sp.onaudioprocess = function(e) { + checkAudioProcessingEvent(e); + + var firstNonZero = findFirstNonZeroSample(e.inputBuffer); + ok(firstNonZero <= 2048, "First non-zero sample within range"); + + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), firstNonZero); + compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), firstNonZero); + compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), 2048 - firstNonZero, firstNonZero, 0); + compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), 2048 - firstNonZero, firstNonZero, 0); + + if (firstNonZero == 0) { + // If we did not experience any delays, the test is done! + sp.onaudioprocess = null; + + SimpleTest.finish(); + } else if (firstNonZero != 2048) { + // In case we just saw a zero buffer this time, wait one more round + sp.onaudioprocess = function(e) { + checkAudioProcessingEvent(e); + + compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), firstNonZero, 0, 2048 - firstNonZero); + compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), firstNonZero, 0, 2048 - firstNonZero); + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), undefined, firstNonZero); + compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), undefined, firstNonZero); + + sp.onaudioprocess = null; + + SimpleTest.finish(); + }; + } + }; + }; +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html b/dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html new file mode 100644 index 000000000..6361a1747 --- /dev/null +++ b/dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// We do not use our generic graph test framework here because +// the testing logic here is sort of complicated, and would +// not be easy to map to OfflineAudioContext, as ScriptProcessorNodes +// can experience delays. + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(6, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + for (var j = 0; j < 6; ++j) { + buffer.getChannelData(0)[i] = Math.sin(440 * j * Math.PI * i / context.sampleRate); + } + } + + var monoBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + monoBuffer.getChannelData(0)[i] = 1; + } + + var source = context.createBufferSource(); + + var sp = context.createScriptProcessor(2048, 3); + expectException(function() { sp.channelCount = 2; }, + DOMException.NOT_SUPPORTED_ERR); + sp.channelCountMode = "explicit"; + expectException(function() { sp.channelCountMode = "max"; }, + DOMException.NOT_SUPPORTED_ERR); + expectException(function() { sp.channelCountMode = "clamped-max"; }, + DOMException.NOT_SUPPORTED_ERR); + sp.channelInterpretation = "discrete"; + source.start(0); + source.buffer = buffer; + source.connect(sp); + sp.connect(context.destination); + + var monoSource = context.createBufferSource(); + monoSource.buffer = monoBuffer; + monoSource.connect(sp); + monoSource.start(2048 / context.sampleRate); + + sp.onaudioprocess = function(e) { + is(e.inputBuffer.numberOfChannels, 3, "Should be correctly down-mixed to three channels"); + for (var i = 0; i < 3; ++i) { + compareChannels(e.inputBuffer.getChannelData(i), buffer.getChannelData(i)); + } + + // On the next iteration, we'll get a silence buffer + sp.onaudioprocess = function(e) { + var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate); + is(e.inputBuffer.numberOfChannels, 3, "Should be correctly up-mixed to three channels"); + compareChannels(e.inputBuffer.getChannelData(0), monoBuffer.getChannelData(0)); + for (var i = 1; i < 3; ++i) { + compareChannels(e.inputBuffer.getChannelData(i), emptyBuffer.getChannelData(0)); + } + + sp.onaudioprocess = null; + sp.disconnect(context.destination); + + SimpleTest.finish(); + }; + }; +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html b/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html new file mode 100644 index 000000000..a3c073e38 --- /dev/null +++ b/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode: should not fire audioprocess if not connected.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("This test needs to wait a while to ensure that a given event does not happen."); +addLoadEvent(function() { + var context = new AudioContext(); + + var sp = context.createScriptProcessor(2048, 2, 2); + sp.onaudioprocess = function(e) { + ok(false, "Should not call onaudioprocess if the node is not connected."); + sp.onaudioprocess = null; + SimpleTest.finish(); + }; + setTimeout(function() { + console.log(sp.onaudioprocess); + if (sp.onaudioprocess) { + ok(true, "onaudioprocess not fired."); + SimpleTest.finish(); + } + }, 4000); +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html b/dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html new file mode 100644 index 000000000..8352a331d --- /dev/null +++ b/dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ScriptProcessorNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// We do not use our generic graph test framework here because +// the testing logic here is sort of complicated, and would +// not be easy to map to OfflineAudioContext, as ScriptProcessorNodes +// can experience delays. + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = null; + + var sourceSP = context.createScriptProcessor(2048); + sourceSP.addEventListener("audioprocess", function(e) { + // generate the audio + for (var i = 0; i < 2048; ++i) { + // Make sure our first sample won't be zero + e.outputBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i + 1) / context.sampleRate); + e.outputBuffer.getChannelData(1)[i] = Math.sin(880 * 2 * Math.PI * (i + 1) / context.sampleRate); + } + // Remember our generated audio + buffer = e.outputBuffer; + + sourceSP.removeEventListener("audioprocess", arguments.callee); + }, false); + + function findFirstNonZeroSample(buffer) { + for (var i = 0; i < buffer.length; ++i) { + if (buffer.getChannelData(0)[i] != 0) { + return i; + } + } + return buffer.length; + } + + var sp = context.createScriptProcessor(2048); + sourceSP.connect(sp); + + var spWrapped = SpecialPowers.wrap(sp); + ok("passThrough" in spWrapped, "ScriptProcessorNode should support the passThrough API"); + spWrapped.passThrough = true; + + sp.onaudioprocess = function() { + ok(false, "The audioprocess event must never be dispatched on the passthrough ScriptProcessorNode"); + }; + + var sp2 = context.createScriptProcessor(2048); + sp.connect(sp2); + sp2.connect(context.destination); + + var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate); + + sp2.onaudioprocess = function(e) { + // Because of the initial latency added by the second script processor node, + // we will never see any generated audio frames in the first callback. + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0)); + compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0)); + + sp2.onaudioprocess = function(e) { + var firstNonZero = findFirstNonZeroSample(e.inputBuffer); + ok(firstNonZero <= 2048, "First non-zero sample within range"); + + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), firstNonZero); + compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), firstNonZero); + compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), 2048 - firstNonZero, firstNonZero, 0); + compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), 2048 - firstNonZero, firstNonZero, 0); + + if (firstNonZero == 0) { + // If we did not experience any delays, the test is done! + sp2.onaudioprocess = null; + + SimpleTest.finish(); + } else if (firstNonZero != 2048) { + // In case we just saw a zero buffer this time, wait one more round + sp2.onaudioprocess = function(e) { + compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), firstNonZero, 0, 2048 - firstNonZero); + compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), firstNonZero, 0, 2048 - firstNonZero); + compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), undefined, firstNonZero); + compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), undefined, firstNonZero); + + sp2.onaudioprocess = null; + + SimpleTest.finish(); + }; + } + }; + }; +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html b/dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html new file mode 100644 index 000000000..6ac8beda0 --- /dev/null +++ b/dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test AudioBufferSourceNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + + var sp = context.createScriptProcessor(2048, 0, 2); + sp.onaudioprocess = function(e) { + is(e.inputBuffer.numberOfChannels, 0, "Should have 0 input channels"); + is(e.outputBuffer.numberOfChannels, 2, "Should have 2 output channels"); + sp.onaudioprocess = null; + + sp = context.createScriptProcessor(2048, 2, 0); + sp.onaudioprocess = function(e) { + is(e.inputBuffer.numberOfChannels, 2, "Should have 2 input channels"); + is(e.outputBuffer.numberOfChannels, 0, "Should have 0 output channels"); + sp.onaudioprocess = null; + + SimpleTest.finish(); + }; + sp.connect(context.destination); + }; + sp.connect(context.destination); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html b/dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html new file mode 100644 index 000000000..43cd13912 --- /dev/null +++ b/dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test ScriptProcessorNode playbackTime for bug 970773</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var context = new AudioContext(); +const delay = 0.1; + +function doTest() { + const processorBufferLength = 256; + // |currentTime| may include double precision floating point + // rounding errors, so round to nearest integer sample to ignore these. + var minimumPlaybackSample = + Math.round(context.currentTime * context.sampleRate) + + processorBufferLength; + var sp = context.createScriptProcessor(processorBufferLength); + sp.connect(context.destination); + sp.onaudioprocess = + function(e) { + is(e.inputBuffer.length, processorBufferLength, + "expected buffer length"); + var playbackSample = Math.round(e.playbackTime * context.sampleRate) + ok(playbackSample >= minimumPlaybackSample, + "playbackSample " + playbackSample + + " beyond expected minimum " + minimumPlaybackSample); + sp.onaudioprocess = null; + SimpleTest.finish(); + }; +} + +// Wait until AudioDestinationNode has accumulated enough 'extra' time so that +// a failure would be easily detected. +(function waitForExtraTime() { + if (context.currentTime < delay) { + SimpleTest.executeSoon(waitForExtraTime); + } else { + doTest(); + } +})(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html b/dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html new file mode 100644 index 000000000..5c03a8a91 --- /dev/null +++ b/dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<title>Test seamless playback of a series of resampled buffers</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Permitting some accumulation of rounding to int16_t. +// 64/2^15 would be only just small enough to detect off-by-one-subsample +// scheduling errors with the frequencies here. +const EPSILON = 4.0 / Math.pow(2, 15); +// Offsets test for rounding to nearest rather than up or down. +const OFFSETS = [EPSILON, 1.0 - EPSILON]; +// The ratio of resampling is 147:160, so 256 start points is enough to cover +// every fractional offset. +const LENGTH = 256; + +function do_test(context_rate, buffer_rate, start_offset) { + + var context = + new OfflineAudioContext(2, LENGTH, context_rate); + + var merger = context.createChannelMerger(context.destination.channelCount); + merger.connect(context.destination); + + // Create an audio signal that will be repeated + var repeating_signal = context.createBuffer(1, 1, buffer_rate); + repeating_signal.getChannelData(0)[0] = 0.5; + + // Schedule a series of nodes to repeat the signal. + for (var i = 0; i < LENGTH; ++i) { + var source = context.createBufferSource(); + source.buffer = repeating_signal; + source.connect(merger, 0, 0); + source.start((i + start_offset) / buffer_rate); + } + + // A single long signal should produce the same result. + var long_signal = context.createBuffer(1, LENGTH, buffer_rate); + var c = long_signal.getChannelData(0); + for (var i = 0; i < c.length; ++i) { + c[i] = 0.5; + } + + var source = context.createBufferSource(); + source.buffer = long_signal; + source.connect(merger, 0, 1); + source.start(start_offset / buffer_rate); + + return context.startRendering(). + then((buffer) => { + series_output = buffer.getChannelData(0); + expected = buffer.getChannelData(1); + + for (var i = 0; i < buffer.length; ++i) { + assert_approx_equals(series_output[i], expected[i], EPSILON, + "series output at " + i); + } + }); +} + +function start_tests(context_rate, buffer_rate) { + OFFSETS.forEach((start_offset) => { + promise_test(() => do_test(context_rate, buffer_rate, start_offset), + "" + context_rate + " context, " + + buffer_rate + " buffer, " + + start_offset + " start"); + }); +} + +start_tests(48000, 44100); +start_tests(44100, 48000); + +</script> diff --git a/dom/media/webaudio/test/test_singleSourceDest.html b/dom/media/webaudio/test/test_singleSourceDest.html new file mode 100644 index 000000000..8613a2dd9 --- /dev/null +++ b/dom/media/webaudio/test/test_singleSourceDest.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test whether we can create an AudioContext interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + var context = new AudioContext(); + var buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + var destination = context.destination; + is(destination.context, context, "Destination node has proper context"); + is(destination.context, context, "Destination node has proper context"); + is(destination.numberOfInputs, 1, "Destination node has 1 inputs"); + is(destination.numberOfOutputs, 0, "Destination node has 0 outputs"); + is(destination.channelCount, 2, "Destination node has 2 input channels by default"); + is(destination.channelCountMode, "explicit", "Correct channelCountMode for the destination node"); + is(destination.channelInterpretation, "speakers", "Correct channelCountInterpretation for the destination node"); + ok(destination instanceof EventTarget, "AudioNodes must be EventTargets"); + + var source = context.createBufferSource(); + is(source.context, context, "Source node has proper context"); + is(source.numberOfInputs, 0, "Source node has 0 inputs"); + is(source.numberOfOutputs, 1, "Source node has 1 outputs"); + is(source.loop, false, "Source node is not looping"); + is(source.loopStart, 0, "Correct default value for loopStart"); + is(source.loopEnd, 0, "Correct default value for loopEnd"); + ok(!source.buffer, "Source node should not have a buffer when it's created"); + is(source.channelCount, 2, "source node has 2 input channels by default"); + is(source.channelCountMode, "max", "Correct channelCountMode for the source node"); + is(source.channelInterpretation, "speakers", "Correct channelCountInterpretation for the source node"); + + expectException(function() { + source.channelCount = 0; + }, DOMException.NOT_SUPPORTED_ERR); + + source.buffer = buffer; + ok(source.buffer, "Source node should have a buffer now"); + + source.connect(destination); + + is(source.numberOfInputs, 0, "Source node has 0 inputs"); + is(source.numberOfOutputs, 1, "Source node has 0 outputs"); + is(destination.numberOfInputs, 1, "Destination node has 0 inputs"); + is(destination.numberOfOutputs, 0, "Destination node has 0 outputs"); + + source.start(0); + SimpleTest.executeSoon(function() { + source.stop(0); + source.disconnect(); + + SpecialPowers.clearUserPref("media.webaudio.enabled"); + SimpleTest.finish(); + }); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_stereoPannerNode.html b/dom/media/webaudio/test/test_stereoPannerNode.html new file mode 100644 index 000000000..ffc735364 --- /dev/null +++ b/dom/media/webaudio/test/test_stereoPannerNode.html @@ -0,0 +1,263 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test StereoPannerNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var SR = 44100; +var BUF_SIZE = 128; +var PANNING = 0.1; +var GAIN = 0.5; + +// Cheap reimplementation of some bits of the spec +function gainForPanningMonoToStereo(panning) { + panning += 1; + panning /= 2; + return [ Math.cos(0.5 * Math.PI * panning), + Math.sin(0.5 * Math.PI * panning) ]; +} + +function gainForPanningStereoToStereo(panning) { + if (panning <= 0) { + panning += 1.; + } + return [ Math.cos(0.5 * Math.PI * panning), + Math.sin(0.5 * Math.PI * panning) ]; +} + +function applyStereoToStereoPanning(l, r, panningValues, panning) { + var outL, outR; + if (panning <= 0) { + outL = l + r * panningValues[0]; + outR = r * panningValues[1]; + } else { + outL = l * panningValues[0]; + outR = r + l * panningValues[1]; + } + return [outL,outR]; +} + +function applyMonoToStereoPanning(c, panning) { + return [c * panning[0], c * panning[1]]; +} + +// Test the DOM interface +var context = new OfflineAudioContext(1, 1, SR); +var stereoPanner = context.createStereoPanner(); +ok(stereoPanner.pan, "The AudioParam member must exist"); +is(stereoPanner.pan.value, 0.0, "Correct initial value"); +is(stereoPanner.pan.defaultValue, 0.0, "Correct default value"); +is(stereoPanner.channelCount, 2, "StereoPannerNode node has 2 input channels by default"); +is(stereoPanner.channelCountMode, "clamped-max", "Correct channelCountMode for the StereoPannerNode"); +is(stereoPanner.channelInterpretation, "speakers", "Correct channelCountInterpretation for the StereoPannerNode"); +expectException(function() { + stereoPanner.channelCount = 3; +}, DOMException.NOT_SUPPORTED_ERR); +expectException(function() { + stereoPanner.channelCountMode = "max"; +}, DOMException.NOT_SUPPORTED_ERR); + +// A sine to be used to fill the buffers +function sine(t) { + return Math.sin(440 * 2 * Math.PI * t / context.sampleRate); +} + +// A couple mono and stereo buffers: the StereoPannerNode equation is different +// if the input is mono or stereo +var stereoBuffer = context.createBuffer(2, BUF_SIZE, context.sampleRate); +var monoBuffer = context.createBuffer(1, BUF_SIZE, context.sampleRate); +for (var i = 0; i < BUF_SIZE; ++i) { + monoBuffer.getChannelData(0)[i] = + stereoBuffer.getChannelData(0)[i] = + stereoBuffer.getChannelData(1)[i] = sine(i); +} + +// Expected test vectors +function expectedBufferNoop(gain) { + gain = gain || 1.0; + var expectedBuffer = context.createBuffer(2, BUF_SIZE, SR); + for (var i = 0; i < BUF_SIZE; i++) { + expectedBuffer.getChannelData(0)[i] = gain * sine(i); + expectedBuffer.getChannelData(1)[i] = gain * sine(i); + } + return expectedBuffer; +} + +function expectedBufferForStereo(gain) { + gain = gain || 1.0; + var expectedBuffer = context.createBuffer(2, BUF_SIZE, SR); + var gainPanning = gainForPanningStereoToStereo(PANNING); + gainPanning[0] *= gain; + gainPanning[1] *= gain; + for (var i = 0; i < BUF_SIZE; i++) { + var values = [ sine(i), sine(i) ]; + var processed = applyStereoToStereoPanning(values[0], values[1], gainPanning, PANNING); + expectedBuffer.getChannelData(0)[i] = processed[0]; + expectedBuffer.getChannelData(1)[i] = processed[1]; + } + return expectedBuffer; +} + +function expectedBufferForMono(gain) { + gain = gain || 1.0; + var expectedBuffer = context.createBuffer(2, BUF_SIZE, SR); + var gainPanning = gainForPanningMonoToStereo(PANNING); + gainPanning[0] *= gain; + gainPanning[1] *= gain; + for (var i = 0; i < BUF_SIZE; i++) { + var value = sine(i); + var processed = applyMonoToStereoPanning(value, gainPanning); + expectedBuffer.getChannelData(0)[i] = processed[0]; + expectedBuffer.getChannelData(1)[i] = processed[1]; + } + return expectedBuffer; +} + +// Actual test cases +var tests = [ + function monoPanningNoop(ctx, panner) { + var monoSource = ctx.createBufferSource(); + monoSource.connect(panner); + monoSource.buffer = monoBuffer; + monoSource.start(0); + return expectedBufferNoop(); + }, + function stereoPanningNoop(ctx, panner) { + var stereoSource = ctx.createBufferSource(); + stereoSource.connect(panner); + stereoSource.buffer = stereoBuffer; + stereoSource.start(0); + return expectedBufferNoop(); + }, + function monoPanningNoopWithGain(ctx, panner) { + var monoSource = ctx.createBufferSource(); + var gain = ctx.createGain(); + gain.gain.value = GAIN; + monoSource.connect(gain); + gain.connect(panner); + monoSource.buffer = monoBuffer; + monoSource.start(0); + return expectedBufferNoop(GAIN); + }, + function stereoPanningNoopWithGain(ctx, panner) { + var stereoSource = ctx.createBufferSource(); + var gain = ctx.createGain(); + gain.gain.value = GAIN; + stereoSource.connect(gain); + gain.connect(panner); + stereoSource.buffer = stereoBuffer; + stereoSource.start(0); + return expectedBufferNoop(GAIN); + }, + function stereoPanningAutomation(ctx, panner) { + var stereoSource = ctx.createBufferSource(); + stereoSource.connect(panner); + stereoSource.buffer = stereoBuffer; + panner.pan.setValueAtTime(0.1, 0.0); + stereoSource.start(0); + return expectedBufferForStereo(); + }, + function stereoPanning(ctx, panner) { + var stereoSource = ctx.createBufferSource(); + stereoSource.buffer = stereoBuffer; + stereoSource.connect(panner); + panner.pan.value = 0.1; + stereoSource.start(0); + return expectedBufferForStereo(); + }, + function monoPanningAutomation(ctx, panner) { + var monoSource = ctx.createBufferSource(); + monoSource.connect(panner); + monoSource.buffer = monoBuffer; + panner.pan.setValueAtTime(PANNING, 0.0); + monoSource.start(0); + return expectedBufferForMono(); + }, + function monoPanning(ctx, panner) { + var monoSource = ctx.createBufferSource(); + monoSource.connect(panner); + monoSource.buffer = monoBuffer; + panner.pan.value = 0.1; + monoSource.start(0); + return expectedBufferForMono(); + }, + function monoPanningWithGain(ctx, panner) { + var monoSource = ctx.createBufferSource(); + var gain = ctx.createGain(); + gain.gain.value = GAIN; + monoSource.connect(gain); + gain.connect(panner); + monoSource.buffer = monoBuffer; + panner.pan.value = 0.1; + monoSource.start(0); + return expectedBufferForMono(GAIN); + }, + function stereoPanningWithGain(ctx, panner) { + var stereoSource = ctx.createBufferSource(); + var gain = ctx.createGain(); + gain.gain.value = GAIN; + stereoSource.connect(gain); + gain.connect(panner); + stereoSource.buffer = stereoBuffer; + panner.pan.value = 0.1; + stereoSource.start(0); + return expectedBufferForStereo(GAIN); + }, + function monoPanningWithGainAndAutomation(ctx, panner) { + var monoSource = ctx.createBufferSource(); + var gain = ctx.createGain(); + gain.gain.value = GAIN; + monoSource.connect(gain); + gain.connect(panner); + monoSource.buffer = monoBuffer; + panner.pan.setValueAtTime(PANNING, 0); + monoSource.start(0); + return expectedBufferForMono(GAIN); + }, + function stereoPanningWithGainAndAutomation(ctx, panner) { + var stereoSource = ctx.createBufferSource(); + var gain = ctx.createGain(); + gain.gain.value = GAIN; + stereoSource.connect(gain); + gain.connect(panner); + stereoSource.buffer = stereoBuffer; + panner.pan.setValueAtTime(PANNING, 0); + stereoSource.start(0); + return expectedBufferForStereo(GAIN); + } +]; + +var finished = 0; +function finish() { + if (++finished == tests.length) { + SimpleTest.finish(); + } +} + +tests.forEach(function(f) { + var ac = new OfflineAudioContext(2, BUF_SIZE, SR); + var panner = ac.createStereoPanner(); + panner.connect(ac.destination); + var expected = f(ac, panner); + ac.oncomplete = function(e) { + info(f.name); + compareBuffers(e.renderedBuffer, expected); + finish(); + }; + ac.startRendering() +}); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<pre id=dump> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_stereoPannerNodePassThrough.html b/dom/media/webaudio/test/test_stereoPannerNodePassThrough.html new file mode 100644 index 000000000..250a1a9de --- /dev/null +++ b/dom/media/webaudio/test/test_stereoPannerNodePassThrough.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test StereoPanerNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + + var stereoPanner = context.createStereoPanner(); + + source.buffer = this.buffer; + + source.connect(stereoPanner); + + var stereoPannerWrapped = SpecialPowers.wrap(stereoPanner); + ok("passThrough" in stereoPannerWrapped, "StereoPannerNode should support the passThrough API"); + stereoPannerWrapped.passThrough = true; + + source.start(0); + return stereoPanner; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_stereoPanningWithGain.html b/dom/media/webaudio/test/test_stereoPanningWithGain.html new file mode 100644 index 000000000..1ef0c037d --- /dev/null +++ b/dom/media/webaudio/test/test_stereoPanningWithGain.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test stereo equalpower panning with a GainNode</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script src="webaudio.js" type="text/javascript"></script> +<script class="testbody" type="text/javascript"> + +const size = 256; + +var gTest = { + numberOfChannels: 2, + createGraph: function(context) { + var panner = context.createPanner(); + panner.setPosition(1.0, 0.0, 0.0); // reference distance the right + panner.panningModel = "equalpower"; + + var gain = context.createGain(); + gain.gain.value = -0.5; + gain.connect(panner); + + var buffer = context.createBuffer(2, 2, context.sampleRate); + buffer.getChannelData(0)[0] = 1.0; + buffer.getChannelData(1)[1] = 1.0; + var source = context.createBufferSource(); + source.buffer = buffer; + source.connect(gain); + source.start(0); + + return panner; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(2, size, context.sampleRate); + expectedBuffer.getChannelData(1)[0] = -0.5; + expectedBuffer.getChannelData(1)[1] = -0.5; + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_waveDecoder.html b/dom/media/webaudio/test/test_waveDecoder.html new file mode 100644 index 000000000..bd5faf6ad --- /dev/null +++ b/dom/media/webaudio/test/test_waveDecoder.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<meta charset=utf-8> +<head> + <title>Test that we decode uint8 and sint16 wave files with correct conversion to float64</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +var testsDone = 0; +var tests = ["UklGRjUrAABXQVZFZm10IBAAAAABAAEAESsAABErAAABAAgAZGF0YQMAAAD/AIA=", + "UklGRkZWAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YQYAAAD/fwCAAAA="]; + +SimpleTest.waitForExplicitFinish(); + +function base64ToUint8Buffer(b64) { + var str = atob(b64) + var u8 = new Uint8Array(str.length); + for (var i = 0; i < str.length; ++i) { + u8[i] = str.charCodeAt(i); + } + return u8; +} + +function fixupBufferSampleRate(u8, rate) { + u8[24] = (rate & 0x000000ff) >> 0; + u8[25] = (rate & 0x0000ff00) >> 8; + u8[26] = (rate & 0x00ff0000) >> 16; + u8[27] = (rate & 0xff000000) >> 24; +} + +function finishTest() { + testsDone += 1; + if (testsDone == tests.length) { + SimpleTest.finish(); + } +} + +function decodeComplete(b) { + ok(true, "Decoding succeeded."); + is(b.numberOfChannels, 1, "Should have 1 channel."); + is(b.length, 3, "Should have three samples."); + var samples = b.getChannelData(0); + ok(samples[0] > 0.99 && samples[0] < 1.01, "Check near 1.0. Got " + samples[0]); + ok(samples[1] > -1.01 && samples[1] < -0.99, "Check near -1.0. Got " + samples[1]); + ok(samples[2] > -0.01 && samples[2] < 0.01, "Check near 0.0. Got " + samples[2]); + finishTest(); +} + +function decodeFailed() { + ok(false, "Decoding failed."); + finishTest(); +} + +addLoadEvent(function() { + var context = new AudioContext(); + + for (var i = 0; i < tests.length; ++i) { + var u8 = base64ToUint8Buffer(tests[i]); + fixupBufferSampleRate(u8, context.sampleRate); + context.decodeAudioData(u8.buffer, decodeComplete, decodeFailed); + } +}); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_waveShaper.html b/dom/media/webaudio/test/test_waveShaper.html new file mode 100644 index 000000000..c95cf5e05 --- /dev/null +++ b/dom/media/webaudio/test/test_waveShaper.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test WaveShaperNode with no curve</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + source.buffer = this.buffer; + + var shaper = context.createWaveShaper(); + shaper.curve = this.curve; + + source.connect(shaper); + + source.start(0); + return shaper; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 4096, context.sampleRate); + for (var i = 1; i < 4095; ++i) { + this.buffer.getChannelData(0)[i] = 2 * (i / 4096) - 1; + } + // Two out of range values + this.buffer.getChannelData(0)[0] = -2; + this.buffer.getChannelData(0)[4095] = 2; + + this.curve = new Float32Array(2048); + for (var i = 0; i < 2048; ++i) { + this.curve[i] = Math.sin(100 * Math.PI * (i + 1) / context.sampleRate); + } + + var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate); + for (var i = 1; i < 4095; ++i) { + var input = this.buffer.getChannelData(0)[i]; + var index = Math.floor(this.curve.length * (input + 1) / 2); + index = Math.max(0, Math.min(this.curve.length - 1, index)); + expectedBuffer.getChannelData(0)[i] = this.curve[index]; + } + expectedBuffer.getChannelData(0)[0] = this.curve[0]; + expectedBuffer.getChannelData(0)[4095] = this.curve[2047]; + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_waveShaperGain.html b/dom/media/webaudio/test/test_waveShaperGain.html new file mode 100644 index 000000000..b0c82d2d8 --- /dev/null +++ b/dom/media/webaudio/test/test_waveShaperGain.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> + <title>Test that WaveShaperNode doesn't corrupt its inputs when the gain is != + 1.0 (bug 1203616)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +var samplerate = 44100; +var context = new OfflineAudioContext(1, 44100, samplerate); + +var dc = context.createBufferSource(); + +var buffer = context.createBuffer(1, 1, samplerate); +buffer.getChannelData(0)[0] = 1.0; +dc.buffer = buffer; + +var gain = context.createGain(); +var ws2 = context.createWaveShaper(); +var ws = []; + +// No-op waveshaper curves. +for (var i = 0; i < 2; i++) { + ws[i] = context.createWaveShaper(); + var curve = new Float32Array(2); + curve[0] = -1.0; + curve[1] = 1.0; + ws[i].curve = curve; + ws[i].connect(context.destination); + gain.connect(ws[i]); +} + +dc.connect(gain); +dc.start(); + +gain.gain.value = 0.5; + +context.startRendering().then(buffer => { + document.querySelector("pre").innerHTML = buffer.getChannelData(0)[0]; + ok(buffer.getChannelData(0)[0] == 1.0, "Volume was handled properly"); + + context = new OfflineAudioContext(1, 100, samplerate); + var oscillator = context.createOscillator(); + var gain = context.createGain(); + var waveShaper = context.createWaveShaper(); + + oscillator.start(0); + oscillator.connect(gain); + + // to silence + gain.gain.value = 0; + gain.connect(waveShaper); + + // convert all signal into 1.0. The non unity values are to detect the use + // of uninitialized buffers (see Bug 1283910). + waveShaper.curve = new Float32Array([ 0.5, 0.5, 0.5, 0.5, 0.5, 1, 1, 0.5, 0.5, 0.5, 0.5, 0.5 ]); + waveShaper.connect(context.destination); + + context.startRendering().then((buffer) => { + var result = buffer.getChannelData(0); + ok(result.every(x => x === 1), "WaveShaper handles zero gain properly"); + SimpleTest.finish(); + }); +}); +</script> +<pre> +</pre> +</body> + diff --git a/dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html b/dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html new file mode 100644 index 000000000..f117f0376 --- /dev/null +++ b/dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test WaveShaperNode with an invalid curve</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + source.buffer = this.buffer; + + var shaper = context.createWaveShaper(); + + expectException(() => { + shaper.curve = new Float32Array(0); + }, DOMException.INVALID_STATE_ERR); + + is(shaper.curve, null, "The curve mustn't have been set"); + + expectException(() => { + shaper.curve = new Float32Array(1); + }, DOMException.INVALID_STATE_ERR); + + is(shaper.curve, null, "The curve mustn't have been set"); + + expectNoException(() => { + shaper.curve = new Float32Array(2); + }); + + isnot(shaper.curve, null, "The curve must have been set"); + + expectNoException(() => { + shaper.curve = null; + }); + + is(shaper.curve, null, "The curve must be null by default"); + + source.connect(shaper); + + source.start(0); + return shaper; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + this.buffer = expectedBuffer; + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_waveShaperNoCurve.html b/dom/media/webaudio/test/test_waveShaperNoCurve.html new file mode 100644 index 000000000..c0d3187b2 --- /dev/null +++ b/dom/media/webaudio/test/test_waveShaperNoCurve.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test WaveShaperNode with no curve</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 2048, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + source.buffer = this.buffer; + + var shaper = context.createWaveShaper(); + is(shaper.curve, null, "The shaper curve must be null by default"); + + source.connect(shaper); + + source.start(0); + return shaper; + }, + createExpectedBuffers: function(context) { + var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate); + for (var i = 0; i < 2048; ++i) { + expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate); + } + this.buffer = expectedBuffer; + return expectedBuffer; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/test_waveShaperPassThrough.html b/dom/media/webaudio/test/test_waveShaperPassThrough.html new file mode 100644 index 000000000..52c70d3c2 --- /dev/null +++ b/dom/media/webaudio/test/test_waveShaperPassThrough.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test WaveShaperNode with passthrough</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="webaudio.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var gTest = { + length: 4096, + numberOfChannels: 1, + createGraph: function(context) { + var source = context.createBufferSource(); + source.buffer = this.buffer; + + var shaper = context.createWaveShaper(); + shaper.curve = this.curve; + + var shaperWrapped = SpecialPowers.wrap(shaper); + ok("passThrough" in shaperWrapped, "WaveShaperNode should support the passThrough API"); + shaperWrapped.passThrough = true; + + source.connect(shaper); + + source.start(0); + return shaper; + }, + createExpectedBuffers: function(context) { + this.buffer = context.createBuffer(1, 4096, context.sampleRate); + for (var i = 1; i < 4095; ++i) { + this.buffer.getChannelData(0)[i] = 2 * (i / 4096) - 1; + } + // Two out of range values + this.buffer.getChannelData(0)[0] = -2; + this.buffer.getChannelData(0)[4095] = 2; + + this.curve = new Float32Array(2048); + for (var i = 0; i < 2048; ++i) { + this.curve[i] = Math.sin(100 * Math.PI * (i + 1) / context.sampleRate); + } + + return [this.buffer]; + }, +}; + +runTest(); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webaudio/test/ting-44.1k-1ch.ogg b/dom/media/webaudio/test/ting-44.1k-1ch.ogg Binary files differnew file mode 100644 index 000000000..a11aaf1cb --- /dev/null +++ b/dom/media/webaudio/test/ting-44.1k-1ch.ogg diff --git a/dom/media/webaudio/test/ting-44.1k-1ch.wav b/dom/media/webaudio/test/ting-44.1k-1ch.wav Binary files differnew file mode 100644 index 000000000..6854c9d89 --- /dev/null +++ b/dom/media/webaudio/test/ting-44.1k-1ch.wav diff --git a/dom/media/webaudio/test/ting-44.1k-2ch.ogg b/dom/media/webaudio/test/ting-44.1k-2ch.ogg Binary files differnew file mode 100644 index 000000000..94e001485 --- /dev/null +++ b/dom/media/webaudio/test/ting-44.1k-2ch.ogg diff --git a/dom/media/webaudio/test/ting-44.1k-2ch.wav b/dom/media/webaudio/test/ting-44.1k-2ch.wav Binary files differnew file mode 100644 index 000000000..703d88589 --- /dev/null +++ b/dom/media/webaudio/test/ting-44.1k-2ch.wav diff --git a/dom/media/webaudio/test/ting-48k-1ch.ogg b/dom/media/webaudio/test/ting-48k-1ch.ogg Binary files differnew file mode 100644 index 000000000..f45ce33a5 --- /dev/null +++ b/dom/media/webaudio/test/ting-48k-1ch.ogg diff --git a/dom/media/webaudio/test/ting-48k-1ch.wav b/dom/media/webaudio/test/ting-48k-1ch.wav Binary files differnew file mode 100644 index 000000000..8fe471666 --- /dev/null +++ b/dom/media/webaudio/test/ting-48k-1ch.wav diff --git a/dom/media/webaudio/test/ting-48k-2ch.ogg b/dom/media/webaudio/test/ting-48k-2ch.ogg Binary files differnew file mode 100644 index 000000000..e4c564abb --- /dev/null +++ b/dom/media/webaudio/test/ting-48k-2ch.ogg diff --git a/dom/media/webaudio/test/ting-48k-2ch.wav b/dom/media/webaudio/test/ting-48k-2ch.wav Binary files differnew file mode 100644 index 000000000..ad4d0466d --- /dev/null +++ b/dom/media/webaudio/test/ting-48k-2ch.wav diff --git a/dom/media/webaudio/test/ting-dualchannel44.1.wav b/dom/media/webaudio/test/ting-dualchannel44.1.wav Binary files differnew file mode 100644 index 000000000..62954394d --- /dev/null +++ b/dom/media/webaudio/test/ting-dualchannel44.1.wav diff --git a/dom/media/webaudio/test/ting-dualchannel48.wav b/dom/media/webaudio/test/ting-dualchannel48.wav Binary files differnew file mode 100644 index 000000000..a0b824788 --- /dev/null +++ b/dom/media/webaudio/test/ting-dualchannel48.wav diff --git a/dom/media/webaudio/test/webaudio.js b/dom/media/webaudio/test/webaudio.js new file mode 100644 index 000000000..1a1a8efb7 --- /dev/null +++ b/dom/media/webaudio/test/webaudio.js @@ -0,0 +1,269 @@ +// Helpers for Web Audio tests + +function expectException(func, exceptionCode) { + var threw = false; + try { + func(); + } catch (ex) { + threw = true; + ok(ex instanceof DOMException, "Expect a DOM exception"); + is(ex.code, exceptionCode, "Expect the correct exception code"); + } + ok(threw, "The exception was thrown"); +} + +function expectNoException(func) { + var threw = false; + try { + func(); + } catch (ex) { + threw = true; + } + ok(!threw, "An exception was not thrown"); +} + +function expectTypeError(func) { + var threw = false; + try { + func(); + } catch (ex) { + threw = true; + ok(ex instanceof TypeError, "Expect a TypeError"); + } + ok(threw, "The exception was thrown"); +} + +function expectRejectedPromise(that, func, exceptionName) { + var promise = that[func](); + + ok(promise instanceof Promise, "Expect a Promise"); + + promise.then(function(res) { + ok(false, "Promise resolved when it should have been rejected."); + }).catch(function(err) { + is(err.name, exceptionName, "Promise correctly reject with " + exceptionName); + }); +} + +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) { + is(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; + } + } + }; + + is(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) { + is(got.numberOfChannels, expected.numberOfChannels, + "Correct number of buffer channels"); + return; + } + if (got.length != expected.length) { + is(got.length, expected.length, + "Correct buffer length"); + return; + } + if (got.sampleRate != expected.sampleRate) { + is(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); + } +} + +/** + * Compute the root mean square (RMS, + * <http://en.wikipedia.org/wiki/Root_mean_square>) of a channel of a slice + * (defined by `start` and `end`) of an AudioBuffer. + * + * This is useful to detect that a buffer is noisy or silent. + */ +function rms(audiobuffer, channel = 0, start = 0, end = audiobuffer.length) { + var buffer= audiobuffer.getChannelData(channel); + var rms = 0; + for (var i = start; i < end; i++) { + rms += buffer[i] * buffer[i]; + } + + rms /= buffer.length; + rms = Math.sqrt(rms); + return rms; +} + +function getEmptyBuffer(context, length) { + return context.createBuffer(gTest.numberOfChannels, length, context.sampleRate); +} + +/** + * This function assumes that the test file 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. + */ +function runTest() +{ + function done() { + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + 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) { + is(expectedBuffers[i].numberOfChannels, gTest.numberOfChannels, + "Correct number of channels for expected buffer " + i); + expectedFrames += expectedBuffers[i].length; + } + if (gTest.length && gTest.createExpectedBuffers) { + is(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(); + is(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(); + } + }); + }; + + if (document.readyState !== 'complete') { + addLoadEvent(runTestFunction); + } else { + runTestFunction(); + } +} |