biquad-testing.js (5313B)
1 // Globals, to make testing and debugging easier. 2 var context; 3 var filter; 4 var signal; 5 var renderedBuffer; 6 var renderedData; 7 8 var sampleRate = 44100.0; 9 var pulseLengthFrames = .1 * sampleRate; 10 11 // Maximum allowed error for the test to succeed. Experimentally determined. 12 var maxAllowedError = 5.9e-8; 13 14 // This must be large enough so that the filtered result is 15 // essentially zero. See comments for createTestAndRun. 16 var timeStep = .1; 17 18 // Maximum number of filters we can process (mostly for setting the 19 // render length correctly.) 20 var maxFilters = 5; 21 22 // How long to render. Must be long enough for all of the filters we 23 // want to test. 24 var renderLengthSeconds = timeStep * (maxFilters + 1) ; 25 26 var renderLengthSamples = Math.round(renderLengthSeconds * sampleRate); 27 28 // Number of filters that will be processed. 29 var nFilters; 30 31 function createImpulseBuffer(context, length) { 32 var impulse = context.createBuffer(1, length, context.sampleRate); 33 var data = impulse.getChannelData(0); 34 for (var k = 1; k < data.length; ++k) { 35 data[k] = 0; 36 } 37 data[0] = 1; 38 39 return impulse; 40 } 41 42 43 function createTestAndRun(context, filterType, filterParameters) { 44 // To test the filters, we apply a signal (an impulse) to each of 45 // the specified filters, with each signal starting at a different 46 // time. The output of the filters is summed together at the 47 // output. Thus for filter k, the signal input to the filter 48 // starts at time k * timeStep. For this to work well, timeStep 49 // must be large enough for the output of each filter to have 50 // decayed to zero with timeStep seconds. That way the filter 51 // outputs don't interfere with each other. 52 53 nFilters = Math.min(filterParameters.length, maxFilters); 54 55 signal = new Array(nFilters); 56 filter = new Array(nFilters); 57 58 impulse = createImpulseBuffer(context, pulseLengthFrames); 59 60 // Create all of the signal sources and filters that we need. 61 for (var k = 0; k < nFilters; ++k) { 62 signal[k] = context.createBufferSource(); 63 signal[k].buffer = impulse; 64 65 filter[k] = context.createBiquadFilter(); 66 filter[k].type = filterType; 67 filter[k].frequency.value = context.sampleRate / 2 * filterParameters[k].cutoff; 68 filter[k].detune.value = (filterParameters[k].detune === undefined) ? 0 : filterParameters[k].detune; 69 filter[k].Q.value = filterParameters[k].q; 70 filter[k].gain.value = filterParameters[k].gain; 71 72 signal[k].connect(filter[k]); 73 filter[k].connect(context.destination); 74 75 signal[k].start(timeStep * k); 76 } 77 78 context.oncomplete = checkFilterResponse(filterType, filterParameters); 79 context.startRendering(); 80 } 81 82 function addSignal(dest, src, destOffset) { 83 // Add src to dest at the given dest offset. 84 for (var k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) { 85 dest[k] += src[j]; 86 } 87 } 88 89 function generateReference(filterType, filterParameters) { 90 var result = new Array(renderLengthSamples); 91 var data = new Array(renderLengthSamples); 92 // Initialize the result array and data. 93 for (var k = 0; k < result.length; ++k) { 94 result[k] = 0; 95 data[k] = 0; 96 } 97 // Make data an impulse. 98 data[0] = 1; 99 100 for (var k = 0; k < nFilters; ++k) { 101 // Filter an impulse 102 var detune = (filterParameters[k].detune === undefined) ? 0 : filterParameters[k].detune; 103 var frequency = filterParameters[k].cutoff * Math.pow(2, detune / 1200); // Apply detune, converting from Cents. 104 105 var filterCoef = createFilter(filterType, 106 frequency, 107 filterParameters[k].q, 108 filterParameters[k].gain); 109 var y = filterData(filterCoef, data, renderLengthSamples); 110 111 // Accumulate this filtered data into the final output at the desired offset. 112 addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate)); 113 } 114 115 return result; 116 } 117 118 function checkFilterResponse(filterType, filterParameters) { 119 return function(event) { 120 renderedBuffer = event.renderedBuffer; 121 renderedData = renderedBuffer.getChannelData(0); 122 123 reference = generateReference(filterType, filterParameters); 124 125 var len = Math.min(renderedData.length, reference.length); 126 127 var success = true; 128 129 // Maximum error between rendered data and expected data 130 var maxError = 0; 131 132 // Sample offset where the maximum error occurred. 133 var maxPosition = 0; 134 135 // Number of infinities or NaNs that occurred in the rendered data. 136 var invalidNumberCount = 0; 137 138 ok(nFilters == filterParameters.length, "Test wanted " + filterParameters.length + " filters but only " + maxFilters + " allowed."); 139 140 compareChannels(renderedData, reference, len, 0, 0, true); 141 142 // Check for bad numbers in the rendered output too. 143 // There shouldn't be any. 144 for (var k = 0; k < len; ++k) { 145 if (!isValidNumber(renderedData[k])) { 146 ++invalidNumberCount; 147 } 148 } 149 150 ok(invalidNumberCount == 0, "Rendered output has " + invalidNumberCount + " infinities or NaNs."); 151 SimpleTest.finish(); 152 } 153 }