Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(122)

Side by Side Diff: third_party/WebKit/LayoutTests/webaudio/biquad-automation.html

Issue 2581463002: Refactor WebAudio test directory (Closed)
Patch Set: Use correct path for wav result files Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!doctype html>
2 <html>
3 <head>
4 <title>Biquad Automation Test</title>
5 <script src="../resources/js-test.js"></script>
6 <script src="resources/compatibility.js"></script>
7 <script src="resources/audit-util.js"></script>
8 <script src="resources/audio-testing.js"></script>
9 <script src="resources/biquad-filters.js"></script>
10 <script src="resources/audioparam-testing.js"></script>
11 </head>
12 <body>
13 <script>
14 description("Test Automation of Biquad Filters");
15
16 window.jsTestIsAsync = true;
17
18 // Don't need to run these tests at high sampling rate, so just use a low one to reduce memory
19 // usage and complexity.
20 var sampleRate = 16000;
21
22 // How long to render for each test.
23 var renderDuration = 1;
24 // Where to end the automations. Fairly arbitrary, but must end before
25 // the renderDuration.
26 var automationEndTime = renderDuration / 2;
27
28 var audit = Audit.createTaskRunner();
29
30 // The definition of the linear ramp automation function.
31 function linearRamp(t, v0, v1, t0, t1) {
32 return v0 + (v1 - v0) * (t - t0) / (t1 - t0);
33 }
34
35 // Generate the filter coefficients for the specified filter using the giv en parameters for
36 // the given duration. |filterTypeFunction| is a function that returns th e filter
37 // coefficients for one set of parameters. |parameters| is a property bag that contains the
38 // start and end values (as an array) for each of the biquad attributes. The properties are
39 // |freq|, |Q|, |gain|, and |detune|. |duration| is the number of seconds for which the
40 // coefficients are generated.
41 //
42 // A property bag with properties |b0|, |b1|, |b2|, |a1|, |a2|. Each prop ery is an array
43 // consisting of the coefficients for the time-varying biquad filter.
44 function generateFilterCoefficients(filterTypeFunction, parameters, durati on) {
45 var renderEndFrame = Math.ceil(renderDuration * sampleRate);
46 var endFrame = Math.ceil(duration * sampleRate);
47 var nCoef = renderEndFrame;
48 var b0 = new Float64Array(nCoef);
49 var b1 = new Float64Array(nCoef);
50 var b2 = new Float64Array(nCoef);
51 var a1 = new Float64Array(nCoef);
52 var a2 = new Float64Array(nCoef);
53
54 var k = 0;
55 // If the property is not given, use the defaults.
56 var freqs = parameters.freq || [350, 350];
57 var qs = parameters.Q || [1, 1];
58 var gains = parameters.gain || [0, 0];
59 var detunes = parameters.detune || [0, 0];
60
61 for (var frame = 0; frame <= endFrame; ++frame) {
62 // Apply linear ramp at frame |frame|.
63 var f = linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, durati on);
64 var q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration);
65 var g = linearRamp(frame / sampleRate, gains[0], gains[1], 0, durati on);
66 var d = linearRamp(frame / sampleRate, detunes[0], detunes[1], 0, du ration);
67
68 // Compute actual frequency parameter
69 f = f * Math.pow(2, d / 1200);
70
71 // Compute filter coefficients
72 var coef = filterTypeFunction(f / (sampleRate / 2), q, g);
73 b0[k] = coef.b0;
74 b1[k] = coef.b1;
75 b2[k] = coef.b2;
76 a1[k] = coef.a1;
77 a2[k] = coef.a2;
78 ++k;
79 }
80
81 // Fill the rest of the arrays with the constant value to the end of
82 // the rendering duration.
83 b0.fill(b0[endFrame], endFrame + 1);
84 b1.fill(b1[endFrame], endFrame + 1);
85 b2.fill(b2[endFrame], endFrame + 1);
86 a1.fill(a1[endFrame], endFrame + 1);
87 a2.fill(a2[endFrame], endFrame + 1);
88
89 return {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2};
90 }
91
92 // Apply the given time-varying biquad filter to the given signal, |signal |. |coef| should be
93 // the time-varying coefficients of the filter, as returned by |generateFi lterCoefficients|.
94 function timeVaryingFilter(signal, coef) {
95 var length = signal.length;
96 // Use double precision for the internal computations.
97 var y = new Float64Array(length);
98
99 // Prime the pump. (Assumes the signal has length >= 2!)
100 y[0] = coef.b0[0] * signal[0];
101 y[1] = coef.b0[1] * signal[1] + coef.b1[1] * signal[0] - coef.a1[1] * y[ 0];
102
103 for (var n = 2; n < length; ++n) {
104 y[n] = coef.b0[n] * signal[n] + coef.b1[n] * signal[n-1] + coef.b2[n] * signal[n-2];
105 y[n] -= coef.a1[n] * y[n-1] + coef.a2[n] * y[n-2];
106 }
107
108 // But convert the result to single precision for comparison.
109 return y.map(Math.fround);
110 }
111
112 // Configure the audio graph using |context|. Returns the biquad filter n ode and the
113 // AudioBuffer used for the source.
114 function configureGraph(context, toneFrequency) {
115 // The source is just a simple sine wave.
116 var src = context.createBufferSource();
117 var b = context.createBuffer(1, renderDuration * sampleRate, sampleRate) ;
118 var data = b.getChannelData(0);
119 var omega = 2 * Math.PI * toneFrequency / sampleRate;
120 for (var k = 0; k < data.length; ++k) {
121 data[k] = Math.sin(omega * k);
122 }
123 src.buffer = b;
124 var f = context.createBiquadFilter();
125 src.connect(f);
126 f.connect(context.destination);
127
128 src.start();
129
130 return {filter: f, source: b};
131 }
132
133 function createFilterVerifier(filterCreator, threshold, parameters, input, message) {
134 return function (resultBuffer) {
135 var actual = resultBuffer.getChannelData(0);
136 var coefs = generateFilterCoefficients(filterCreator, parameters, auto mationEndTime);
137
138 reference = timeVaryingFilter(input, coefs);
139
140 Should(message, actual, {
141 verbose: true
142 }).beCloseToArray(reference, threshold);
143 };
144 }
145
146 // Automate just the frequency parameter. A bandpass filter is used where the center
147 // frequency is swept across the source (which is a simple tone).
148 audit.defineTask("automate-freq", function (done) {
149 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa mpleRate);
150
151 // Center frequency of bandpass filter and also the frequency of the tes t tone.
152 var centerFreq = 10*440;
153
154 // Sweep the frequency +/- 5*440 Hz from the center. This should cause
155 // the output to be low at the beginning and end of the test where the
156 // tone is outside the pass band of the filter, but high in the middle
157 // of the automation time where the tone is near the center of the pass
158 // band. Make sure the frequency sweep stays inside the Nyquist
159 // frequency.
160 var parameters = {
161 freq: [centerFreq - 5*440, centerFreq + 5*440]
162 }
163 var graph = configureGraph(context, centerFreq);
164 var f = graph.filter;
165 var b = graph.source;
166
167 f.type = "bandpass";
168 f.frequency.setValueAtTime(parameters.freq[0], 0);
169 f.frequency.linearRampToValueAtTime(parameters.freq[1], automationEndTim e);
170
171 context.startRendering()
172 .then(createFilterVerifier(createBandpassFilter, 4.8429e-6, parameters , b.getChannelData(0),
173 "Output of bandpass filter with frequency automation"))
174 .then(done);
175 });
176
177 // Automate just the Q parameter. A bandpass filter is used where the Q o f the filter is
178 // swept.
179 audit.defineTask("automate-q", function (done) {
180 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa mpleRate);
181
182 // The frequency of the test tone.
183 var centerFreq = 440;
184
185 // Sweep the Q paramter between 1 and 200. This will cause the output o f the filter to pass
186 // most of the tone at the beginning to passing less of the tone at the end. This is
187 // because we set center frequency of the bandpass filter to be slightly off from the actual
188 // tone.
189 var parameters = {
190 Q: [1, 200],
191 // Center frequency of the bandpass filter is just 25 Hz above the ton e frequency.
192 freq: [centerFreq + 25, centerFreq + 25]
193 };
194 var graph = configureGraph(context, centerFreq);
195 var f = graph.filter;
196 var b = graph.source;
197
198 f.type = "bandpass";
199 f.frequency.value = parameters.freq[0];
200 f.Q.setValueAtTime(parameters.Q[0], 0);
201 f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime);
202
203 context.startRendering()
204 .then(createFilterVerifier(createBandpassFilter, 1.1062e-6, parameters , b.getChannelData(0),
205 "Output of bandpass filter with Q automation"))
206 .then(done);
207 });
208
209 // Automate just the gain of the lowshelf filter. A test tone will be in the lowshelf part of
210 // the filter. The output will vary as the gain of the lowshelf is change d.
211 audit.defineTask("automate-gain", function (done) {
212 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa mpleRate);
213
214 // Frequency of the test tone.
215 var centerFreq = 440;
216
217 // Set the cutoff frequency of the lowshelf to be significantly higher t han the test tone.
218 // Sweep the gain from 20 dB to -20 dB. (We go from 20 to -20 to easily verify that the
219 // filter didn't go unstable.)
220 var parameters = {
221 freq: [3500, 3500],
222 gain: [20, -20]
223 }
224 var graph = configureGraph(context, centerFreq);
225 var f = graph.filter;
226 var b = graph.source;
227
228 f.type = "lowshelf";
229 f.frequency.value = parameters.freq[0];
230 f.gain.setValueAtTime(parameters.gain[0], 0);
231 f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime);
232
233 context.startRendering()
234 .then(createFilterVerifier(createLowShelfFilter, 1.4306e-5, parameters , b.getChannelData(0),
235 "Output of lowshelf filter with gain automation"))
236 .then(done);
237 });
238
239 // Automate just the detune parameter. Basically the same test as for the frequncy parameter
240 // but we just use the detune parameter to modulate the frequency paramete r.
241 audit.defineTask("automate-detune", function (done) {
242 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa mpleRate);
243 var centerFreq = 10*440;
244 var parameters = {
245 freq: [centerFreq, centerFreq],
246 detune: [-10*1200, 10*1200]
247 };
248 var graph = configureGraph(context, centerFreq);
249 var f = graph.filter;
250 var b = graph.source;
251
252 f.type = "bandpass";
253 f.frequency.value = parameters.freq[0];
254 f.detune.setValueAtTime(parameters.detune[0], 0);
255 f.detune.linearRampToValueAtTime(parameters.detune[1], automationEndTime );
256
257 context.startRendering()
258 .then(createFilterVerifier(createBandpassFilter, 2.9535e-5, parameters , b.getChannelData(0),
259 "Output of bandpass filter with detune automation"))
260 .then(done);
261 });
262
263 // Automate all of the filter parameters at once. This is a basic check t hat everything is
264 // working. A peaking filter is used because it uses all of the parameter s.
265 audit.defineTask("automate-all", function (done) {
266 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa mpleRate);
267 var graph = configureGraph(context, 10*440);
268 var f = graph.filter;
269 var b = graph.source;
270
271 // Sweep all of the filter parameters. These are pretty much arbitrary.
272 var parameters = {
273 freq: [8000, 100],
274 Q: [f.Q.value, .0001],
275 gain: [f.gain.value, 20],
276 detune: [2400, -2400]
277 };
278
279 f.type = "peaking";
280 // Set starting points for all parameters of the filter. Start at 10 kH z for the center
281 // frequency, and the defaults for Q and gain.
282 f.frequency.setValueAtTime(parameters.freq[0], 0);
283 f.Q.setValueAtTime(parameters.Q[0], 0);
284 f.gain.setValueAtTime(parameters.gain[0], 0);
285 f.detune.setValueAtTime(parameters.detune[0], 0);
286
287 // Linear ramp each parameter
288 f.frequency.linearRampToValueAtTime(parameters.freq[1], automationEndTim e);
289 f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime);
290 f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime);
291 f.detune.linearRampToValueAtTime(parameters.detune[1], automationEndTime );
292
293 context.startRendering()
294 .then(createFilterVerifier(createPeakingFilter, 6.2907e-4, parameters, b.getChannelData(0),
295 "Output of peaking filter with automation of all parameters"))
296 .then(done);
297 });
298
299 // Test that modulation of the frequency parameter of the filter works. A sinusoid of 440 Hz
300 // is the test signal that is applied to a bandpass biquad filter. The fr equency parameter of
301 // the filter is modulated by a sinusoid at 103 Hz, and the frequency modu lation varies from
302 // 116 to 412 Hz. (This test was taken from the description in
303 // https://github.com/WebAudio/web-audio-api/issues/509#issuecomment-94731 355)
304 audit.defineTask("modulation", function (done) {
305 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa mpleRate);
306
307 // Create a graph with the sinusoidal source at 440 Hz as the input to a biquad filter.
308 var graph = configureGraph(context, 440);
309 var f = graph.filter;
310 var b = graph.source;
311
312 f.type = "bandpass";
313 f.Q.value = 5;
314 f.frequency.value = 264;
315
316 // Create the modulation source, a sinusoid with frequency 103 Hz and am plitude 148. (The
317 // amplitude of 148 is added to the filter's frequency value of 264 to p roduce a sinusoidal
318 // modulation of the frequency parameter from 116 to 412 Hz.)
319 var mod = context.createBufferSource();
320 var mbuffer = context.createBuffer(1, renderDuration * sampleRate, sampl eRate);
321 var d = mbuffer.getChannelData(0);
322 var omega = 2 * Math.PI * 103 / sampleRate;
323 for (var k = 0; k < d.length; ++k) {
324 d[k] = 148 * Math.sin(omega * k);
325 }
326 mod.buffer = mbuffer;
327
328 mod.connect(f.frequency);
329
330 mod.start();
331 context.startRendering()
332 .then(function (resultBuffer) {
333 var actual = resultBuffer.getChannelData(0);
334 // Compute the filter coefficients using the mod sine wave
335
336 var endFrame = Math.ceil(renderDuration * sampleRate);
337 var nCoef = endFrame;
338 var b0 = new Float64Array(nCoef);
339 var b1 = new Float64Array(nCoef);
340 var b2 = new Float64Array(nCoef);
341 var a1 = new Float64Array(nCoef);
342 var a2 = new Float64Array(nCoef);
343
344 // Generate the filter coefficients when the frequency varies from 116 to 248 Hz using
345 // the 103 Hz sinusoid.
346 for (var k = 0; k < nCoef; ++k) {
347 var freq = f.frequency.value + d[k];
348 var c = createBandpassFilter(freq / (sampleRate / 2), f.Q.value, f.gain.value);
349 b0[k] = c.b0;
350 b1[k] = c.b1;
351 b2[k] = c.b2;
352 a1[k] = c.a1;
353 a2[k] = c.a2;
354 }
355 reference = timeVaryingFilter(b.getChannelData(0),
356 {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2});
357
358 Should("Output of bandpass filter with sinusoidal modulation of ban dpass center frequency",
359 actual)
360 .beCloseToArray(reference, 3.9787e-5);
361 })
362 .then(done);
363 });
364
365 // All done!
366 audit.defineTask("finish", function (done) {
367 finishJSTest();
368 done();
369 });
370
371 audit.runTasks();
372 </script>
373 </body>
374 </html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698