Bug 1455442 [wpt PR 10541] - Move BiquadFilter tests to WPT, a=testonly

Automatic update from web-platform-testsMove BiquadFilter tests to WPT

Move the BiquadFilter tests to WPT.

The tail-time-*.html tests can't be moved because they test Chrome's
implementation of tail time and is thus specific to Chrome.

Firefox fails biquad-automation.html because Firefox hasn't
implemented a-rate parameters for the filters.  Firefox also fails
biquad-basic.html because it doesn't throw errors if the array lengths
for getFrequencyResponse have different lengths.

Bug: 745778
Change-Id: I0c729038670931d4a47f6c28ae0b688c89320f49
Reviewed-on: https://chromium-review.googlesource.com/994056
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#552399}

--

wpt-commits: 24fc87cff04462b1c65aeb83ceb53f52b98eabf8
wpt-pr: 10541
This commit is contained in:
Raymond Toy 2018-04-29 20:21:49 +00:00 коммит произвёл moz-wptsync-bot
Родитель 27f872a362
Коммит 971ba1b83e
16 изменённых файлов: 1966 добавлений и 0 удалений

Просмотреть файл

@ -296340,6 +296340,11 @@
{}
]
],
"webaudio/resources/biquad-testing.js": [
[
{}
]
],
"webaudio/resources/convolution-testing.js": [
[
{}
@ -368298,6 +368303,90 @@
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-allpass.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-allpass.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-automation.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-automation.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-bandpass.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-bandpass.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-basic.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-basic.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-getFrequencyResponse.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-getFrequencyResponse.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highpass.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highpass.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highshelf.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highshelf.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowshelf.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowshelf.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-notch.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-notch.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-peaking.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-peaking.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-tail.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-tail.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquadfilternode-basic.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/biquadfilternode-basic.html",
{}
]
],
"webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html": [
[
"/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html",
{}
]
],
"webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-basic.html": [
[
"/webaudio/the-audio-api/the-channelmergernode-interface/audiochannelmerger-basic.html",
@ -606913,6 +607002,10 @@
"914a0268776de98f3b608babfa5699a93a2cd723",
"support"
],
"webaudio/resources/biquad-testing.js": [
"148a402f0a212c8b37bc377c5c9f7091d653bb7c",
"support"
],
"webaudio/resources/convolution-testing.js": [
"4a0ae6b3701147d16cb91e5660e1f4909d022d06",
"support"
@ -607177,6 +607270,62 @@
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-allpass.html": [
"5e614c338cde7788c8f88200c8e5540c8ddd7fe0",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-automation.html": [
"13549ae0edffb8b64ad57a0767cf1eaa98a45c48",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-bandpass.html": [
"85b51950563a14ec76a971d79b31b42c677d6185",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-basic.html": [
"3675c5430229774a8079eae866a355ca0793103b",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-getFrequencyResponse.html": [
"68a2545839beaacebaf65286829174375435a83f",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highpass.html": [
"f20705cb15906fa78f3db321d57b568478ffdb1c",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-highshelf.html": [
"9fa170eb3b99552faf418b7efb84cb9e39ddf5d0",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html": [
"8e300cd5b95c753ee234cb1256f5ec1295492d2e",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowshelf.html": [
"8addf4c6790feaeb028f31224cccd98c6e5fb7ee",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-notch.html": [
"0fe79eda36594125827af62d3a8abfd884b0a1d3",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-peaking.html": [
"8b66bfd29e8ea0b6405810d9d738331a7869e9a0",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquad-tail.html": [
"e15e696492076d522c1052ae890b37f52e7bd27b",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/biquadfilternode-basic.html": [
"f24a473f695c2d10788ba7d50728259a08ed53c8",
"testharness"
],
"webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html": [
"348376a643b765700342e9620e1346d432a28131",
"testharness"
],
"webaudio/the-audio-api/the-channelmergernode-interface/.gitkeep": [
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"support"

Просмотреть файл

@ -0,0 +1,171 @@
// Globals, to make testing and debugging easier.
let context;
let filter;
let signal;
let renderedBuffer;
let renderedData;
let sampleRate = 44100.0;
let pulseLengthFrames = .1 * sampleRate;
// Maximum allowed error for the test to succeed. Experimentally determined.
let maxAllowedError = 5.9e-8;
// This must be large enough so that the filtered result is
// essentially zero. See comments for createTestAndRun.
let timeStep = .1;
// Maximum number of filters we can process (mostly for setting the
// render length correctly.)
let maxFilters = 5;
// How long to render. Must be long enough for all of the filters we
// want to test.
let renderLengthSeconds = timeStep * (maxFilters + 1);
let renderLengthSamples = Math.round(renderLengthSeconds * sampleRate);
// Number of filters that will be processed.
let nFilters;
function createImpulseBuffer(context, length) {
let impulse = context.createBuffer(1, length, context.sampleRate);
let data = impulse.getChannelData(0);
for (let k = 1; k < data.length; ++k) {
data[k] = 0;
}
data[0] = 1;
return impulse;
}
function createTestAndRun(context, filterType, testParameters) {
// 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.
let filterParameters = testParameters.filterParameters;
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 (let 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);
}
return context.startRendering().then(buffer => {
checkFilterResponse(buffer, filterType, testParameters);
});
}
function addSignal(dest, src, destOffset) {
// Add src to dest at the given dest offset.
for (let k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) {
dest[k] += src[j];
}
}
function generateReference(filterType, filterParameters) {
let result = new Array(renderLengthSamples);
let data = new Array(renderLengthSamples);
// Initialize the result array and data.
for (let k = 0; k < result.length; ++k) {
result[k] = 0;
data[k] = 0;
}
// Make data an impulse.
data[0] = 1;
for (let k = 0; k < nFilters; ++k) {
// Filter an impulse
let detune = (filterParameters[k].detune === undefined) ?
0 :
filterParameters[k].detune;
let frequency = filterParameters[k].cutoff *
Math.pow(2, detune / 1200); // Apply detune, converting from Cents.
let filterCoef = createFilter(
filterType, frequency, filterParameters[k].q, filterParameters[k].gain);
let 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(renderedBuffer, filterType, testParameters) {
let filterParameters = testParameters.filterParameters;
let maxAllowedError = testParameters.threshold;
let should = testParameters.should;
renderedData = renderedBuffer.getChannelData(0);
reference = generateReference(filterType, filterParameters);
let len = Math.min(renderedData.length, reference.length);
let success = true;
// Maximum error between rendered data and expected data
let maxError = 0;
// Sample offset where the maximum error occurred.
let maxPosition = 0;
// Number of infinities or NaNs that occurred in the rendered data.
let invalidNumberCount = 0;
should(nFilters, 'Number of filters tested')
.beEqualTo(filterParameters.length);
// Compare the rendered signal with our reference, keeping
// track of the maximum difference (and the offset of the max
// difference.) Check for bad numbers in the rendered output
// too. There shouldn't be any.
for (let k = 0; k < len; ++k) {
let err = Math.abs(renderedData[k] - reference[k]);
if (err > maxError) {
maxError = err;
maxPosition = k;
}
if (!isValidNumber(renderedData[k])) {
++invalidNumberCount;
}
}
should(
invalidNumberCount, 'Number of non-finite values in the rendered output')
.beEqualTo(0);
should(maxError, 'Max error in ' + filterTypeName[filterType] + ' response')
.beLessThanOrEqualTo(maxAllowedError);
}

Просмотреть файл

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-allpass.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad allpass filter'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
let 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', {
should: should,
threshold: 3.9337e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,406 @@
<!DOCTYPE html>
<html>
<head>
<title>
Biquad Automation Test
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
// Don't need to run these tests at high sampling rate, so just use a low
// one to reduce memory usage and complexity.
let sampleRate = 16000;
// How long to render for each test.
let renderDuration = 0.25;
// Where to end the automations. Fairly arbitrary, but must end before
// the renderDuration.
let automationEndTime = renderDuration / 2;
let audit = Audit.createTaskRunner();
// 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) {
let renderEndFrame = Math.ceil(renderDuration * sampleRate);
let endFrame = Math.ceil(duration * sampleRate);
let nCoef = renderEndFrame;
let b0 = new Float64Array(nCoef);
let b1 = new Float64Array(nCoef);
let b2 = new Float64Array(nCoef);
let a1 = new Float64Array(nCoef);
let a2 = new Float64Array(nCoef);
let k = 0;
// If the property is not given, use the defaults.
let freqs = parameters.freq || [350, 350];
let qs = parameters.Q || [1, 1];
let gains = parameters.gain || [0, 0];
let detunes = parameters.detune || [0, 0];
for (let frame = 0; frame <= endFrame; ++frame) {
// Apply linear ramp at frame |frame|.
let f =
linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, duration);
let q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration);
let g =
linearRamp(frame / sampleRate, gains[0], gains[1], 0, duration);
let d = linearRamp(
frame / sampleRate, detunes[0], detunes[1], 0, duration);
// Compute actual frequency parameter
f = f * Math.pow(2, d / 1200);
// Compute filter coefficients
let 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;
}
// Fill the rest of the arrays with the constant value to the end of
// the rendering duration.
b0.fill(b0[endFrame], endFrame + 1);
b1.fill(b1[endFrame], endFrame + 1);
b2.fill(b2[endFrame], endFrame + 1);
a1.fill(a1[endFrame], endFrame + 1);
a2.fill(a2[endFrame], endFrame + 1);
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) {
let length = signal.length;
// Use double precision for the internal computations.
let 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 (let 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.
let src = context.createBufferSource();
let b =
context.createBuffer(1, renderDuration * sampleRate, sampleRate);
let data = b.getChannelData(0);
let omega = 2 * Math.PI * toneFrequency / sampleRate;
for (let k = 0; k < data.length; ++k) {
data[k] = Math.sin(omega * k);
}
src.buffer = b;
let f = context.createBiquadFilter();
src.connect(f);
f.connect(context.destination);
src.start();
return {filter: f, source: b};
}
function createFilterVerifier(
should, filterCreator, threshold, parameters, input, message) {
return function(resultBuffer) {
let actual = resultBuffer.getChannelData(0);
let coefs = generateFilterCoefficients(
filterCreator, parameters, automationEndTime);
reference = timeVaryingFilter(input, coefs);
should(actual, message).beCloseToArray(reference, {
absoluteThreshold: threshold
});
};
}
// Automate just the frequency parameter. A bandpass filter is used where
// the center frequency is swept across the source (which is a simple
// tone).
audit.define('automate-freq', (task, should) => {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
// Center frequency of bandpass filter and also the frequency of the
// test tone.
let centerFreq = 10 * 440;
// Sweep the frequency +/- 5*440 Hz from the center. This should cause
// the output to be low at the beginning and end of the test where the
// tone is outside the pass band of the filter, but high in the middle
// of the automation time where the tone is near the center of the pass
// band. Make sure the frequency sweep stays inside the Nyquist
// frequency.
let parameters = {freq: [centerFreq - 5 * 440, centerFreq + 5 * 440]};
let graph = configureGraph(context, centerFreq);
let f = graph.filter;
let b = graph.source;
f.type = 'bandpass';
f.frequency.setValueAtTime(parameters.freq[0], 0);
f.frequency.linearRampToValueAtTime(
parameters.freq[1], automationEndTime);
context.startRendering()
.then(createFilterVerifier(
should, createBandpassFilter, 4.6455e-6, parameters,
b.getChannelData(0),
'Output of bandpass filter with frequency automation'))
.then(() => task.done());
});
// Automate just the Q parameter. A bandpass filter is used where the Q
// of the filter is swept.
audit.define('automate-q', (task, should) => {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
// The frequency of the test tone.
let 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.
let parameters = {
Q: [1, 200],
// Center frequency of the bandpass filter is just 25 Hz above the
// tone frequency.
freq: [centerFreq + 25, centerFreq + 25]
};
let graph = configureGraph(context, centerFreq);
let f = graph.filter;
let 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], automationEndTime);
context.startRendering()
.then(createFilterVerifier(
should, createBandpassFilter, 9.8348e-7, parameters,
b.getChannelData(0),
'Output of bandpass filter with Q automation'))
.then(() => task.done());
});
// 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.
audit.define('automate-gain', (task, should) => {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
// Frequency of the test tone.
let 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.)
let parameters = {freq: [3500, 3500], gain: [20, -20]};
let graph = configureGraph(context, centerFreq);
let f = graph.filter;
let 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], automationEndTime);
context.startRendering()
.then(createFilterVerifier(
should, createLowShelfFilter, 2.7657e-5, parameters,
b.getChannelData(0),
'Output of lowshelf filter with gain automation'))
.then(() => task.done());
});
// 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.
audit.define('automate-detune', (task, should) => {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
let centerFreq = 10 * 440;
let parameters = {
freq: [centerFreq, centerFreq],
detune: [-10 * 1200, 10 * 1200]
};
let graph = configureGraph(context, centerFreq);
let f = graph.filter;
let 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], automationEndTime);
context.startRendering()
.then(createFilterVerifier(
should, createBandpassFilter, 3.1471e-5, parameters,
b.getChannelData(0),
'Output of bandpass filter with detune automation'))
.then(() => task.done());
});
// 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.
audit.define('automate-all', (task, should) => {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
let graph = configureGraph(context, 10 * 440);
let f = graph.filter;
let b = graph.source;
// Sweep all of the filter parameters. These are pretty much arbitrary.
let parameters = {
freq: [8000, 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], automationEndTime);
f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime);
f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime);
f.detune.linearRampToValueAtTime(
parameters.detune[1], automationEndTime);
context.startRendering()
.then(createFilterVerifier(
should, createPeakingFilter, 6.2907e-4, parameters,
b.getChannelData(0),
'Output of peaking filter with automation of all parameters'))
.then(() => task.done());
});
// 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)
audit.define('modulation', (task, should) => {
let context =
new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
// Create a graph with the sinusoidal source at 440 Hz as the input to a
// biquad filter.
let graph = configureGraph(context, 440);
let f = graph.filter;
let 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.)
let mod = context.createBufferSource();
let mbuffer =
context.createBuffer(1, renderDuration * sampleRate, sampleRate);
let d = mbuffer.getChannelData(0);
let omega = 2 * Math.PI * 103 / sampleRate;
for (let k = 0; k < d.length; ++k) {
d[k] = 148 * Math.sin(omega * k);
}
mod.buffer = mbuffer;
mod.connect(f.frequency);
mod.start();
context.startRendering()
.then(function(resultBuffer) {
let actual = resultBuffer.getChannelData(0);
// Compute the filter coefficients using the mod sine wave
let endFrame = Math.ceil(renderDuration * sampleRate);
let nCoef = endFrame;
let b0 = new Float64Array(nCoef);
let b1 = new Float64Array(nCoef);
let b2 = new Float64Array(nCoef);
let a1 = new Float64Array(nCoef);
let a2 = new Float64Array(nCoef);
// Generate the filter coefficients when the frequency varies from
// 116 to 248 Hz using the 103 Hz sinusoid.
for (let k = 0; k < nCoef; ++k) {
let freq = f.frequency.value + d[k];
let 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});
should(
actual,
'Output of bandpass filter with sinusoidal modulation of bandpass center frequency')
.beCloseToArray(reference, {absoluteThreshold: 3.9787e-5});
})
.then(() => task.done());
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-bandpass.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad bandpass filter.'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
// The filters we want to test.
let 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', {
should: should,
threshold: 2.2501e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Basic BiquadFilterNode Properties
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 48000;
let testFrames = 100;
// Global context that can be used by the individual tasks. It must be
// defined by the initialize task.
let context;
let audit = Audit.createTaskRunner();
audit.define('initialize', (task, should) => {
should(() => {
context = new OfflineAudioContext(1, testFrames, sampleRate);
}, 'Initialize context for testing').notThrow();
task.done();
});
audit.define('existence', (task, should) => {
should(context.createBiquadFilter, 'context.createBiquadFilter')
.exist();
task.done();
});
audit.define('parameters', (task, should) => {
// Create a really simple IIR filter. Doesn't much matter what.
let coef = Float32Array.from([1]);
let f = context.createBiquadFilter(coef, coef);
should(f.numberOfInputs, 'numberOfInputs').beEqualTo(1);
should(f.numberOfOutputs, 'numberOfOutputs').beEqualTo(1);
should(f.channelCountMode, 'channelCountMode').beEqualTo('max');
should(f.channelInterpretation, 'channelInterpretation')
.beEqualTo('speakers');
task.done();
});
audit.define('exceptions-createBiquadFilter', (task, should) => {
should(function() {
// Two args are required.
context.createBiquadFilter();
}, 'createBiquadFilter()').notThrow();
task.done();
});
audit.define('exceptions-getFrequencyData', (task, should) => {
// Create a really simple IIR filter. Doesn't much matter what.
let coef = Float32Array.from([1]);
let f = context.createBiquadFilter(coef, coef);
should(
function() {
// frequencyHz can't be null.
f.getFrequencyResponse(
null, new Float32Array(1), new Float32Array(1));
},
'getFrequencyResponse(' +
'null, ' +
'new Float32Array(1), ' +
'new Float32Array(1))')
.throw('TypeError');
should(
function() {
// magResponse can't be null.
f.getFrequencyResponse(
new Float32Array(1), null, new Float32Array(1));
},
'getFrequencyResponse(' +
'new Float32Array(1), ' +
'null, ' +
'new Float32Array(1))')
.throw('TypeError');
should(
function() {
// phaseResponse can't be null.
f.getFrequencyResponse(
new Float32Array(1), new Float32Array(1), null);
},
'getFrequencyResponse(' +
'new Float32Array(1), ' +
'new Float32Array(1), ' +
'null)')
.throw('TypeError');
should(
function() {
// magResponse array must the same length as frequencyHz
f.getFrequencyResponse(
new Float32Array(10), new Float32Array(1),
new Float32Array(20));
},
'getFrequencyResponse(' +
'new Float32Array(10), ' +
'new Float32Array(1), ' +
'new Float32Array(20))')
.throw('InvalidAccessError');
should(
function() {
// phaseResponse array must be the same length as frequencyHz
f.getFrequencyResponse(
new Float32Array(10), new Float32Array(20),
new Float32Array(1));
},
'getFrequencyResponse(' +
'new Float32Array(10), ' +
'new Float32Array(20), ' +
'new Float32Array(1))')
.throw('InvalidAccessError');
task.done();
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,335 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test BiquadFilter getFrequencyResponse() functionality
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// 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.
// The magnitude response of the biquad filter.
let magResponse;
// The phase response of the biquad filter.
let phaseResponse;
// Number of frequency samples to take.
let numberOfFrequencies = 1000;
// The filter parameters.
let filterCutoff = 1000; // Hz.
let filterQ = 1;
let filterGain = 5; // Decibels.
// The maximum allowed error in the magnitude response.
let maxAllowedMagError = 9.775e-7;
// The maximum allowed error in the phase response.
let maxAllowedPhaseError = 5.4187e-8;
// The magnitudes and phases of the reference frequency response.
let expectedMagnitudes;
let expectedPhases;
// Convert frequency in Hz to a normalized frequency between 0 to 1 with 1
// corresponding to the Nyquist frequency.
function normalizedFrequency(freqHz, sampleRate) {
let 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) {
let b0 = coef.b0;
let b1 = coef.b1;
let b2 = coef.b2;
let a1 = coef.a1;
let 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.
let omega = Math.PI * f;
let numeratorReal =
b0 + b1 * Math.cos(omega) + b2 * Math.cos(2 * omega);
let numeratorImag = -(b1 * Math.sin(omega) + b2 * Math.sin(2 * omega));
let denominatorReal =
1 + a1 * Math.cos(omega) + a2 * Math.cos(2 * omega);
let denominatorImag =
-(a1 * Math.sin(omega) + a2 * Math.sin(2 * omega));
let magnitude = Math.sqrt(
(numeratorReal * numeratorReal + numeratorImag * numeratorImag) /
(denominatorReal * denominatorReal +
denominatorImag * denominatorImag));
let 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) {
let sampleRate = filter.context.sampleRate;
let normalizedFreq =
normalizedFrequency(filter.frequency.value, sampleRate);
let filterCoefficients = createFilter(
filter.type, normalizedFreq, filter.Q.value, filter.gain.value);
let magnitudes = [];
let phases = [];
for (let k = 0; k < frequencies.length; ++k) {
let 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) {
let frequencies = new Float32Array(nFrequencies);
let nyquist = sampleRate / 2;
let freqDelta = nyquist / nFrequencies;
for (let 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 (let 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) {
let 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(
should, filter, frequencies, magResponse, phaseResponse) {
let expectedResponse = frequencyResponseReference(filter, frequencies);
expectedMagnitudes = expectedResponse.magnitudes;
expectedPhases = expectedResponse.phases;
let n = magResponse.length;
let badResponse = false;
let maxMagError = -1;
let maxMagErrorIndex = -1;
let k;
let hasBadNumber;
hasBadNumber = findBadNumber(magResponse);
badResponse = !should(
hasBadNumber >= 0 ? 1 : 0,
'Number of non-finite values in magnitude response')
.beEqualTo(0);
hasBadNumber = findBadNumber(phaseResponse);
badResponse = !should(
hasBadNumber >= 0 ? 1 : 0,
'Number of non-finte values in phase response')
.beEqualTo(0);
// 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);
badResponse =
!should(
hasBadNumber >= 0 ? 1 : 0,
'Number of non-finite values in the expected magnitude response')
.beEqualTo(0);
hasBadNumber = findBadNumber(expectedPhases);
badResponse =
!should(
hasBadNumber >= 0 ? 1 : 0,
'Number of non-finite values in expected phase response')
.beEqualTo(0);
// If we found a NaN or infinity, the following tests aren't very
// helpful, especially for NaN. We run them anyway, after printing a
// warning message.
should(
!badResponse,
'Actual and expected results contained only finite values')
.beTrue();
for (k = 0; k < n; ++k) {
let error = Math.abs(
linearToDecibels(magResponse[k]) -
linearToDecibels(expectedMagnitudes[k]));
if (error > maxMagError) {
maxMagError = error;
maxMagErrorIndex = k;
}
}
should(
linearToDecibels(maxMagError),
'Max error (' + linearToDecibels(maxMagError) +
' dB) of magnitude response at frequency ' +
frequencies[maxMagErrorIndex] + ' Hz')
.beLessThanOrEqualTo(linearToDecibels(maxAllowedMagError));
let maxPhaseError = -1;
let maxPhaseErrorIndex = -1;
for (k = 0; k < n; ++k) {
let error =
absolutePhaseDifference(phaseResponse[k], expectedPhases[k]);
if (error > maxPhaseError) {
maxPhaseError = error;
maxPhaseErrorIndex = k;
}
}
should(
radToDegree(maxPhaseError),
'Max error (' + radToDegree(maxPhaseError) +
' deg) in phase response at frequency ' +
frequencies[maxPhaseErrorIndex] + ' Hz')
.beLessThanOrEqualTo(radToDegree(maxAllowedPhaseError));
}
function radToDegree(rad) {
// Radians to degrees
return rad * 180 / Math.PI;
}
audit.define(
{label: 'test', description: 'Biquad frequency response'},
function(task, should) {
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;
let frequencies =
createFrequencies(numberOfFrequencies, context.sampleRate);
magResponse = new Float32Array(numberOfFrequencies);
phaseResponse = new Float32Array(numberOfFrequencies);
filter.getFrequencyResponse(
frequencies, magResponse, phaseResponse);
compareResponses(
should, filter, frequencies, magResponse, phaseResponse);
task.done();
});
audit.define(
{
label: 'getFrequencyResponse',
description: 'Test out-of-bounds frequency values'
},
(task, should) => {
let context = new OfflineAudioContext(1, 1, sampleRate);
let filter = new BiquadFilterNode(context);
// Frequencies to test. These are all outside the valid range of
// frequencies of 0 to Nyquist.
let freq = new Float32Array(2);
freq[0] = -1;
freq[1] = context.sampleRate / 2 + 1;
let mag = new Float32Array(freq.length);
let phase = new Float32Array(freq.length);
filter.getFrequencyResponse(freq, mag, phase);
// Verify that the returned magnitude and phase entries are alL NaN
// since the frequencies are outside the valid range
for (let k = 0; k < mag.length; ++k) {
should(mag[k],
'Magnitude response at frequency ' + freq[k])
.beNaN();
}
for (let k = 0; k < phase.length; ++k) {
should(phase[k],
'Phase response at frequency ' + freq[k])
.beNaN();
}
task.done();
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-highpass.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad highpass filter'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
// The filters we want to test.
let filterParameters = [
{cutoff: 0, q: 1, gain: 1},
{cutoff: 1, q: 1, gain: 1},
{cutoff: 0.25, q: 1, gain: 1},
];
createTestAndRun(context, 'highpass', {
should: should,
threshold: 1.5487e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-highshelf.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad highshelf filter'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
// The filters we want to test.
let filterParameters = [
{cutoff: 0, q: 10, gain: 10},
{cutoff: 1, q: 10, gain: 10},
{cutoff: 0.25, q: 10, gain: 10},
];
createTestAndRun(context, 'highshelf', {
should: should,
threshold: 6.2577e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-lowpass.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad lowpass filter'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
// The filters we want to test.
let 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', {
should: should,
threshold: 9.7869e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-lowshelf.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad lowshelf filter'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
// The filters we want to test.
let filterParameters = [
{cutoff: 0, q: 10, gain: 10},
{cutoff: 1, q: 10, gain: 10},
{cutoff: 0.25, q: 10, gain: 10},
];
createTestAndRun(context, 'lowshelf', {
should: should,
threshold: 3.8349e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-notch.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad notch filter'},
function(task, should) {
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
let 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', {
should: should,
threshold: 1.9669e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-peaking.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
<script src="/webaudio/resources/biquad-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Biquad peaking filter'},
function(task, should) {
window.jsTestIsAsync = true;
// Create offline audio context.
let context = new OfflineAudioContext(
2, sampleRate * renderLengthSeconds, sampleRate);
// The filters we want to test.
let 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', {
should: should,
threshold: 5.8234e-8,
filterParameters: filterParameters
}).then(task.done.bind(task));
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test Biquad Tail Output
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// A high sample rate shows the issue more clearly.
let sampleRate = 192000;
// Some short duration because we don't need to run the test for very
// long.
let testDurationSec = 0.5;
let 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.)
let sourceAmplitude = 100;
// The output of the biquad filter should not change by more than this
// much between output samples. Threshold was determined experimentally.
let glitchThreshold = 0.012968;
// 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.
audit.define(
{label: 'test', description: 'Biquad Tail Output'},
function(task, should) {
let context =
new OfflineAudioContext(1, testDurationFrames, sampleRate);
// Create an impulse source.
let buffer = context.createBuffer(1, 1, context.sampleRate);
buffer.getChannelData(0)[0] = sourceAmplitude;
let 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.
let 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
should(result.getChannelData(0), 'Biquad output')
.notGlitch(glitchThreshold);
task.done();
})
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquadfilternode-basic.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
audit.define(
{label: 'test', description: 'Basic tests for BiquadFilterNode'},
function(task, should) {
let context = new AudioContext();
let filter = context.createBiquadFilter();
should(filter.numberOfInputs, 'Number of inputs').beEqualTo(1);
should(filter.numberOfOutputs, 'Number of outputs').beEqualTo(1);
should(filter.type, 'Default filter type').beEqualTo('lowpass');
should(filter.frequency.value, 'Default frequency value')
.beEqualTo(350);
should(filter.Q.value, 'Default Q value').beEqualTo(1);
should(filter.gain.value, 'Default gain value').beEqualTo(0);
// Check that all legal filter types can be set.
let filterTypeArray = [
{type: 'lowpass'}, {type: 'highpass'}, {type: 'bandpass'},
{type: 'lowshelf'}, {type: 'highshelf'}, {type: 'peaking'},
{type: 'notch'}, {type: 'allpass'}
];
for (let i = 0; i < filterTypeArray.length; ++i) {
should(
() => filter.type = filterTypeArray[i].type,
'Setting filter.type to ' + filterTypeArray[i].type)
.notThrow();
should(filter.type, 'Filter type is')
.beEqualTo(filterTypeArray[i].type);
}
// Check that numerical values are no longer supported
filter.type = 99;
should(filter.type, 'Setting filter.type to (invalid) 99')
.notBeEqualTo(99);
task.done();
});
audit.run();
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,288 @@
<!DOCTYPE html>
<html>
<head>
<title>
biquad-bandpass.html
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit-util.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/webaudio/resources/biquad-filters.js"></script>
</head>
<body>
<script id="layout-test-code">
let audit = Audit.createTaskRunner();
// In the tests below, the initial values are not important, except that
// we wanted them to be all different so that the output contains
// different values for the first few samples. Otherwise, the actual
// values don't really matter. A peaking filter is used because the
// frequency, Q, gain, and detune parameters are used by this filter.
//
// Also, for the changeList option, the times and new values aren't really
// important. They just need to change so that we can verify that the
// outputs from the .value setter still matches the output from the
// corresponding setValueAtTime.
audit.define(
{label: 'Test 0', description: 'No dezippering for frequency'},
(task, should) => {
doTest(should, {
paramName: 'frequency',
initializer: {type: 'peaking', Q: 1, gain: 5},
changeList:
[{quantum: 2, newValue: 800}, {quantum: 7, newValue: 200}],
threshold: 3.0399e-6
}).then(() => task.done());
});
audit.define(
{label: 'Test 1', description: 'No dezippering for detune'},
(task, should) => {
doTest(should, {
paramName: 'detune',
initializer:
{type: 'peaking', frequency: 400, Q: 3, detune: 33, gain: 10},
changeList:
[{quantum: 2, newValue: 1000}, {quantum: 5, newValue: -400}],
threshold: 4.0532e-6
}).then(() => task.done());
});
audit.define(
{label: 'Test 2', description: 'No dezippering for Q'},
(task, should) => {
doTest(should, {
paramName: 'Q',
initializer: {type: 'peaking', Q: 5},
changeList:
[{quantum: 2, newValue: 10}, {quantum: 8, newValue: -10}]
}).then(() => task.done());
});
audit.define(
{label: 'Test 3', description: 'No dezippering for gain'},
(task, should) => {
doTest(should, {
paramName: 'gain',
initializer: {type: 'peaking', gain: 1},
changeList:
[{quantum: 2, newValue: 5}, {quantum: 6, newValue: -.3}],
threshold: 1.9074e-6
}).then(() => task.done());
});
// This test compares the filter output against a JS implementation of the
// filter. We're only testing a change in the frequency for a lowpass
// filter. This assumes we don't need to test other AudioParam changes
// with JS code because any mistakes would be exposed in the tests above.
audit.define(
{
label: 'Test 4',
description: 'No dezippering of frequency vs JS filter'
},
(task, should) => {
// Channel 0 is the source, channel 1 is the filtered output.
let context = new OfflineAudioContext(2, 2048, 16384);
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let src = new OscillatorNode(context);
let f = new BiquadFilterNode(context, {type: 'lowpass'});
// Remember the initial filter parameters.
let initialFilter = {
type: f.type,
frequency: f.frequency.value,
gain: f.gain.value,
detune: f.detune.value,
Q: f.Q.value
};
src.connect(merger, 0, 0);
src.connect(f).connect(merger, 0, 1);
// Apply the filter change at frame |changeFrame| with a new
// frequency value of |newValue|.
let changeFrame = 2 * RENDER_QUANTUM_FRAMES;
let newValue = 750;
context.suspend(changeFrame / context.sampleRate)
.then(() => f.frequency.value = newValue)
.then(() => context.resume());
src.start();
context.startRendering()
.then(audio => {
let signal = audio.getChannelData(0);
let actual = audio.getChannelData(1);
// Get initial filter coefficients and updated coefficients
let nyquistFreq = context.sampleRate / 2;
let initialCoef = createFilter(
initialFilter.type, initialFilter.frequency / nyquistFreq,
initialFilter.Q, initialFilter.gain);
let finalCoef = createFilter(
f.type, f.frequency.value / nyquistFreq, f.Q.value,
f.gain.value);
let expected = new Float32Array(signal.length);
// Filter the initial part of the signal.
expected[0] =
filterSample(signal[0], initialCoef, 0, 0, 0, 0);
expected[1] = filterSample(
signal[1], initialCoef, expected[0], 0, signal[0], 0);
for (let k = 2; k < changeFrame; ++k) {
expected[k] = filterSample(
signal[k], initialCoef, expected[k - 1],
expected[k - 2], signal[k - 1], signal[k - 2]);
}
// Filter the rest of the input with the new coefficients
for (let k = changeFrame; k < signal.length; ++k) {
expected[k] = filterSample(
signal[k], finalCoef, expected[k - 1], expected[k - 2],
signal[k - 1], signal[k - 2]);
}
// The JS filter should match the actual output.
let match =
should(actual, 'Output from ' + f.type + ' filter')
.beCloseToArray(
expected, {absoluteThreshold: 4.7684e-7});
should(match, 'Output matches JS filter results').beTrue();
})
.then(() => task.done());
});
audit.define(
{label: 'Test 5', description: 'Test with modulation'},
(task, should) => {
doTest(should, {
prefix: 'Modulation: ',
paramName: 'frequency',
initializer: {type: 'peaking', Q: 5, gain: 5},
modulation: true,
changeList:
[{quantum: 2, newValue: 10}, {quantum: 8, newValue: -10}]
}).then(() => task.done());
});
audit.run();
// Run test, returning the promise from startRendering. |options|
// specifies the parameters for the test. |options.paramName| is the name
// of the AudioParam of the filter that is being tested.
// |options.initializer| is the initial value to be used in constructing
// the filter. |options.changeList| is an array consisting of dictionary
// with two members: |quantum| is the rendering quantum at which time we
// want to change the AudioParam value, and |newValue| is the value to be
// used.
function doTest(should, options) {
let paramName = options.paramName;
let newValue = options.newValue;
let prefix = options.prefix || '';
// Create offline audio context. The sample rate should be a power of
// two to eliminate any round-off errors in computing the time at which
// to suspend the context for the parameter change. The length is
// fairly arbitrary as long as it's big enough to the changeList
// values. There are two channels: channel 0 is output for the filter
// under test, and channel 1 is the output of referencef filter.
let context = new OfflineAudioContext(2, 2048, 16384);
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let src = new OscillatorNode(context);
// |f0| is the filter under test that will have its AudioParam value
// changed. |f1| is the reference filter that uses setValueAtTime to
// update the AudioParam value.
let f0 = new BiquadFilterNode(context, options.initializer);
let f1 = new BiquadFilterNode(context, options.initializer);
src.connect(f0).connect(merger, 0, 0);
src.connect(f1).connect(merger, 0, 1);
// Modulate the AudioParam with an input signal, if requested.
if (options.modulation) {
// The modulation signal is a sine wave with amplitude 1/3 the cutoff
// frequency of the test filter. The amplitude is fairly arbitrary,
// but we want it to be a significant fraction of the cutoff so that
// the cutoff varies quite a bit in the test.
let mod =
new OscillatorNode(context, {type: 'sawtooth', frequency: 1000});
let modGain = new GainNode(context, {gain: f0.frequency.value / 3});
mod.connect(modGain);
modGain.connect(f0[paramName]);
modGain.connect(f1[paramName]);
mod.start();
}
// Output a message showing where we're starting from.
should(f0[paramName].value, prefix + `At time 0, ${paramName}`)
.beEqualTo(f0[paramName].value);
// Schedule all of the desired changes from |changeList|.
options.changeList.forEach(change => {
let changeTime =
change.quantum * RENDER_QUANTUM_FRAMES / context.sampleRate;
let value = change.newValue;
// Just output a message to show what we're doing.
should(value, prefix + `At time ${changeTime}, ${paramName}`)
.beEqualTo(value);
// Update the AudioParam value of each filter using setValueAtTime or
// the value setter.
f1[paramName].setValueAtTime(value, changeTime);
context.suspend(changeTime)
.then(() => f0[paramName].value = value)
.then(() => context.resume());
});
src.start();
return context.startRendering().then(audio => {
let actual = audio.getChannelData(0);
let expected = audio.getChannelData(1);
// The output from both filters MUST match exactly if dezippering has
// been properly removed.
let match = should(actual, `${prefix}Output from ${paramName} setter`)
.beCloseToArray(
expected, {absoluteThreshold: options.threshold});
// Just an extra message saying that what we're comparing, to make the
// output clearer. (Not really neceesary, but nice.)
should(
match,
`${prefix}Output from ${
paramName
} setter matches setValueAtTime output`)
.beTrue();
});
}
// Filter one sample:
//
// y[n] = b0 * x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
//
// where |x| is x[n], |xn1| is x[n-1], |xn2| is x[n-2], |yn1| is y[n-1],
// and |yn2| is y[n-2]. |coef| is a dictonary of the filter coefficients
// |b0|, |b1|, |b2|, |a1|, and |a2|.
function filterSample(x, coef, yn1, yn2, xn1, xn2) {
return coef.b0 * x + coef.b1 * xn1 + coef.b2 * xn2 - coef.a1 * yn1 -
coef.a2 * yn2;
}
</script>
</body>
</html>