|
7 | 7 | <script src="/resources/testharness.js"></script>
|
8 | 8 | <script src="/resources/testharnessreport.js"></script>
|
9 | 9 | <script src="../../resources/audit-util.js"></script>
|
10 |
| - <script src="../../resources/audit.js"></script> |
11 | 10 | <script src="../../resources/biquad-filters.js"></script>
|
12 | 11 | </head>
|
13 | 12 | <body>
|
14 |
| - <script id="layout-test-code"> |
15 |
| - let sampleRate = 48000; |
| 13 | + <script> |
| 14 | + |
| 15 | + const sampleRate = 48000; |
16 | 16 | // Some short duration; we're not actually looking at the rendered output.
|
17 |
| - let testDurationSec = 0.01; |
| 17 | + const testDurationSec = 0.01; |
18 | 18 |
|
19 | 19 | // Number of frequency samples to take.
|
20 |
| - let numberOfFrequencies = 1000; |
21 |
| - |
22 |
| - let audit = Audit.createTaskRunner(); |
23 |
| - |
| 20 | + const numberOfFrequencies = 1000; |
24 | 21 |
|
25 | 22 | // Compute a set of linearly spaced frequencies.
|
26 | 23 | function createFrequencies(nFrequencies, sampleRate) {
|
|
35 | 32 | return frequencies;
|
36 | 33 | }
|
37 | 34 |
|
38 |
| - audit.define('1-pole IIR', (task, should) => { |
39 |
| - let context = new OfflineAudioContext( |
| 35 | + test(t => { |
| 36 | + const context = new OfflineAudioContext( |
40 | 37 | 1, testDurationSec * sampleRate, sampleRate);
|
| 38 | + const iir = context.createIIRFilter([1], [1, -0.9]); |
41 | 39 |
|
42 |
| - let iir = context.createIIRFilter([1], [1, -0.9]); |
43 |
| - let frequencies = |
44 |
| - createFrequencies(numberOfFrequencies, context.sampleRate); |
45 |
| - |
46 |
| - let iirMag = new Float32Array(numberOfFrequencies); |
47 |
| - let iirPhase = new Float32Array(numberOfFrequencies); |
48 |
| - let trueMag = new Float32Array(numberOfFrequencies); |
49 |
| - let truePhase = new Float32Array(numberOfFrequencies); |
| 40 | + const frequencies = createFrequencies( |
| 41 | + numberOfFrequencies, context.sampleRate); |
| 42 | + const iirMag = new Float32Array(numberOfFrequencies); |
| 43 | + const iirPhase = new Float32Array(numberOfFrequencies); |
| 44 | + const trueMag = new Float32Array(numberOfFrequencies); |
| 45 | + const truePhase = new Float32Array(numberOfFrequencies); |
50 | 46 |
|
51 | 47 | // The IIR filter is
|
52 | 48 | // H(z) = 1/(1 - 0.9*z^(-1)).
|
|
60 | 56 | // The phase is
|
61 | 57 | // arg(H(exp(j*w)) = atan(0.9*sin(w)/(.9*cos(w)-1))
|
62 | 58 |
|
63 |
| - let frequencyScale = Math.PI / (sampleRate / 2); |
64 |
| - |
| 59 | + const frequencyScale = Math.PI / (sampleRate / 2); |
65 | 60 | for (let k = 0; k < frequencies.length; ++k) {
|
66 |
| - let omega = frequencyScale * frequencies[k]; |
| 61 | + const omega = frequencyScale * frequencies[k]; |
67 | 62 | trueMag[k] = 1 / Math.sqrt(1.81 - 1.8 * Math.cos(omega));
|
68 | 63 | truePhase[k] =
|
69 | 64 | Math.atan(0.9 * Math.sin(omega) / (0.9 * Math.cos(omega) - 1));
|
|
72 | 67 | iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
|
73 | 68 |
|
74 | 69 | // Thresholds were experimentally determined.
|
75 |
| - should(iirMag, '1-pole IIR Magnitude Response') |
76 |
| - .beCloseToArray(trueMag, {absoluteThreshold: 2.8611e-6}); |
77 |
| - should(iirPhase, '1-pole IIR Phase Response') |
78 |
| - .beCloseToArray(truePhase, {absoluteThreshold: 1.7882e-7}); |
79 |
| - |
80 |
| - task.done(); |
81 |
| - }); |
82 |
| - |
83 |
| - audit.define('compare IIR and biquad', (task, should) => { |
| 70 | + assert_close_to_array( |
| 71 | + iirMag, trueMag, 2.8611e-6, '1‑pole IIR magnitude response ' + |
| 72 | + 'should be closed to calculated magnitude response'); |
| 73 | + assert_close_to_array( |
| 74 | + iirPhase, truePhase, 1.7882e-7, '1‑pole IIR phase response ' + |
| 75 | + ' should be closed to calculated phase response'); |
| 76 | + }, '1‑pole IIR getFrequencyResponse() matches analytic response'); |
| 77 | + |
| 78 | + test(t => { |
84 | 79 | // Create an IIR filter equivalent to the biquad filter. Compute the
|
85 | 80 | // frequency response for both and verify that they are the same.
|
86 |
| - let context = new OfflineAudioContext( |
| 81 | + const context = new OfflineAudioContext( |
87 | 82 | 1, testDurationSec * sampleRate, sampleRate);
|
88 | 83 |
|
89 |
| - let biquad = context.createBiquadFilter(); |
90 |
| - let coef = createFilter( |
91 |
| - biquad.type, biquad.frequency.value / (context.sampleRate / 2), |
92 |
| - biquad.Q.value, biquad.gain.value); |
| 84 | + const biquad = context.createBiquadFilter(); |
| 85 | + const coef = createFilter( |
| 86 | + biquad.type, |
| 87 | + biquad.frequency.value / (context.sampleRate / 2), |
| 88 | + biquad.Q.value, |
| 89 | + biquad.gain.value); |
93 | 90 |
|
94 |
| - let iir = context.createIIRFilter( |
| 91 | + const iir = context.createIIRFilter( |
95 | 92 | [coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
|
96 | 93 |
|
97 |
| - let frequencies = |
98 |
| - createFrequencies(numberOfFrequencies, context.sampleRate); |
99 |
| - let biquadMag = new Float32Array(numberOfFrequencies); |
100 |
| - let biquadPhase = new Float32Array(numberOfFrequencies); |
101 |
| - let iirMag = new Float32Array(numberOfFrequencies); |
102 |
| - let iirPhase = new Float32Array(numberOfFrequencies); |
| 94 | + const frequencies = createFrequencies( |
| 95 | + numberOfFrequencies, context.sampleRate); |
| 96 | + const biquadMag = new Float32Array(numberOfFrequencies); |
| 97 | + const biquadPhase = new Float32Array(numberOfFrequencies); |
| 98 | + const iirMag = new Float32Array(numberOfFrequencies); |
| 99 | + const iirPhase = new Float32Array(numberOfFrequencies); |
103 | 100 |
|
104 | 101 | biquad.getFrequencyResponse(frequencies, biquadMag, biquadPhase);
|
105 | 102 | iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
|
106 |
| - |
107 |
| - // Thresholds were experimentally determined. |
108 |
| - should(iirMag, 'IIR Magnitude Response').beCloseToArray(biquadMag, { |
109 |
| - absoluteThreshold: 2.7419e-5 |
110 |
| - }); |
111 |
| - should(iirPhase, 'IIR Phase Response').beCloseToArray(biquadPhase, { |
112 |
| - absoluteThreshold: 2.7657e-5 |
113 |
| - }); |
114 |
| - |
115 |
| - task.done(); |
116 |
| - }); |
117 |
| - |
118 |
| - audit.define( |
119 |
| - { |
120 |
| - label: 'getFrequencyResponse', |
121 |
| - description: 'Test out-of-bounds frequency values' |
122 |
| - }, |
123 |
| - (task, should) => { |
124 |
| - let context = new OfflineAudioContext(1, 1, sampleRate); |
125 |
| - let filter = new IIRFilterNode( |
126 |
| - context, {feedforward: [1], feedback: [1, -.9]}); |
127 |
| - |
128 |
| - // Frequencies to test. These are all outside the valid range of |
129 |
| - // frequencies of 0 to Nyquist. |
130 |
| - let freq = new Float32Array(2); |
131 |
| - freq[0] = -1; |
132 |
| - freq[1] = context.sampleRate / 2 + 1; |
133 |
| - |
134 |
| - let mag = new Float32Array(freq.length); |
135 |
| - let phase = new Float32Array(freq.length); |
136 |
| - |
137 |
| - filter.getFrequencyResponse(freq, mag, phase); |
138 |
| - |
139 |
| - // Verify that the returned magnitude and phase entries are alL NaN |
140 |
| - // since the frequencies are outside the valid range |
141 |
| - for (let k = 0; k < mag.length; ++k) { |
142 |
| - should(mag[k], |
143 |
| - 'Magnitude response at frequency ' + freq[k]) |
144 |
| - .beNaN(); |
145 |
| - } |
146 |
| - |
147 |
| - for (let k = 0; k < phase.length; ++k) { |
148 |
| - should(phase[k], |
149 |
| - 'Phase response at frequency ' + freq[k]) |
150 |
| - .beNaN(); |
151 |
| - } |
152 |
| - |
153 |
| - task.done(); |
154 |
| - }); |
155 |
| - |
156 |
| - audit.run(); |
| 103 | + // Thresholds were experimentally determined. |
| 104 | + assert_close_to_array( |
| 105 | + iirMag, biquadMag, 2.7419e-5, 'IIR magnitude response should be' + |
| 106 | + 'close to Biquad magnitude response'); |
| 107 | + assert_close_to_array( |
| 108 | + iirPhase, biquadPhase, 2.7657e-5, 'IIR phase response should be' + |
| 109 | + 'close to Biquad phase response'); |
| 110 | + }, 'IIR filter equivalent to biquad has matching frequency response'); |
| 111 | + |
| 112 | + test(t => { |
| 113 | + const context = new OfflineAudioContext(1, 1, sampleRate); |
| 114 | + const filter = new IIRFilterNode( |
| 115 | + context, {feedforward: [1], feedback: [1, -0.9]}); |
| 116 | + // Frequencies to test. These are all outside the valid range of |
| 117 | + // frequencies of 0 to Nyquist. |
| 118 | + const freq = new Float32Array([-1, context.sampleRate / 2 + 1]); |
| 119 | + const mag = new Float32Array(freq.length); |
| 120 | + const phase = new Float32Array(freq.length); |
| 121 | + |
| 122 | + filter.getFrequencyResponse(freq, mag, phase); |
| 123 | + |
| 124 | + // Verify that the returned magnitude and phase entries are all NaN |
| 125 | + // since the frequencies are outside the valid range |
| 126 | + for (let k = 0; k < freq.length; ++k) { |
| 127 | + assert_true( |
| 128 | + Number.isNaN(mag[k]), |
| 129 | + `Magnitude response at f=${freq[k]} should be NaN`); |
| 130 | + assert_true( |
| 131 | + Number.isNaN(phase[k]), |
| 132 | + `Phase response at f=${freq[k]} should be NaN`); |
| 133 | + } |
| 134 | + }, 'Out‑of‑range frequency values yield NaN responses'); |
157 | 135 | </script>
|
158 | 136 | </body>
|
159 | 137 | </html>
|
0 commit comments