1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4 <script src="resources/audio-testing.js"></script>
5 <script src="resources/biquad-testing.js"></script>
6 <script src="../fast/js/resources/js-test-pre.js"></script>
10 <div id="description"></div>
11 <div id="console"></div>
14 description("Test Biquad getFrequencyResponse() functionality.");
16 // Test the frequency response of a biquad filter. We compute the frequency response for a simple
17 // peaking biquad filter and compare it with the expected frequency response. The actual filter
18 // used doesn't matter since we're testing getFrequencyResponse and not the actual filter output.
19 // The filters are extensively tested in other biquad tests.
23 // The biquad filter node.
26 // The magnitude response of the biquad filter.
29 // The phase response of the biquad filter.
32 // Number of frequency samples to take.
33 var numberOfFrequencies = 1000;
35 // The filter parameters.
36 var filterCutoff = 1000; // Hz.
38 var filterGain = 5; // Decibels.
40 // The maximum allowed error in the magnitude response.
41 var maxAllowedMagError = 5.7e-7;
43 // The maximum allowed error in the phase response.
44 var maxAllowedPhaseError = 4.7e-8;
46 // The magnitudes and phases of the reference frequency response.
50 // The magnitudes and phases of the reference frequency response.
51 var expectedMagnitudes;
54 // Convert frequency in Hz to a normalized frequency between 0 to 1 with 1 corresponding to the
56 function normalizedFrequency(freqHz, sampleRate)
58 var nyquist = sampleRate / 2;
59 return freqHz / nyquist;
62 // Get the filter response at a (normalized) frequency |f| for the filter with coefficients |coef|.
63 function getResponseAt(coef, f)
71 // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
73 // Compute H(exp(i * pi * f)). No native complex numbers in javascript, so break H(exp(i * pi * // f))
74 // in to the real and imaginary parts of the numerator and denominator. Let omega = pi * f.
75 // Then the numerator is
77 // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) + b2 * sin(2 * omega))
79 // and the denominator is
81 // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2 * sin(2 * omega))
83 // Compute the magnitude and phase from the real and imaginary parts.
85 var omega = Math.PI * f;
86 var numeratorReal = b0 + b1 * Math.cos(omega) + b2 * Math.cos(2 * omega);
87 var numeratorImag = -(b1 * Math.sin(omega) + b2 * Math.sin(2 * omega));
88 var denominatorReal = 1 + a1 * Math.cos(omega) + a2 * Math.cos(2 * omega);
89 var denominatorImag = -(a1 * Math.sin(omega) + a2 * Math.sin(2 * omega));
91 var magnitude = Math.sqrt((numeratorReal * numeratorReal + numeratorImag * numeratorImag)
92 / (denominatorReal * denominatorReal + denominatorImag * denominatorImag));
93 var phase = Math.atan2(numeratorImag, numeratorReal) - Math.atan2(denominatorImag, denominatorReal);
95 if (phase >= Math.PI) {
97 } else if (phase <= -Math.PI) {
101 return {magnitude : magnitude, phase : phase};
104 // Compute the reference frequency response for the biquad filter |filter| at the frequency samples
105 // given by |frequencies|.
106 function frequencyResponseReference(filter, frequencies)
108 var sampleRate = filter.context.sampleRate;
109 var normalizedFreq = normalizedFrequency(filter.frequency.value, sampleRate);
110 var filterCoefficients = createFilter(filterTypeIndex[filter.type], normalizedFreq, filter.Q.value, filter.gain.value);
115 for (var k = 0; k < frequencies.length; ++k) {
116 var response = getResponseAt(filterCoefficients, normalizedFrequency(frequencies[k], sampleRate));
117 magnitudes.push(response.magnitude);
118 phases.push(response.phase);
121 return {magnitudes : magnitudes, phases : phases};
124 // Compute a set of linearly spaced frequencies.
125 function createFrequencies(nFrequencies, sampleRate)
127 var frequencies = new Float32Array(nFrequencies);
128 var nyquist = sampleRate / 2;
129 var freqDelta = nyquist / nFrequencies;
131 for (var k = 0; k < nFrequencies; ++k) {
132 frequencies[k] = k * freqDelta;
138 function linearToDecibels(x)
141 return 20 * Math.log(x) / Math.LN10;
147 // Look through the array and find any NaN or infinity. Returns the index of the first occurence or
149 function findBadNumber(signal)
151 for (var k = 0; k < signal.length; ++k) {
152 if (!isValidNumber(signal[k])) {
159 // Compute absolute value of the difference between phase angles, taking into account the wrapping
161 function absolutePhaseDifference(x, y)
163 var diff = Math.abs(x - y);
165 if (diff > Math.PI) {
166 diff = 2 * Math.PI - diff;
171 // Compare the frequency response with our expected response.
172 function compareResponses(filter, frequencies, magResponse, phaseResponse)
174 var expectedResponse = frequencyResponseReference(filter, frequencies);
176 expectedMagnitudes = expectedResponse.magnitudes;
177 expectedPhases = expectedResponse.phases;
179 var n = magResponse.length;
181 var badResponse = false;
183 var maxMagError = -1;
184 var maxMagErrorIndex = -1;
189 hasBadNumber = findBadNumber(magResponse);
190 if (hasBadNumber >= 0) {
191 testFailed("Magnitude response has NaN or infinity at " + hasBadNumber);
196 hasBadNumber = findBadNumber(phaseResponse);
197 if (hasBadNumber >= 0) {
198 testFailed("Phase response has NaN or infinity at " + hasBadNumber);
203 // These aren't testing the implementation itself. Instead, these are sanity checks on the
204 // reference. Failure here does not imply an error in the implementation.
205 hasBadNumber = findBadNumber(expectedMagnitudes);
206 if (hasBadNumber >= 0) {
207 testFailed("Expected magnitude response has NaN or infinity at " + hasBadNumber);
212 hasBadNumber = findBadNumber(expectedPhases);
213 if (hasBadNumber >= 0) {
214 testFailed("Expected phase response has NaN or infinity at " + hasBadNumber);
219 // If we found a NaN or infinity, the following tests aren't very helpful, especially for NaN.
220 // We run them anyway, after printing a warning message.
223 testFailed("NaN or infinity in the actual or expected results makes the following test results suspect.");
227 for (k = 0; k < n; ++k) {
228 var error = Math.abs(linearToDecibels(magResponse[k]) - linearToDecibels(expectedMagnitudes[k]));
229 if (error > maxMagError) {
231 maxMagErrorIndex = k;
235 if (maxMagError > maxAllowedMagError) {
236 var message = "Magnitude error (" + maxMagError + " dB)";
237 message += " exceeded threshold at " + frequencies[maxMagErrorIndex];
238 message += " Hz. Actual: " + linearToDecibels(magResponse[maxMagErrorIndex]);
239 message += " dB, expected: " + linearToDecibels(expectedMagnitudes[maxMagErrorIndex]) + " dB.";
243 testPassed("Magnitude response within acceptable threshold.");
246 var maxPhaseError = -1;
247 var maxPhaseErrorIndex = -1;
249 for (k = 0; k < n; ++k) {
250 var error = absolutePhaseDifference(phaseResponse[k], expectedPhases[k]);
251 if (error > maxPhaseError) {
252 maxPhaseError = error;
253 maxPhaseErrorIndex = k;
257 if (maxPhaseError > maxAllowedPhaseError) {
258 var message = "Phase error (radians) (" + maxPhaseError;
259 message += ") exceeded threshold at " + frequencies[maxPhaseErrorIndex];
260 message += " Hz. Actual: " + phaseResponse[maxPhaseErrorIndex];
261 message += " expected: " + expectedPhases[maxPhaseErrorIndex];
265 testPassed("Phase response within acceptable threshold.");
274 if (window.testRunner) {
275 testRunner.dumpAsText();
276 testRunner.waitUntilDone();
279 window.jsTestIsAsync = true;
281 context = new webkitAudioContext();
283 filter = context.createBiquadFilter();
285 // Arbitrarily test a peaking filter, but any kind of filter can be tested.
286 filter.type = "peaking";
287 filter.frequency.value = filterCutoff;
288 filter.Q.value = filterQ;
289 filter.gain.value = filterGain;
291 var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate);
292 magResponse = new Float32Array(numberOfFrequencies);
293 phaseResponse = new Float32Array(numberOfFrequencies);
295 filter.getFrequencyResponse(frequencies, magResponse, phaseResponse);
296 var success = compareResponses(filter, frequencies, magResponse, phaseResponse);
299 testPassed("Frequency response was correct.");
301 testFailed("Frequency response was incorrect.");
308 successfullyParsed = true;
312 <script src="../fast/js/resources/js-test-post.js"></script>