summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/resources/biquad-testing.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webaudio/resources/biquad-testing.js')
-rw-r--r--testing/web-platform/tests/webaudio/resources/biquad-testing.js172
1 files changed, 172 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webaudio/resources/biquad-testing.js b/testing/web-platform/tests/webaudio/resources/biquad-testing.js
new file mode 100644
index 0000000000..7f90a1f72b
--- /dev/null
+++ b/testing/web-platform/tests/webaudio/resources/biquad-testing.js
@@ -0,0 +1,172 @@
+// Globals, to make testing and debugging easier.
+let context;
+let filter;
+let signal;
+let renderedBuffer;
+let renderedData;
+
+// Use a power of two to eliminate round-off in converting frame to time
+let sampleRate = 32768;
+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. This must be a whole number of frames.
+let timeStep = Math.ceil(.1 * sampleRate) / sampleRate;
+
+// 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);
+}