import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,3 @@
onmessage = function (event) {
postMessage("Pong");
};

Binary file not shown.

View file

@ -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.

View file

@ -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.");
}
}

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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]

View file

@ -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();
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1 @@
[browser_bug1181073.js]

View file

@ -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)");
});
});
});

View file

@ -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();
}

View file

@ -0,0 +1 @@
Surely this is not an audio file!

View file

@ -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);
}

View file

@ -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]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

Some files were not shown because too many files have changed in this diff Show more