k-rate-oscillator-connections.html (21882B)
1 <!doctype html> 2 <html> 3 <head> 4 <title> 5 k-rate AudioParams with inputs for OscillatorNode 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="/webaudio/resources/audit.js"></script> 10 <script src="/webaudio/resources/audit-util.js"></script> 11 </head> 12 13 <body> 14 <script> 15 let audit = Audit.createTaskRunner(); 16 17 // Sample rate must be a power of two to eliminate round-off when 18 // computing time from frames and vice versa. Using a non-power of two 19 // will work, but the thresholds below will not be zero. They're probably 20 // closer to 1e-5 or so, but if everything is working correctly, the 21 // outputs really should be exactly equal. 22 const sampleRate = 8192; 23 24 // Fairly arbitrary but short duration to limit runtime. 25 const testFrames = 5 * RENDER_QUANTUM_FRAMES; 26 const testDuration = testFrames / sampleRate; 27 28 audit.define( 29 {label: 'Test 1', description: 'k-rate frequency input'}, 30 async (task, should) => { 31 // Test that an input to the frequency AudioParam set to k-rate 32 // works. 33 34 // Fairly arbitrary start and end frequencies for the automation. 35 const freqStart = 100; 36 const freqEnd = 2000; 37 38 let refSetup = (context) => { 39 let srcRef = new OscillatorNode(context, {frequency: 0}); 40 41 should( 42 () => srcRef.frequency.automationRate = 'k-rate', 43 `${task.label}: srcRef.frequency.automationRate = 'k-rate'`) 44 .notThrow(); 45 should( 46 () => srcRef.frequency.setValueAtTime(freqStart, 0), 47 `${task.label}: srcRef.frequency.setValueAtTime(${ 48 freqStart}, 0)`) 49 .notThrow(); 50 should( 51 () => srcRef.frequency.linearRampToValueAtTime( 52 freqEnd, testDuration), 53 `${task.label}: srcRef.frequency.linearRampToValueAtTime(${ 54 freqEnd}, ${testDuration})`) 55 .notThrow(); 56 57 return srcRef; 58 }; 59 60 let testSetup = (context) => { 61 let srcTest = new OscillatorNode(context, {frequency: 0}); 62 should( 63 () => srcTest.frequency.automationRate = 'k-rate', 64 `${task.label}: srcTest.frequency.automationRate = 'k-rate'`) 65 .notThrow(); 66 67 return srcTest; 68 }; 69 70 let modSetup = (context) => { 71 let mod = new ConstantSourceNode(context, {offset: 0}); 72 73 should( 74 () => mod.offset.setValueAtTime(freqStart, 0), 75 `${task.label}: modFreq.offset.setValueAtTime(${ 76 freqStart}, 0)`) 77 .notThrow(); 78 should( 79 () => 80 mod.offset.linearRampToValueAtTime(freqEnd, testDuration), 81 `${task.label}: modFreq.offset.linearRampToValueAtTime(${ 82 freqEnd}, ${testDuration})`) 83 .notThrow(); 84 85 // This node is going to be connected to the frequency AudioParam. 86 return {frequency: mod}; 87 }; 88 89 await testParams(should, { 90 prefix: task.label, 91 summary: 'k-rate frequency with input', 92 setupRefOsc: refSetup, 93 setupTestOsc: testSetup, 94 setupMod: modSetup 95 }); 96 97 task.done(); 98 }); 99 100 audit.define( 101 {label: 'Test 2', description: 'k-rate detune input'}, 102 async (task, should) => { 103 // Test that an input to the detune AudioParam set to k-rate works. 104 // Threshold experimentally determined. It should be probably not 105 // be much larger than 5e-5. or something is not right. 106 107 // Fairly arbitrary start and end detune values for automation. 108 const detuneStart = 0; 109 const detuneEnd = 2000; 110 111 let refSetup = (context) => { 112 let srcRef = new OscillatorNode(context, {detune: 0}); 113 114 should( 115 () => srcRef.detune.automationRate = 'k-rate', 116 `${task.label}: srcRef.detune.automationRate = 'k-rate'`) 117 .notThrow(); 118 119 should( 120 () => srcRef.detune.setValueAtTime(detuneStart, 0), 121 `${task.label}: srcRef.detune.setValueAtTime(${ 122 detuneStart}, 0)`) 123 .notThrow(); 124 should( 125 () => srcRef.detune.linearRampToValueAtTime( 126 detuneEnd, testDuration), 127 `${task.label}: srcRef.detune.linearRampToValueAtTime(${ 128 detuneEnd}, ${testDuration})`) 129 .notThrow(); 130 131 return srcRef; 132 }; 133 134 let testSetup = (context) => { 135 let srcTest = new OscillatorNode(context, {detune: 0}); 136 137 should( 138 () => srcTest.detune.automationRate = 'k-rate', 139 `${task.label}: srcTest.detune.automationRate = 'k-rate'`) 140 .notThrow(); 141 142 return srcTest; 143 }; 144 145 let modSetup = (context) => { 146 let mod = new ConstantSourceNode(context, {offset: 0}); 147 148 should( 149 () => mod.offset.setValueAtTime(detuneStart, 0), 150 `${task.label}: modDetune.offset.setValueAtTime(${ 151 detuneStart}, 0)`) 152 .notThrow(); 153 should( 154 () => mod.offset.linearRampToValueAtTime( 155 detuneEnd, testDuration), 156 `${task.label}: modDetune.offset.linearRampToValueAtTime(${ 157 detuneEnd}, ${testDuration})`) 158 .notThrow(); 159 160 return {detune: mod}; 161 }; 162 163 await testParams(should, { 164 prefix: task.label, 165 summary: 'k-rate detune with input', 166 setupRefOsc: refSetup, 167 setupTestOsc: testSetup, 168 setupMod: modSetup 169 }); 170 171 task.done(); 172 }); 173 174 audit.define( 175 { 176 label: 'Test 3', 177 description: 'k-rate frequency input with a-rate detune' 178 }, 179 async (task, should) => { 180 // Test OscillatorNode with a k-rate frequency with input and an 181 // a-rate detune iwth automations. 182 183 // Fairly arbitrary start and end values for the frequency and 184 // detune automations. 185 const freqStart = 100; 186 const freqEnd = 2000; 187 const detuneStart = 0; 188 const detuneEnd = -2000; 189 190 let refSetup = (context) => { 191 let node = new OscillatorNode(context, {frequency: 0}); 192 193 // Set up k-rate frequency and a-rate detune 194 should( 195 () => node.frequency.automationRate = 'k-rate', 196 `${task.label}: srcRef.frequency.automationRate = 'k-rate'`) 197 .notThrow(); 198 should( 199 () => node.frequency.setValueAtTime(freqStart, 0), 200 `${task.label}: srcRef.frequency.setValueAtTime(${ 201 freqStart}, 0)`) 202 .notThrow(); 203 should( 204 () => node.frequency.linearRampToValueAtTime( 205 2000, testDuration), 206 `${task.label}: srcRef.frequency.linearRampToValueAtTime(${ 207 freqEnd}, ${testDuration})`) 208 .notThrow(); 209 should( 210 () => node.detune.setValueAtTime(detuneStart, 0), 211 `${task.label}: srcRef.detune.setValueAtTime(${ 212 detuneStart}, 0)`) 213 .notThrow(); 214 should( 215 () => node.detune.linearRampToValueAtTime( 216 detuneEnd, testDuration), 217 `${task.label}: srcRef.detune.linearRampToValueAtTime(${ 218 detuneEnd}, ${testDuration})`) 219 .notThrow(); 220 221 return node; 222 }; 223 224 let testSetup = (context) => { 225 let node = new OscillatorNode(context, {frequency: 0}); 226 227 should( 228 () => node.frequency.automationRate = 'k-rate', 229 `${task.label}: srcTest.frequency.automationRate = 'k-rate'`) 230 .notThrow(); 231 should( 232 () => node.detune.setValueAtTime(detuneStart, 0), 233 `${task.label}: srcTest.detune.setValueAtTime(${ 234 detuneStart}, 0)`) 235 .notThrow(); 236 should( 237 () => node.detune.linearRampToValueAtTime( 238 detuneEnd, testDuration), 239 `${task.label}: srcTest.detune.linearRampToValueAtTime(${ 240 detuneEnd}, ${testDuration})`) 241 .notThrow(); 242 243 return node; 244 }; 245 246 let modSetup = (context) => { 247 let mod = {}; 248 mod['frequency'] = new ConstantSourceNode(context, {offset: 0}); 249 250 should( 251 () => mod['frequency'].offset.setValueAtTime(freqStart, 0), 252 `${task.label}: modFreq.offset.setValueAtTime(${ 253 freqStart}, 0)`) 254 .notThrow(); 255 256 should( 257 () => mod['frequency'].offset.linearRampToValueAtTime( 258 2000, testDuration), 259 `${task.label}: modFreq.offset.linearRampToValueAtTime(${ 260 freqEnd}, ${testDuration})`) 261 .notThrow(); 262 263 return mod; 264 }; 265 266 await testParams(should, { 267 prefix: task.label, 268 summary: 'k-rate frequency input with a-rate detune', 269 setupRefOsc: refSetup, 270 setupTestOsc: testSetup, 271 setupMod: modSetup 272 }); 273 274 task.done(); 275 }); 276 277 audit.define( 278 { 279 label: 'Test 4', 280 description: 'a-rate frequency with k-rate detune input' 281 }, 282 async (task, should) => { 283 // Test OscillatorNode with an a-rate frequency with automations and 284 // a k-rate detune with input. 285 286 // Fairly arbitrary start and end values for the frequency and 287 // detune automations. 288 const freqStart = 100; 289 const freqEnd = 2000; 290 const detuneStart = 0; 291 const detuneEnd = -2000; 292 293 let refSetup = (context) => { 294 let node = new OscillatorNode(context, {detune: 0}); 295 296 // Set up a-rate frequency and k-rate detune 297 should( 298 () => node.frequency.setValueAtTime(freqStart, 0), 299 `${task.label}: srcRef.frequency.setValueAtTime(${ 300 freqStart}, 0)`) 301 .notThrow(); 302 should( 303 () => node.frequency.linearRampToValueAtTime( 304 2000, testDuration), 305 `${task.label}: srcRef.frequency.linearRampToValueAtTime(${ 306 freqEnd}, ${testDuration})`) 307 .notThrow(); 308 should( 309 () => node.detune.automationRate = 'k-rate', 310 `${task.label}: srcRef.detune.automationRate = 'k-rate'`) 311 .notThrow(); 312 should( 313 () => node.detune.setValueAtTime(detuneStart, 0), 314 `${task.label}: srcRef.detune.setValueAtTime(${ 315 detuneStart}, 0)`) 316 .notThrow(); 317 should( 318 () => node.detune.linearRampToValueAtTime( 319 detuneEnd, testDuration), 320 `${task.label}: srcRef.detune.linearRampToValueAtTime(${ 321 detuneEnd}, ${testDuration})`) 322 .notThrow(); 323 324 return node; 325 }; 326 327 let testSetup = (context) => { 328 let node = new OscillatorNode(context, {detune: 0}); 329 330 should( 331 () => node.detune.automationRate = 'k-rate', 332 `${task.label}: srcTest.detune.automationRate = 'k-rate'`) 333 .notThrow(); 334 should( 335 () => node.frequency.setValueAtTime(freqStart, 0), 336 `${task.label}: srcTest.frequency.setValueAtTime(${ 337 freqStart}, 0)`) 338 .notThrow(); 339 should( 340 () => node.frequency.linearRampToValueAtTime( 341 freqEnd, testDuration), 342 `${task.label}: srcTest.frequency.linearRampToValueAtTime(${ 343 freqEnd}, ${testDuration})`) 344 .notThrow(); 345 346 return node; 347 }; 348 349 let modSetup = (context) => { 350 let mod = {}; 351 const name = 'detune'; 352 353 mod['detune'] = new ConstantSourceNode(context, {offset: 0}); 354 should( 355 () => mod[name].offset.setValueAtTime(detuneStart, 0), 356 `${task.label}: modDetune.offset.setValueAtTime(${ 357 detuneStart}, 0)`) 358 .notThrow(); 359 360 should( 361 () => mod[name].offset.linearRampToValueAtTime( 362 detuneEnd, testDuration), 363 `${task.label}: modDetune.offset.linearRampToValueAtTime(${ 364 detuneEnd}, ${testDuration})`) 365 .notThrow(); 366 367 return mod; 368 }; 369 370 await testParams(should, { 371 prefix: task.label, 372 summary: 'k-rate detune input with a-rate frequency', 373 setupRefOsc: refSetup, 374 setupTestOsc: testSetup, 375 setupMod: modSetup 376 }); 377 378 task.done(); 379 }); 380 381 audit.define( 382 { 383 label: 'Test 5', 384 description: 'k-rate inputs for frequency and detune' 385 }, 386 async (task, should) => { 387 // Test OscillatorNode with k-rate frequency and detune with inputs 388 // on both. 389 390 // Fairly arbitrary start and end values for the frequency and 391 // detune automations. 392 const freqStart = 100; 393 const freqEnd = 2000; 394 const detuneStart = 0; 395 const detuneEnd = -2000; 396 397 let refSetup = (context) => { 398 let node = new OscillatorNode(context, {frequency: 0, detune: 0}); 399 400 should( 401 () => node.frequency.automationRate = 'k-rate', 402 `${task.label}: srcRef.frequency.automationRate = 'k-rate'`) 403 .notThrow(); 404 should( 405 () => node.frequency.setValueAtTime(freqStart, 0), 406 `${task.label}: srcRef.setValueAtTime(${freqStart}, 0)`) 407 .notThrow(); 408 should( 409 () => node.frequency.linearRampToValueAtTime( 410 freqEnd, testDuration), 411 `${task.label}: srcRef;.frequency.linearRampToValueAtTime(${ 412 freqEnd}, ${testDuration})`) 413 .notThrow(); 414 should( 415 () => node.detune.automationRate = 'k-rate', 416 `${task.label}: srcRef.detune.automationRate = 'k-rate'`) 417 .notThrow(); 418 should( 419 () => node.detune.setValueAtTime(detuneStart, 0), 420 `${task.label}: srcRef.detune.setValueAtTime(${ 421 detuneStart}, 0)`) 422 .notThrow(); 423 should( 424 () => node.detune.linearRampToValueAtTime( 425 detuneEnd, testDuration), 426 `${task.label}: srcRef.detune.linearRampToValueAtTime(${ 427 detuneEnd}, ${testDuration})`) 428 .notThrow(); 429 430 return node; 431 }; 432 433 let testSetup = (context) => { 434 let node = new OscillatorNode(context, {frequency: 0, detune: 0}); 435 436 should( 437 () => node.frequency.automationRate = 'k-rate', 438 `${task.label}: srcTest.frequency.automationRate = 'k-rate'`) 439 .notThrow(); 440 should( 441 () => node.detune.automationRate = 'k-rate', 442 `${task.label}: srcTest.detune.automationRate = 'k-rate'`) 443 .notThrow(); 444 445 return node; 446 }; 447 448 let modSetup = (context) => { 449 let modF = new ConstantSourceNode(context, {offset: 0}); 450 451 should( 452 () => modF.offset.setValueAtTime(freqStart, 0), 453 `${task.label}: modFreq.offset.setValueAtTime(${ 454 freqStart}, 0)`) 455 .notThrow(); 456 should( 457 () => modF.offset.linearRampToValueAtTime( 458 freqEnd, testDuration), 459 `${task.label}: modFreq.offset.linearRampToValueAtTime(${ 460 freqEnd}, ${testDuration})`) 461 .notThrow(); 462 463 let modD = new ConstantSourceNode(context, {offset: 0}); 464 465 should( 466 () => modD.offset.setValueAtTime(detuneStart, 0), 467 `${task.label}: modDetune.offset.setValueAtTime(${ 468 detuneStart}, 0)`) 469 .notThrow(); 470 should( 471 () => modD.offset.linearRampToValueAtTime( 472 detuneEnd, testDuration), 473 `${task.label}: modDetune.offset.linearRampToValueAtTime(${ 474 detuneEnd}, ${testDuration})`) 475 .notThrow(); 476 477 return {frequency: modF, detune: modD}; 478 }; 479 480 await testParams(should, { 481 prefix: task.label, 482 summary: 'k-rate inputs for both frequency and detune', 483 setupRefOsc: refSetup, 484 setupTestOsc: testSetup, 485 setupMod: modSetup 486 }); 487 488 task.done(); 489 }); 490 491 audit.run(); 492 493 async function testParams(should, options) { 494 // Test a-rate and k-rate AudioParams of an OscillatorNode. 495 // 496 // |options| should be a dictionary with these members: 497 // prefix - prefix to use for messages 498 // summary - message to be printed with the final results 499 // setupRefOsc - function returning the reference oscillator 500 // setupTestOsc - function returning the test oscillator 501 // setupMod - function returning nodes to be connected to the 502 // AudioParams. 503 // 504 // |setupRefOsc| and |setupTestOsc| are given the context and each 505 // method is expected to create an OscillatorNode with the appropriate 506 // automations for testing. The constructed OscillatorNode is returned. 507 // 508 // The reference oscillator 509 // should automate the desired AudioParams at the appropriate automation 510 // rate, and the output is the expected result. 511 // 512 // The test oscillator should set up the AudioParams but expect the 513 // AudioParam(s) have an input that matches the automation for the 514 // reference oscillator. 515 // 516 // |setupMod| must create one or two ConstantSourceNodes with exactly 517 // the same automations as used for the reference oscillator. This node 518 // is used as the input to an AudioParam of the test oscillator. This 519 // function returns a dictionary whose members are named 'frequency' and 520 // 'detune'. The name indicates which AudioParam the constant source 521 // node should be connected to. 522 523 // Two channels: 0 = reference signal, 1 = test signal 524 let context = new OfflineAudioContext({ 525 numberOfChannels: 2, 526 sampleRate: sampleRate, 527 length: testDuration * sampleRate 528 }); 529 530 let merger = new ChannelMergerNode( 531 context, {numberOfInputs: context.destination.channelCount}); 532 merger.connect(context.destination); 533 534 // The reference oscillator. 535 let srcRef = options.setupRefOsc(context); 536 537 // The test oscillator. 538 let srcTest = options.setupTestOsc(context); 539 540 // Inputs to AudioParam. 541 let mod = options.setupMod(context); 542 543 if (mod['frequency']) { 544 should( 545 () => mod['frequency'].connect(srcTest.frequency), 546 `${options.prefix}: modFreq.connect(srcTest.frequency)`) 547 .notThrow(); 548 mod['frequency'].start() 549 } 550 551 if (mod['detune']) { 552 should( 553 () => mod['detune'].connect(srcTest.detune), 554 `${options.prefix}: modDetune.connect(srcTest.detune)`) 555 .notThrow(); 556 mod['detune'].start() 557 } 558 559 srcRef.connect(merger, 0, 0); 560 srcTest.connect(merger, 0, 1); 561 562 srcRef.start(); 563 srcTest.start(); 564 565 let buffer = await context.startRendering(); 566 let expected = buffer.getChannelData(0); 567 let actual = buffer.getChannelData(1); 568 569 // The output of the reference and test oscillator should be 570 // exactly equal because the AudioParam values should be exactly 571 // equal. 572 should(actual, options.summary).beCloseToArray(expected, { 573 absoluteThreshold: 0 574 }); 575 } 576 </script> 577 </body> 578 </html>