OLD | NEW |
| (Empty) |
1 <!doctype html> | |
2 <html> | |
3 <head> | |
4 <script src="../resources/js-test.js"></script> | |
5 <script src="resources/compatibility.js"></script> | |
6 <script src="resources/audit-util.js"></script> | |
7 <script src="resources/audio-testing.js"></script> | |
8 <title>Test AudioParam Nominal Range Values</title> | |
9 </head> | |
10 | |
11 <body> | |
12 <script> | |
13 description("Test AudioParam Nominal Range Values."); | |
14 window.jsTestIsAsync = true; | |
15 | |
16 // Some arbitrary sample rate for the offline context. | |
17 var sampleRate = 48000; | |
18 | |
19 // The actual offline context | |
20 var context; | |
21 | |
22 // The set of all methods that we've tested for verifying that we tested a
ll of the necessary | |
23 // objects. | |
24 var testedMethods = new Set(); | |
25 | |
26 // The most positive single float value (the value just before infinity).
Be careful when | |
27 // changing this value! Javascript only uses double floats, so the value
here should be the | |
28 // max single-float value, converted directly to a double-float value. Th
is also depends on | |
29 // Javascript reading this value and producing the desired double-float va
lue correctly. | |
30 var mostPositiveFloat = 3.4028234663852886e38; | |
31 | |
32 var audit = Audit.createTaskRunner(); | |
33 | |
34 // Array describing the tests that should be run. | |
35 var testConfigs = [{ | |
36 // The name of the method to create the particular node to be tested. | |
37 creator: "createGain", | |
38 | |
39 // Any args to pass to the creator function. | |
40 args: [], | |
41 | |
42 // The min/max limits for each AudioParam of the node. This is a dictio
nary whose keys are | |
43 // the names of each AudioParam in the node. Don't define this if the n
ode doesn't have any | |
44 // AudioParam attributes. | |
45 limits: { | |
46 gain: { | |
47 // The expected min and max values for this AudioParam. | |
48 minValue: -mostPositiveFloat, | |
49 maxValue: mostPositiveFloat | |
50 } | |
51 } | |
52 }, { | |
53 creator: "createDelay", | |
54 // Just specify a non-default value for the maximum delay so we can make
sure the limits are | |
55 // set correctly. | |
56 args: [1.5], | |
57 limits: { | |
58 delayTime: { | |
59 minValue: 0, | |
60 maxValue: 1.5 | |
61 } | |
62 } | |
63 }, { | |
64 creator: "createBufferSource", | |
65 args: [], | |
66 limits: { | |
67 playbackRate: { | |
68 minValue: -mostPositiveFloat, | |
69 maxValue: mostPositiveFloat | |
70 }, | |
71 detune: { | |
72 minValue: -mostPositiveFloat, | |
73 maxValue: mostPositiveFloat | |
74 } | |
75 } | |
76 }, { | |
77 creator: "createStereoPanner", | |
78 args: [], | |
79 limits: { | |
80 pan: { | |
81 minValue: -1, | |
82 maxValue: 1 | |
83 } | |
84 } | |
85 }, { | |
86 creator: "createDynamicsCompressor", | |
87 args: [], | |
88 // Do not set limits for reduction; it's currently an AudioParam but sh
ould be a float. | |
89 // So let the test fail for reduction. When reduction is changed, this
test will then | |
90 // correctly pass. | |
91 limits: { | |
92 threshold: { | |
93 minValue: -100, | |
94 maxValue: 0 | |
95 }, | |
96 knee: { | |
97 minValue: 0, | |
98 maxValue: 40 | |
99 }, | |
100 ratio: { | |
101 minValue: 1, | |
102 maxValue: 20 | |
103 }, | |
104 attack: { | |
105 minValue: 0, | |
106 maxValue: 1 | |
107 }, | |
108 release: { | |
109 minValue: 0, | |
110 maxValue: 1 | |
111 } | |
112 } | |
113 }, | |
114 { | |
115 creator: "createBiquadFilter", | |
116 args: [], | |
117 limits: { | |
118 gain: { | |
119 minValue: -mostPositiveFloat, | |
120 maxValue: mostPositiveFloat | |
121 }, | |
122 Q: { | |
123 minValue: -mostPositiveFloat, | |
124 maxValue: mostPositiveFloat | |
125 }, | |
126 frequency: { | |
127 minValue: 0, | |
128 maxValue: sampleRate / 2 | |
129 }, | |
130 detune: { | |
131 minValue: -mostPositiveFloat, | |
132 maxValue: mostPositiveFloat | |
133 } | |
134 } | |
135 }, { | |
136 creator: "createOscillator", | |
137 args: [], | |
138 limits: { | |
139 frequency: { | |
140 minValue: -sampleRate / 2, | |
141 maxValue: sampleRate / 2 | |
142 }, | |
143 detune: { | |
144 minValue: -mostPositiveFloat, | |
145 maxValue: mostPositiveFloat | |
146 } | |
147 } | |
148 }, { | |
149 creator: "createPanner", | |
150 args: [], | |
151 limits : { | |
152 positionX: { | |
153 minValue: -mostPositiveFloat, | |
154 maxValue: mostPositiveFloat, | |
155 }, | |
156 positionY: { | |
157 minValue: -mostPositiveFloat, | |
158 maxValue: mostPositiveFloat, | |
159 }, | |
160 positionZ: { | |
161 minValue: -mostPositiveFloat, | |
162 maxValue: mostPositiveFloat, | |
163 }, | |
164 orientationX: { | |
165 minValue: -mostPositiveFloat, | |
166 maxValue: mostPositiveFloat, | |
167 }, | |
168 orientationY: { | |
169 minValue: -mostPositiveFloat, | |
170 maxValue: mostPositiveFloat, | |
171 }, | |
172 orientationZ: { | |
173 minValue: -mostPositiveFloat, | |
174 maxValue: mostPositiveFloat, | |
175 } | |
176 }, | |
177 }, { | |
178 creator: "createConstantSource", | |
179 args: [], | |
180 limits: { | |
181 offset: { | |
182 minValue: -mostPositiveFloat, | |
183 maxValue: mostPositiveFloat | |
184 } | |
185 } | |
186 }, | |
187 // These nodes don't have AudioParams, but we want to test them anyway. A
ny arguments for the | |
188 // constructor are pretty much arbitrary; they just need to be valid. | |
189 { | |
190 creator: "createBuffer", | |
191 args: [1, 1, sampleRate], | |
192 }, { | |
193 creator: "createIIRFilter", | |
194 args: [[1,2],[3,4]] | |
195 }, { | |
196 creator: "createWaveShaper", | |
197 args: [], | |
198 }, { | |
199 creator: "createConvolver", | |
200 args: [], | |
201 }, { | |
202 creator: "createAnalyser", | |
203 args: [], | |
204 }, { | |
205 creator: "createScriptProcessor", | |
206 args: [0], | |
207 }, { | |
208 creator: "createPeriodicWave", | |
209 args: [Float32Array.from([0, 0]), Float32Array.from([1, 0])], | |
210 }, { | |
211 creator: "createChannelSplitter", | |
212 args: [], | |
213 }, { | |
214 creator: "createChannelMerger", | |
215 args: [], | |
216 }, { | |
217 creator: "createMediaElementSource", | |
218 args: [new Audio()] | |
219 },{ | |
220 creator: "createMediaStreamDestination", | |
221 args: [] | |
222 } | |
223 // Can't currently test MediaStreamSource because we're using an offline | |
224 // context. | |
225 ]; | |
226 | |
227 // Create the context so we can use it in the following test. | |
228 audit.defineTask("initialize", function (done) { | |
229 // Just any context so that we can create the nodes. | |
230 context = new OfflineAudioContext(1, 1, sampleRate); | |
231 done(); | |
232 }); | |
233 | |
234 // Create a task for each entry in testConfigs | |
235 for (var test in testConfigs) { | |
236 var config = testConfigs[test] | |
237 audit.defineTask(config.creator, (function (c) { | |
238 return function (done) { | |
239 var node = context[c.creator](...c.args); | |
240 testLimits(c.creator, node, c.limits); | |
241 done(); | |
242 }; | |
243 })(config)); | |
244 } | |
245 | |
246 // Test the AudioListener params that were added for the automated Panner | |
247 audit.defineTask("AudioListener", function (done) { | |
248 testLimits("", context.listener, { | |
249 positionX: { | |
250 minValue: -mostPositiveFloat, | |
251 maxValue: mostPositiveFloat, | |
252 }, | |
253 positionY: { | |
254 minValue: -mostPositiveFloat, | |
255 maxValue: mostPositiveFloat, | |
256 }, | |
257 positionZ: { | |
258 minValue: -mostPositiveFloat, | |
259 maxValue: mostPositiveFloat, | |
260 }, | |
261 forwardX: { | |
262 minValue: -mostPositiveFloat, | |
263 maxValue: mostPositiveFloat, | |
264 }, | |
265 forwardY: { | |
266 minValue: -mostPositiveFloat, | |
267 maxValue: mostPositiveFloat, | |
268 }, | |
269 forwardZ: { | |
270 minValue: -mostPositiveFloat, | |
271 maxValue: mostPositiveFloat, | |
272 }, | |
273 upX: { | |
274 minValue: -mostPositiveFloat, | |
275 maxValue: mostPositiveFloat, | |
276 }, | |
277 upY: { | |
278 minValue: -mostPositiveFloat, | |
279 maxValue: mostPositiveFloat, | |
280 }, | |
281 upZ: { | |
282 minValue: -mostPositiveFloat, | |
283 maxValue: mostPositiveFloat, | |
284 } | |
285 }); | |
286 done(); | |
287 }); | |
288 | |
289 // Verify that we have tested all the create methods available on the cont
ext. | |
290 audit.defineTask("verifyTests", function (done) { | |
291 var allNodes = new Set(); | |
292 // Create the set of all "create" methods from the context. | |
293 for (var method in context) { | |
294 if (typeof context[method] === "function" && method.substring(0, 6) ==
= "create") { | |
295 allNodes.add(method); | |
296 } | |
297 } | |
298 | |
299 // Compute the difference between the set of all create methods on the c
ontext and the set | |
300 // of tests that we've run. | |
301 var diff = new Set([...allNodes].filter(x => !testedMethods.has(x))); | |
302 | |
303 // Can't currently test a MediaStreamSourceNode, so remove it from the d
iff set. | |
304 diff.delete("createMediaStreamSource"); | |
305 | |
306 // It's a test failure if we didn't test all of the create methods in th
e context (except | |
307 // createMediaStreamSource, of course). | |
308 if (diff.size) { | |
309 var output = []; | |
310 for (let item of diff) | |
311 output.push(" " + item.substring(6)); | |
312 testFailed("These nodes were not tested:" + output + "\n"); | |
313 } else { | |
314 testPassed("All nodes were tested.\n"); | |
315 } | |
316 | |
317 done(); | |
318 }); | |
319 | |
320 // Simple test of a few automation methods to verify we get warnings. | |
321 audit.defineTask("automation", function (done) { | |
322 // Just use a DelayNode for testing because the audio param has finite l
imits. | |
323 var d = context.createDelay(); | |
324 | |
325 // The console output should have the warnings that we're interested in. | |
326 d.delayTime.setValueAtTime(-1, 0); | |
327 d.delayTime.linearRampToValueAtTime(2, 1); | |
328 d.delayTime.exponentialRampToValueAtTime(3, 2); | |
329 d.delayTime.setTargetAtTime(-1, 3, .1); | |
330 d.delayTime.setValueCurveAtTime(Float32Array.from([.1, .2, 1.5, -1]), 4,
.1); | |
331 done(); | |
332 }); | |
333 | |
334 // All done! | |
335 audit.defineTask("finish", function (done) { | |
336 finishJSTest(); | |
337 done(); | |
338 }); | |
339 | |
340 audit.runTasks(); | |
341 | |
342 // Is |object| an AudioParam? We determine this by checking the construct
or name. | |
343 function isAudioParam(object) { | |
344 return object && object.constructor.name === "AudioParam"; | |
345 } | |
346 | |
347 // Does |limitOptions| exist and does it have valid values for the expecte
d min and max | |
348 // values? | |
349 function hasValidLimits(limitOptions) { | |
350 return limitOptions && (typeof limitOptions.minValue === "number") && (t
ypeof limitOptions.maxValue === "number"); | |
351 } | |
352 | |
353 // Check the min and max values for the AudioParam attribute named |paramN
ame| for the |node|. | |
354 // The expected limits is given by the dictionary |limits|. If some test
fails, add the name | |
355 // of the failed | |
356 function validateAudioParamLimits(node, paramName, limits) { | |
357 var nodeName = node.constructor.name; | |
358 var parameter = node[paramName]; | |
359 var prefix = nodeName + "." + paramName; | |
360 | |
361 var success = true; | |
362 if (hasValidLimits(limits[paramName])) { | |
363 // Verify that the min and max values for the parameter are correct. | |
364 var isCorrect = Should(prefix + ".minValue", parameter.minValue) | |
365 .beEqualTo(limits[paramName].minValue); | |
366 isCorrect = Should(prefix + ".maxValue", parameter.maxValue) | |
367 .beEqualTo(limits[paramName].maxValue) && isCorrect; | |
368 | |
369 // Verify that the min and max attributes are read-only | |
370 parameter.minValue = Math.PI; | |
371 var isReadOnly; | |
372 isReadOnly = Should(prefix + ".minValue = Math.PI", parameter.minValue
) | |
373 .notBeEqualTo(Math.PI); | |
374 if (isReadOnly) | |
375 testPassed(prefix + ".minValue is read-only."); | |
376 else | |
377 testFailed(prefix + ".minValue should be read-only but was changed."
); | |
378 isCorrect = isReadOnly && isCorrect; | |
379 | |
380 parameter.maxValue = Math.PI; | |
381 isReadOnly = Should(prefix + ".maxValue = Math.PI", parameter.maxValue
) | |
382 .notBeEqualTo(Math.PI); | |
383 if (isReadOnly) | |
384 testPassed(prefix + ".maxValue is read-only."); | |
385 else | |
386 testFailed(prefix + ".maxValue should be read-only but was changed."
); | |
387 isCorrect = isReadOnly && isCorrect; | |
388 | |
389 // Now try to set the parameter outside the nominal range. | |
390 var newValue = 2 * limits[paramName].minValue - 1; | |
391 | |
392 var isClipped = true; | |
393 var clippingTested = false; | |
394 // If the new value is beyond float the largest single-precision float
, skip the test | |
395 // because Chrome throws an error. | |
396 if (newValue >= -mostPositiveFloat) { | |
397 parameter.value = newValue; | |
398 clippingTested = true; | |
399 isClipped = Should("Set " + prefix + ".value = " + newValue, paramet
er.value) | |
400 .beEqualTo(parameter.minValue) && isClipped; | |
401 } | |
402 | |
403 newValue = 2 * limits[paramName].maxValue + 1; | |
404 | |
405 if (newValue <= mostPositiveFloat) { | |
406 parameter.value = newValue; | |
407 clippingTested = true; | |
408 isClipped = Should("Set " + prefix + ".value = " + newValue, paramet
er.value) | |
409 .beEqualTo(parameter.maxValue) && isClipped; | |
410 | |
411 } | |
412 | |
413 if (clippingTested) { | |
414 if (isClipped) | |
415 testPassed(prefix + " was correctly clipped to lie within the nomi
nal range.") | |
416 else | |
417 testPassed(prefix + " was not correctly clipped to lie within the
nominal range.") | |
418 } | |
419 | |
420 isCorrect = isCorrect && isClipped; | |
421 | |
422 success = isCorrect && success; | |
423 } else { | |
424 // Test config didn't specify valid limits. Fail this test! | |
425 testFailed("Limits for " + nodeName + "." + paramName + " were not cor
rectly defined."); | |
426 success = false; | |
427 } | |
428 | |
429 return success; | |
430 } | |
431 | |
432 // Test all of the AudioParams for |node| using the expected values in |li
mits|. | |
433 // |creatorName| is the name of the method to create the node, and is used
to keep trakc of | |
434 // which tests we've run. | |
435 function testLimits(creatorName, node, limits) { | |
436 var nodeName = node.constructor.name; | |
437 testedMethods.add(creatorName); | |
438 | |
439 var success = true; | |
440 | |
441 // List of all of the AudioParams that were tested. | |
442 var audioParams = []; | |
443 | |
444 // List of AudioParams that failed the test. | |
445 var incorrectParams = []; | |
446 | |
447 // Look through all of the keys for the node and extract just the AudioP
arams | |
448 Object.keys(node.__proto__).forEach(function (paramName) { | |
449 if (isAudioParam(node[paramName])) { | |
450 audioParams.push(paramName); | |
451 var isValid = validateAudioParamLimits(node, paramName, limits, inco
rrectParams); | |
452 if (!isValid) | |
453 incorrectParams.push(paramName); | |
454 | |
455 success = isValid && success; | |
456 } | |
457 }); | |
458 | |
459 // Print an appropriate message depending on whether there were AudioPar
ams defined or not. | |
460 if (audioParams.length) { | |
461 var message = "Nominal ranges for AudioParam(s) of " + node.constructo
r.name; | |
462 if (success) | |
463 testPassed(message + " are correct.\n"); | |
464 else | |
465 testFailed(message + " are incorrect for: " + incorrectParams + ".\n
"); | |
466 return success; | |
467 } else { | |
468 if (limits) | |
469 testFailed(nodeName + " has no AudioParams but test expected " + lim
its + ".\n"); | |
470 else | |
471 testPassed(nodeName + " has no AudioParams as expected.\n"); | |
472 } | |
473 } | |
474 </script> | |
475 </body> | |
476 </html> | |
OLD | NEW |