WebProcessCache should keep track of processes being added
[WebKit-https.git] / LayoutTests / webaudio / biquad-getFrequencyResponse.html
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2 <html>
3 <head>
4 <script src="resources/audio-testing.js"></script>
5 <script src="resources/biquad-testing.js"></script>
6 <script src="../resources/js-test.js"></script>
7 </head>
8
9 <body>
10 <div id="description"></div>
11 <div id="console"></div>
12
13 <script>
14 description("Test Biquad getFrequencyResponse() functionality.");
15
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.
20
21 var context;
22
23 // The biquad filter node.
24 var filter;
25
26 // The magnitude response of the biquad filter.
27 var magResponse;
28
29 // The phase response of the biquad filter.
30 var phaseResponse;
31
32 // Number of frequency samples to take.
33 var numberOfFrequencies = 1000;
34
35 // The filter parameters.
36 var filterCutoff = 1000; // Hz.
37 var filterQ = 1;
38 var filterGain = 5; // Decibels.
39
40 // The maximum allowed error in the magnitude response.
41 var maxAllowedMagError = 5.7e-7;
42
43 // The maximum allowed error in the phase response.
44 var maxAllowedPhaseError = 4.7e-8;
45
46 // The magnitudes and phases of the reference frequency response.
47 var magResponse;
48 var phaseResponse;
49       
50 // The magnitudes and phases of the reference frequency response.
51 var expectedMagnitudes;
52 var expectedPhases;
53
54 // Convert frequency in Hz to a normalized frequency between 0 to 1 with 1 corresponding to the
55 // Nyquist frequency.
56 function normalizedFrequency(freqHz, sampleRate)
57 {
58     var nyquist = sampleRate / 2;
59     return freqHz / nyquist;
60 }
61
62 // Get the filter response at a (normalized) frequency |f| for the filter with coefficients |coef|.
63 function getResponseAt(coef, f)
64 {
65     var b0 = coef.b0;
66     var b1 = coef.b1;
67     var b2 = coef.b2;
68     var a1 = coef.a1;
69     var a2 = coef.a2;
70
71     // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
72     //
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
76     //
77     // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) + b2 * sin(2 * omega))
78     //
79     // and the denominator is
80     //
81     // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2 * sin(2 * omega))
82     //
83     // Compute the magnitude and phase from the real and imaginary parts.
84
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));
90
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);
94     
95     if (phase >= Math.PI) {
96         phase -= 2 * Math.PI;
97     } else if (phase <= -Math.PI) {
98         phase += 2 * Math.PI;
99     }
100    
101     return {magnitude : magnitude, phase : phase}; 
102 }
103
104 // Compute the reference frequency response for the biquad filter |filter| at the frequency samples
105 // given by |frequencies|.
106 function frequencyResponseReference(filter, frequencies)
107 {
108     var sampleRate = filter.context.sampleRate;
109     var normalizedFreq = normalizedFrequency(filter.frequency.value, sampleRate);
110     var filterCoefficients = createFilter(filter.type, normalizedFreq, filter.Q.value, filter.gain.value);
111
112     var magnitudes = [];
113     var phases = [];
114
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);
119     }
120
121     return {magnitudes : magnitudes, phases : phases};
122 }
123
124 // Compute a set of linearly spaced frequencies.
125 function createFrequencies(nFrequencies, sampleRate)
126 {
127     var frequencies = new Float32Array(nFrequencies);
128     var nyquist = sampleRate / 2;
129     var freqDelta = nyquist / nFrequencies;
130
131     for (var k = 0; k < nFrequencies; ++k) {
132         frequencies[k] = k * freqDelta;
133     }
134
135     return frequencies;
136 }
137
138 function linearToDecibels(x)
139 {
140     if (x) {
141         return 20 * Math.log(x) / Math.LN10;
142     } else {
143         return -1000;
144     }
145 }
146
147 // Look through the array and find any NaN or infinity. Returns the index of the first occurence or
148 // -1 if none.
149 function findBadNumber(signal)
150 {
151     for (var k = 0; k < signal.length; ++k) {
152         if (!isValidNumber(signal[k])) {
153            return k;
154         }
155     }
156     return -1;
157 }
158
159 // Compute absolute value of the difference between phase angles, taking into account the wrapping
160 // of phases.
161 function absolutePhaseDifference(x, y)
162 {
163     var diff = Math.abs(x - y);
164     
165     if (diff > Math.PI) {
166         diff = 2 * Math.PI - diff;
167     }
168     return diff;
169 }
170
171 // Compare the frequency response with our expected response.
172 function compareResponses(filter, frequencies, magResponse, phaseResponse)
173 {
174     var expectedResponse = frequencyResponseReference(filter, frequencies);
175
176     expectedMagnitudes = expectedResponse.magnitudes;
177     expectedPhases = expectedResponse.phases;
178
179     var n = magResponse.length;
180     var success = true;
181     var badResponse = false;
182
183     var maxMagError = -1;
184     var maxMagErrorIndex = -1;
185
186     var k;
187     var hasBadNumber;
188
189     hasBadNumber = findBadNumber(magResponse);
190     if (hasBadNumber >= 0) {
191         testFailed("Magnitude response has NaN or infinity at " + hasBadNumber);
192         success = false;
193         badResponse = true;
194     }
195
196     hasBadNumber = findBadNumber(phaseResponse);
197     if (hasBadNumber >= 0) {
198         testFailed("Phase response has NaN or infinity at " + hasBadNumber);
199         success = false;
200         badResponse = true;
201     }
202
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);
208         success = false;
209         badResponse = true;
210     }
211
212     hasBadNumber = findBadNumber(expectedPhases);
213     if (hasBadNumber >= 0) {
214         testFailed("Expected phase response has NaN or infinity at " + hasBadNumber);
215         success = false;
216         badResponse = true;
217     }
218
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.
221
222     if (badResponse) {
223         testFailed("NaN or infinity in the actual or expected results makes the following test results suspect.");
224         success = false;
225     }
226
227     for (k = 0; k < n; ++k) {
228         var error = Math.abs(linearToDecibels(magResponse[k]) - linearToDecibels(expectedMagnitudes[k]));
229         if (error > maxMagError) {
230             maxMagError = error;
231             maxMagErrorIndex = k;
232         }
233     }
234
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.";
240         testFailed(message);
241         success = false;
242     } else {
243         testPassed("Magnitude response within acceptable threshold.");
244     }
245
246     var maxPhaseError = -1;
247     var maxPhaseErrorIndex = -1;
248
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;
254         }
255     }
256
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];
262         testFailed(message);
263         success = false;
264     } else {
265         testPassed("Phase response within acceptable threshold.");
266     }
267
268
269     return success;
270 }
271
272 function runTest()
273 {
274     window.jsTestIsAsync = true;
275
276     context = new webkitAudioContext();
277     
278     filter = context.createBiquadFilter();
279
280     // Arbitrarily test a peaking filter, but any kind of filter can be tested.
281     filter.type = "peaking";
282     filter.frequency.value = filterCutoff;
283     filter.Q.value = filterQ;
284     filter.gain.value = filterGain;
285
286     var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate);
287     magResponse = new Float32Array(numberOfFrequencies);
288     phaseResponse = new Float32Array(numberOfFrequencies);
289
290     filter.getFrequencyResponse(frequencies, magResponse, phaseResponse);
291     var success = compareResponses(filter, frequencies, magResponse, phaseResponse);
292
293     if (success) {
294         testPassed("Frequency response was correct.");
295     } else {
296         testFailed("Frequency response was incorrect.");
297     }
298
299     finishJSTest();
300 }
301
302 runTest();
303
304 </script>
305 </body>
306 </html>