From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/media/webaudio/test/blink/README | 9 + dom/media/webaudio/test/blink/audio-testing.js | 192 +++++++++ dom/media/webaudio/test/blink/biquad-filters.js | 368 ++++++++++++++++ dom/media/webaudio/test/blink/biquad-testing.js | 153 +++++++ .../webaudio/test/blink/convolution-testing.js | 182 ++++++++ dom/media/webaudio/test/blink/mochitest.ini | 22 + .../webaudio/test/blink/panner-model-testing.js | 210 +++++++++ .../test/blink/test_biquadFilterNodeAllPass.html | 32 ++ .../blink/test_biquadFilterNodeAutomation.html | 351 ++++++++++++++++ .../test/blink/test_biquadFilterNodeBandPass.html | 34 ++ .../test_biquadFilterNodeGetFrequencyResponse.html | 261 ++++++++++++ .../test/blink/test_biquadFilterNodeHighPass.html | 33 ++ .../test/blink/test_biquadFilterNodeHighShelf.html | 33 ++ .../test/blink/test_biquadFilterNodeLowPass.html | 34 ++ .../test/blink/test_biquadFilterNodeLowShelf.html | 34 ++ .../test/blink/test_biquadFilterNodeNotch.html | 33 ++ .../test/blink/test_biquadFilterNodePeaking.html | 34 ++ .../test/blink/test_biquadFilterNodeTail.html | 76 ++++ .../webaudio/test/blink/test_iirFilterNode.html | 467 +++++++++++++++++++++ .../test_iirFilterNodeGetFrequencyResponse.html | 97 +++++ 20 files changed, 2655 insertions(+) create mode 100644 dom/media/webaudio/test/blink/README create mode 100644 dom/media/webaudio/test/blink/audio-testing.js create mode 100644 dom/media/webaudio/test/blink/biquad-filters.js create mode 100644 dom/media/webaudio/test/blink/biquad-testing.js create mode 100644 dom/media/webaudio/test/blink/convolution-testing.js create mode 100644 dom/media/webaudio/test/blink/mochitest.ini create mode 100644 dom/media/webaudio/test/blink/panner-model-testing.js create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html create mode 100644 dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html create mode 100644 dom/media/webaudio/test/blink/test_iirFilterNode.html create mode 100644 dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html (limited to 'dom/media/webaudio/test/blink') diff --git a/dom/media/webaudio/test/blink/README b/dom/media/webaudio/test/blink/README new file mode 100644 index 0000000000..1d819221fd --- /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 0000000000..c66d32c7f2 --- /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 0000000000..06fff98b18 --- /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 0000000000..795adf6012 --- /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 0000000000..98ff0c7756 --- /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 0000000000..8d115b2e8e --- /dev/null +++ b/dom/media/webaudio/test/blink/mochitest.ini @@ -0,0 +1,22 @@ +[DEFAULT] +tags = mtg 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 0000000000..45460e2768 --- /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 0000000000..024c2f50df --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html @@ -0,0 +1,32 @@ + + + + Test BiquadFilterNode All Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..5a71ce46e5 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html @@ -0,0 +1,351 @@ + + + + Test BiquadFilterNode All Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..05c4c3a265 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html @@ -0,0 +1,34 @@ + + + + Test BiquadFilterNode Band Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..c2b6612034 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html @@ -0,0 +1,261 @@ + + + + Test BiquadFilterNode All Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..a615574c2d --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html @@ -0,0 +1,33 @@ + + + + Test BiquadFilterNode High Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..c8f6815930 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html @@ -0,0 +1,33 @@ + + + + Test BiquadFilterNode High Shelf Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..dcea18551a --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html @@ -0,0 +1,34 @@ + + + + Test BiquadFilterNode Low Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..c1349f8e7e --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html @@ -0,0 +1,34 @@ + + + + Test BiquadFilterNode Low Shelf Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..0fcbc5546e --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html @@ -0,0 +1,33 @@ + + + + Test BiquadFilterNode Notch Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..8e4727a37e --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html @@ -0,0 +1,34 @@ + + + + Test BiquadFilterNode Low Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..cc170df2a5 --- /dev/null +++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html @@ -0,0 +1,76 @@ + + + + Test BiquadFilterNode All Pass Filter + + + + +
+
+
+
+
+
+
+ + 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 0000000000..0ef7a37e3b --- /dev/null +++ b/dom/media/webaudio/test/blink/test_iirFilterNode.html @@ -0,0 +1,467 @@ + + + + Test IIRFilterNode GetFrequencyResponse + + + + + + +
+
+
+ + 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 0000000000..09782f73be --- /dev/null +++ b/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html @@ -0,0 +1,97 @@ + + + + Test IIRFilterNode GetFrequencyResponse + + + + + + +
+
+
+ + -- cgit v1.2.3