| 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 |