1 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN">
4 <script src=
"resources/compatibility.js"></script>
5 <script src=
"resources/audio-testing.js"></script>
6 <script src=
"resources/biquad-testing.js"></script>
7 <script src=
"../resources/js-test.js"></script>
11 <div id=
"description"></div>
12 <div id=
"console"></div>
15 description("Test Biquad getFrequencyResponse() functionality.");
17 // Test the frequency response of a biquad filter. We compute the frequency response for a simple
18 // peaking biquad filter and compare it with the expected frequency response. The actual filter
19 // used doesn't matter since we're testing getFrequencyResponse and not the actual filter output.
20 // The filters are extensively tested in other biquad tests.
24 // The biquad filter node.
27 // The magnitude response of the biquad filter.
30 // The phase response of the biquad filter.
33 // Number of frequency samples to take.
34 var numberOfFrequencies
= 1000;
36 // The filter parameters.
37 var filterCutoff
= 1000; // Hz.
39 var filterGain
= 5; // Decibels.
41 // The maximum allowed error in the magnitude response.
42 var maxAllowedMagError
= 5.7e-7;
44 // The maximum allowed error in the phase response.
45 var maxAllowedPhaseError
= 4.7e-8;
47 // The magnitudes and phases of the reference frequency response.
51 // The magnitudes and phases of the reference frequency response.
52 var expectedMagnitudes
;
55 // Convert frequency in Hz to a normalized frequency between 0 to 1 with 1 corresponding to the
57 function normalizedFrequency(freqHz
, sampleRate
)
59 var nyquist
= sampleRate
/ 2;
60 return freqHz
/ nyquist
;
63 // Get the filter response at a (normalized) frequency |f| for the filter with coefficients |coef|.
64 function getResponseAt(coef
, f
)
72 // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
74 // Compute H(exp(i * pi * f)). No native complex numbers in javascript, so break H(exp(i * pi * // f))
75 // in to the real and imaginary parts of the numerator and denominator. Let omega = pi * f.
76 // Then the numerator is
78 // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) + b2 * sin(2 * omega))
80 // and the denominator is
82 // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2 * sin(2 * omega))
84 // Compute the magnitude and phase from the real and imaginary parts.
86 var omega
= Math
.PI
* f
;
87 var numeratorReal
= b0
+ b1
* Math
.cos(omega
) + b2
* Math
.cos(2 * omega
);
88 var numeratorImag
= -(b1
* Math
.sin(omega
) + b2
* Math
.sin(2 * omega
));
89 var denominatorReal
= 1 + a1
* Math
.cos(omega
) + a2
* Math
.cos(2 * omega
);
90 var denominatorImag
= -(a1
* Math
.sin(omega
) + a2
* Math
.sin(2 * omega
));
92 var magnitude
= Math
.sqrt((numeratorReal
* numeratorReal
+ numeratorImag
* numeratorImag
)
93 / (denominatorReal
* denominatorReal
+ denominatorImag
* denominatorImag
));
94 var phase
= Math
.atan2(numeratorImag
, numeratorReal
) - Math
.atan2(denominatorImag
, denominatorReal
);
96 if (phase
>= Math
.PI
) {
98 } else if (phase
<= -Math
.PI
) {
102 return {magnitude
: magnitude
, phase
: phase
};
105 // Compute the reference frequency response for the biquad filter |filter| at the frequency samples
106 // given by |frequencies|.
107 function frequencyResponseReference(filter
, frequencies
)
109 var sampleRate
= filter
.context
.sampleRate
;
110 var normalizedFreq
= normalizedFrequency(filter
.frequency
.value
, sampleRate
);
111 var filterCoefficients
= createFilter(filter
.type
, normalizedFreq
, filter
.Q
.value
, filter
.gain
.value
);
116 for (var k
= 0; k
< frequencies
.length
; ++k
) {
117 var response
= getResponseAt(filterCoefficients
, normalizedFrequency(frequencies
[k
], sampleRate
));
118 magnitudes
.push(response
.magnitude
);
119 phases
.push(response
.phase
);
122 return {magnitudes
: magnitudes
, phases
: phases
};
125 // Compute a set of linearly spaced frequencies.
126 function createFrequencies(nFrequencies
, sampleRate
)
128 var frequencies
= new Float32Array(nFrequencies
);
129 var nyquist
= sampleRate
/ 2;
130 var freqDelta
= nyquist
/ nFrequencies
;
132 for (var k
= 0; k
< nFrequencies
; ++k
) {
133 frequencies
[k
] = k
* freqDelta
;
139 function linearToDecibels(x
)
142 return 20 * Math
.log(x
) / Math
.LN10
;
148 // Look through the array and find any NaN or infinity. Returns the index of the first occurence or
150 function findBadNumber(signal
)
152 for (var k
= 0; k
< signal
.length
; ++k
) {
153 if (!isValidNumber(signal
[k
])) {
160 // Compute absolute value of the difference between phase angles, taking into account the wrapping
162 function absolutePhaseDifference(x
, y
)
164 var diff
= Math
.abs(x
- y
);
166 if (diff
> Math
.PI
) {
167 diff
= 2 * Math
.PI
- diff
;
172 // Compare the frequency response with our expected response.
173 function compareResponses(filter
, frequencies
, magResponse
, phaseResponse
)
175 var expectedResponse
= frequencyResponseReference(filter
, frequencies
);
177 expectedMagnitudes
= expectedResponse
.magnitudes
;
178 expectedPhases
= expectedResponse
.phases
;
180 var n
= magResponse
.length
;
182 var badResponse
= false;
184 var maxMagError
= -1;
185 var maxMagErrorIndex
= -1;
190 hasBadNumber
= findBadNumber(magResponse
);
191 if (hasBadNumber
>= 0) {
192 testFailed("Magnitude response has NaN or infinity at " + hasBadNumber
);
197 hasBadNumber
= findBadNumber(phaseResponse
);
198 if (hasBadNumber
>= 0) {
199 testFailed("Phase response has NaN or infinity at " + hasBadNumber
);
204 // These aren't testing the implementation itself. Instead, these are sanity checks on the
205 // reference. Failure here does not imply an error in the implementation.
206 hasBadNumber
= findBadNumber(expectedMagnitudes
);
207 if (hasBadNumber
>= 0) {
208 testFailed("Expected magnitude response has NaN or infinity at " + hasBadNumber
);
213 hasBadNumber
= findBadNumber(expectedPhases
);
214 if (hasBadNumber
>= 0) {
215 testFailed("Expected phase response has NaN or infinity at " + hasBadNumber
);
220 // If we found a NaN or infinity, the following tests aren't very helpful, especially for NaN.
221 // We run them anyway, after printing a warning message.
224 testFailed("NaN or infinity in the actual or expected results makes the following test results suspect.");
228 for (k
= 0; k
< n
; ++k
) {
229 var error
= Math
.abs(linearToDecibels(magResponse
[k
]) - linearToDecibels(expectedMagnitudes
[k
]));
230 if (error
> maxMagError
) {
232 maxMagErrorIndex
= k
;
236 if (maxMagError
> maxAllowedMagError
) {
237 var message
= "Magnitude error (" + maxMagError
+ " dB)";
238 message
+= " exceeded threshold at " + frequencies
[maxMagErrorIndex
];
239 message
+= " Hz. Actual: " + linearToDecibels(magResponse
[maxMagErrorIndex
]);
240 message
+= " dB, expected: " + linearToDecibels(expectedMagnitudes
[maxMagErrorIndex
]) + " dB.";
244 testPassed("Magnitude response within acceptable threshold.");
247 var maxPhaseError
= -1;
248 var maxPhaseErrorIndex
= -1;
250 for (k
= 0; k
< n
; ++k
) {
251 var error
= absolutePhaseDifference(phaseResponse
[k
], expectedPhases
[k
]);
252 if (error
> maxPhaseError
) {
253 maxPhaseError
= error
;
254 maxPhaseErrorIndex
= k
;
258 if (maxPhaseError
> maxAllowedPhaseError
) {
259 var message
= "Phase error (radians) (" + maxPhaseError
;
260 message
+= ") exceeded threshold at " + frequencies
[maxPhaseErrorIndex
];
261 message
+= " Hz. Actual: " + phaseResponse
[maxPhaseErrorIndex
];
262 message
+= " expected: " + expectedPhases
[maxPhaseErrorIndex
];
266 testPassed("Phase response within acceptable threshold.");
275 if (window
.testRunner
) {
276 testRunner
.dumpAsText();
277 testRunner
.waitUntilDone();
280 window
.jsTestIsAsync
= true;
282 context
= new AudioContext();
284 filter
= context
.createBiquadFilter();
286 // Arbitrarily test a peaking filter, but any kind of filter can be tested.
287 filter
.type
= "peaking";
288 filter
.frequency
.value
= filterCutoff
;
289 filter
.Q
.value
= filterQ
;
290 filter
.gain
.value
= filterGain
;
292 var frequencies
= createFrequencies(numberOfFrequencies
, context
.sampleRate
);
293 magResponse
= new Float32Array(numberOfFrequencies
);
294 phaseResponse
= new Float32Array(numberOfFrequencies
);
296 filter
.getFrequencyResponse(frequencies
, magResponse
, phaseResponse
);
297 var success
= compareResponses(filter
, frequencies
, magResponse
, phaseResponse
);
300 testPassed("Frequency response was correct.");
302 testFailed("Frequency response was incorrect.");
309 successfullyParsed
= true;