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