1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 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);
}
|