audioparam-cancel-and-hold.html (35154B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title> 5 Test CancelValuesAndHoldAtTime 6 </title> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 <script src="/webaudio/resources/audio-param.js"></script> 10 <script src="/webaudio/resources/audit-util.js"></script> 11 <script src="/webaudio/resources/audit.js"></script> 12 </head> 13 <body> 14 <script id="layout-test-code"> 15 let sampleRate = 48000; 16 let renderDuration = 0.5; 17 18 let audit = Audit.createTaskRunner(); 19 20 audit.define( 21 {label: 'cancelTime', description: 'Test Invalid Values'}, 22 (task, should) => { 23 let context = new OfflineAudioContext({ 24 numberOfChannels: 1, 25 length: 1, 26 sampleRate: 8000 27 }); 28 29 let src = new ConstantSourceNode(context); 30 src.connect(context.destination); 31 32 should( 33 () => src.offset.cancelAndHoldAtTime(-1), 34 'cancelAndHoldAtTime(-1)') 35 .throw(RangeError); 36 37 // These are TypeErrors because |cancelTime| is a 38 // double, not unrestricted double. 39 should( 40 () => src.offset.cancelAndHoldAtTime(NaN), 41 'cancelAndHoldAtTime(NaN)') 42 .throw(TypeError); 43 44 should( 45 () => src.offset.cancelAndHoldAtTime(Infinity), 46 'cancelAndHoldAtTime(Infinity)') 47 .throw(TypeError); 48 49 task.done(); 50 }); 51 52 // The first few tasks test the cancellation of each relevant automation 53 // function. For the test, a simple linear ramp from 0 to 1 is used to 54 // start things off. Then the automation to be tested is scheduled and 55 // cancelled. 56 57 audit.define( 58 {label: 'linear', description: 'Cancel linearRampToValueAtTime'}, 59 function(task, should) { 60 cancelTest(should, linearRampTest('linearRampToValueAtTime'), { 61 valueThreshold: 8.3998e-5, 62 curveThreshold: 5.9605e-5 63 }).then(task.done.bind(task)); 64 }); 65 66 audit.define( 67 {label: 'exponential', description: 'Cancel exponentialRampAtTime'}, 68 function(task, should) { 69 // Cancel an exponential ramp. The thresholds are experimentally 70 // determined. 71 cancelTest(should, function(g, v0, t0, cancelTime) { 72 // Initialize values to 0. 73 g[0].gain.setValueAtTime(0, 0); 74 g[1].gain.setValueAtTime(0, 0); 75 // Schedule a short linear ramp to start things off. 76 g[0].gain.linearRampToValueAtTime(v0, t0); 77 g[1].gain.linearRampToValueAtTime(v0, t0); 78 79 // After the linear ramp, schedule an exponential ramp to the end. 80 // (This is the event that will be be cancelled.) 81 let v1 = 0.001; 82 let t1 = renderDuration; 83 84 g[0].gain.exponentialRampToValueAtTime(v1, t1); 85 g[1].gain.exponentialRampToValueAtTime(v1, t1); 86 87 expectedConstant = Math.fround( 88 v0 * Math.pow(v1 / v0, (cancelTime - t0) / (t1 - t0))); 89 return { 90 expectedConstant: expectedConstant, 91 autoMessage: 'exponentialRampToValue(' + v1 + ', ' + t1 + ')', 92 summary: 'exponentialRampToValueAtTime', 93 }; 94 }, { 95 valueThreshold: 1.8664e-6, 96 curveThreshold: 5.9605e-8 97 }).then(task.done.bind(task)); 98 }); 99 100 audit.define( 101 {label: 'setTarget', description: 'Cancel setTargetAtTime'}, 102 function(task, should) { 103 // Cancel a setTarget event. 104 cancelTest(should, function(g, v0, t0, cancelTime) { 105 // Initialize values to 0. 106 g[0].gain.setValueAtTime(0, 0); 107 g[1].gain.setValueAtTime(0, 0); 108 // Schedule a short linear ramp to start things off. 109 g[0].gain.linearRampToValueAtTime(v0, t0); 110 g[1].gain.linearRampToValueAtTime(v0, t0); 111 112 // At the end of the linear ramp, schedule a setTarget. (This is 113 // the event that will be cancelled.) 114 let v1 = 0; 115 let t1 = t0; 116 let timeConstant = 0.05; 117 118 g[0].gain.setTargetAtTime(v1, t1, timeConstant); 119 g[1].gain.setTargetAtTime(v1, t1, timeConstant); 120 121 expectedConstant = Math.fround( 122 v1 + (v0 - v1) * Math.exp(-(cancelTime - t0) / timeConstant)); 123 return { 124 expectedConstant: expectedConstant, 125 autoMessage: 'setTargetAtTime(' + v1 + ', ' + t1 + ', ' + 126 timeConstant + ')', 127 summary: 'setTargetAtTime', 128 }; 129 }, { 130 valueThreshold: 4.5267e-7, // 1.1317e-7, 131 curveThreshold: 0 132 }).then(task.done.bind(task)); 133 }); 134 135 audit.define( 136 {label: 'setValueCurve', description: 'Cancel setValueCurveAtTime'}, 137 function(task, should) { 138 // Cancel a setValueCurve event. 139 cancelTest(should, function(g, v0, t0, cancelTime) { 140 // Initialize values to 0. 141 g[0].gain.setValueAtTime(0, 0); 142 g[1].gain.setValueAtTime(0, 0); 143 // Schedule a short linear ramp to start things off. 144 g[0].gain.linearRampToValueAtTime(v0, t0); 145 g[1].gain.linearRampToValueAtTime(v0, t0); 146 147 // After the linear ramp, schedule a setValuesCurve. (This is the 148 // event that will be cancelled.) 149 let v1 = 0; 150 let duration = renderDuration - t0; 151 152 // For simplicity, a 2-point curve so we get a linear interpolated 153 // result. 154 let curve = Float32Array.from([v0, 0]); 155 156 g[0].gain.setValueCurveAtTime(curve, t0, duration); 157 g[1].gain.setValueCurveAtTime(curve, t0, duration); 158 159 let index = 160 Math.floor((curve.length - 1) / duration * (cancelTime - t0)); 161 162 let curvePointsPerFrame = 163 (curve.length - 1) / duration / sampleRate; 164 let virtualIndex = 165 (cancelTime - t0) * sampleRate * curvePointsPerFrame; 166 167 let delta = virtualIndex - index; 168 expectedConstant = curve[0] + (curve[1] - curve[0]) * delta; 169 return { 170 expectedConstant: expectedConstant, 171 autoMessage: 'setValueCurveAtTime([' + curve + '], ' + t0 + 172 ', ' + duration + ')', 173 summary: 'setValueCurveAtTime', 174 }; 175 }, { 176 valueThreshold: 9.5368e-9, 177 curveThreshold: 0 178 }).then(task.done.bind(task)); 179 }); 180 181 audit.define( 182 { 183 label: 'setValueCurve after end', 184 description: 'Cancel setValueCurveAtTime after the end' 185 }, 186 function(task, should) { 187 cancelTest(should, function(g, v0, t0, cancelTime) { 188 // Initialize values to 0. 189 g[0].gain.setValueAtTime(0, 0); 190 g[1].gain.setValueAtTime(0, 0); 191 // Schedule a short linear ramp to start things off. 192 g[0].gain.linearRampToValueAtTime(v0, t0); 193 g[1].gain.linearRampToValueAtTime(v0, t0); 194 195 // After the linear ramp, schedule a setValuesCurve. (This is the 196 // event that will be cancelled.) Make sure the curve ends before 197 // the cancellation time. 198 let v1 = 0; 199 let duration = cancelTime - t0 - 0.125; 200 201 // For simplicity, a 2-point curve so we get a linear interpolated 202 // result. 203 let curve = Float32Array.from([v0, 0]); 204 205 g[0].gain.setValueCurveAtTime(curve, t0, duration); 206 g[1].gain.setValueCurveAtTime(curve, t0, duration); 207 208 expectedConstant = curve[1]; 209 return { 210 expectedConstant: expectedConstant, 211 autoMessage: 'setValueCurveAtTime([' + curve + '], ' + t0 + 212 ', ' + duration + ')', 213 summary: 'setValueCurveAtTime', 214 }; 215 }, { 216 valueThreshold: 0, 217 curveThreshold: 0 218 }).then(task.done.bind(task)); 219 }); 220 221 // Special case where we schedule a setTarget and there is no earlier 222 // automation event. This tests that we pick up the starting point 223 // correctly from the last setting of the AudioParam value attribute. 224 225 226 audit.define( 227 { 228 label: 'initial setTarget', 229 description: 'Cancel with initial setTargetAtTime' 230 }, 231 function(task, should) { 232 cancelTest(should, function(g, v0, t0, cancelTime) { 233 let v1 = 0; 234 let timeConstant = 0.1; 235 g[0].gain.value = 1; 236 g[0].gain.setTargetAtTime(v1, t0, timeConstant); 237 g[1].gain.value = 1; 238 g[1].gain.setTargetAtTime(v1, t0, timeConstant); 239 240 let expectedConstant = Math.fround( 241 v1 + (v0 - v1) * Math.exp(-(cancelTime - t0) / timeConstant)); 242 243 return { 244 expectedConstant: expectedConstant, 245 autoMessage: 'setTargetAtTime(' + v1 + ', ' + t0 + ', ' + 246 timeConstant + ')', 247 summary: 'Initial setTargetAtTime', 248 }; 249 }, { 250 valueThreshold: 3.1210e-6, 251 curveThreshold: 0 252 }).then(task.done.bind(task)); 253 }); 254 255 // Test automations scheduled after the call to cancelAndHoldAtTime. 256 // Very similar to the above tests, but we also schedule an event after 257 // cancelAndHoldAtTime and verify that curve after cancellation has 258 // the correct values. 259 260 audit.define( 261 { 262 label: 'post cancel: Linear', 263 description: 'LinearRamp after cancelling' 264 }, 265 function(task, should) { 266 // Run the cancel test using a linearRamp as the event to be 267 // cancelled. Then schedule another linear ramp after the 268 // cancellation. 269 cancelTest( 270 should, 271 linearRampTest('Post cancellation linearRampToValueAtTime'), 272 {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, 273 function(g, cancelTime, expectedConstant) { 274 // Schedule the linear ramp on g[0], and do the same for g[2], 275 // using the starting point given by expectedConstant. 276 let v2 = 2; 277 let t2 = cancelTime + 0.125; 278 g[0].gain.linearRampToValueAtTime(v2, t2); 279 g[2].gain.setValueAtTime(expectedConstant, cancelTime); 280 g[2].gain.linearRampToValueAtTime(v2, t2); 281 return { 282 constantEndTime: cancelTime, 283 message: 'Post linearRamp(' + v2 + ', ' + t2 + ')' 284 }; 285 }) 286 .then(task.done.bind(task)); 287 }); 288 289 audit.define( 290 { 291 label: 'post cancel: Exponential', 292 description: 'ExponentialRamp after cancelling' 293 }, 294 function(task, should) { 295 // Run the cancel test using a linearRamp as the event to be 296 // cancelled. Then schedule an exponential ramp after the 297 // cancellation. 298 cancelTest( 299 should, 300 linearRampTest('Post cancel exponentialRampToValueAtTime'), 301 {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, 302 function(g, cancelTime, expectedConstant) { 303 // Schedule the exponential ramp on g[0], and do the same for 304 // g[2], using the starting point given by expectedConstant. 305 let v2 = 2; 306 let t2 = cancelTime + 0.125; 307 g[0].gain.exponentialRampToValueAtTime(v2, t2); 308 g[2].gain.setValueAtTime(expectedConstant, cancelTime); 309 g[2].gain.exponentialRampToValueAtTime(v2, t2); 310 return { 311 constantEndTime: cancelTime, 312 message: 'Post exponentialRamp(' + v2 + ', ' + t2 + ')' 313 }; 314 }) 315 .then(task.done.bind(task)); 316 }); 317 318 audit.define('post cancel: ValueCurve', function(task, should) { 319 // Run the cancel test using a linearRamp as the event to be cancelled. 320 // Then schedule a setValueCurve after the cancellation. 321 cancelTest( 322 should, linearRampTest('Post cancel setValueCurveAtTime'), 323 {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, 324 function(g, cancelTime, expectedConstant) { 325 // Schedule the exponential ramp on g[0], and do the same for 326 // g[2], using the starting point given by expectedConstant. 327 let t2 = cancelTime + 0.125; 328 let duration = 0.125; 329 let curve = Float32Array.from([.125, 2]); 330 g[0].gain.setValueCurveAtTime(curve, t2, duration); 331 g[2].gain.setValueAtTime(expectedConstant, cancelTime); 332 g[2].gain.setValueCurveAtTime(curve, t2, duration); 333 return { 334 constantEndTime: cancelTime, 335 message: 'Post setValueCurve([' + curve + '], ' + t2 + ', ' + 336 duration + ')', 337 errorThreshold: 8.3998e-5 338 }; 339 }) 340 .then(task.done.bind(task)); 341 }); 342 343 audit.define('post cancel: setTarget', function(task, should) { 344 // Run the cancel test using a linearRamp as the event to be cancelled. 345 // Then schedule a setTarget after the cancellation. 346 cancelTest( 347 should, linearRampTest('Post cancel setTargetAtTime'), 348 {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, 349 function(g, cancelTime, expectedConstant) { 350 // Schedule the exponential ramp on g[0], and do the same for 351 // g[2], using the starting point given by expectedConstant. 352 let v2 = 0.125; 353 let t2 = cancelTime + 0.125; 354 let timeConstant = 0.1; 355 g[0].gain.setTargetAtTime(v2, t2, timeConstant); 356 g[2].gain.setValueAtTime(expectedConstant, cancelTime); 357 g[2].gain.setTargetAtTime(v2, t2, timeConstant); 358 return { 359 constantEndTime: cancelTime + 0.125, 360 message: 'Post setTargetAtTime(' + v2 + ', ' + t2 + ', ' + 361 timeConstant + ')', 362 errorThreshold: 8.4037e-5 363 }; 364 }) 365 .then(task.done.bind(task)); 366 }); 367 368 audit.define('post cancel: setValue', function(task, should) { 369 // Run the cancel test using a linearRamp as the event to be cancelled. 370 // Then schedule a setTarget after the cancellation. 371 cancelTest( 372 should, linearRampTest('Post cancel setValueAtTime'), 373 {valueThreshold: 8.3998e-5, curveThreshold: 5.9605e-8}, 374 function(g, cancelTime, expectedConstant) { 375 // Schedule the exponential ramp on g[0], and do the same for 376 // g[2], using the starting point given by expectedConstant. 377 let v2 = 0.125; 378 let t2 = cancelTime + 0.125; 379 g[0].gain.setValueAtTime(v2, t2); 380 g[2].gain.setValueAtTime(expectedConstant, cancelTime); 381 g[2].gain.setValueAtTime(v2, t2); 382 return { 383 constantEndTime: cancelTime + 0.125, 384 message: 'Post setValueAtTime(' + v2 + ', ' + t2 + ')' 385 }; 386 }) 387 .then(task.done.bind(task)); 388 }); 389 390 audit.define('cancel future setTarget', (task, should) => { 391 const context = 392 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); 393 const src = new ConstantSourceNode(context); 394 src.connect(context.destination); 395 396 src.offset.setValueAtTime(0.5, 0); 397 src.offset.setTargetAtTime(0, 0.75 * renderDuration, 0.1); 398 // Now cancel the effect of the setTarget. 399 src.offset.cancelAndHoldAtTime(0.5 * renderDuration); 400 401 src.start(); 402 context.startRendering() 403 .then(buffer => { 404 let actual = buffer.getChannelData(0); 405 // Because the setTarget was cancelled, the output should be a 406 // constant. 407 should(actual, 'After cancelling future setTarget event, output') 408 .beConstantValueOf(0.5); 409 }) 410 .then(task.done.bind(task)); 411 }); 412 413 audit.define('cancel setTarget now', (task, should) => { 414 const context = 415 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); 416 const src = new ConstantSourceNode(context); 417 src.connect(context.destination); 418 419 src.offset.setValueAtTime(0.5, 0); 420 src.offset.setTargetAtTime(0, 0.5 * renderDuration, 0.1); 421 // Now cancel the effect of the setTarget. 422 src.offset.cancelAndHoldAtTime(0.5 * renderDuration); 423 424 src.start(); 425 context.startRendering() 426 .then(buffer => { 427 let actual = buffer.getChannelData(0); 428 // Because the setTarget was cancelled, the output should be a 429 // constant. 430 should( 431 actual, 432 'After cancelling setTarget event starting now, output') 433 .beConstantValueOf(0.5); 434 }) 435 .then(task.done.bind(task)); 436 }); 437 438 audit.define('cancel future setValueCurve', (task, should) => { 439 const context = 440 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); 441 const src = new ConstantSourceNode(context); 442 src.connect(context.destination); 443 444 src.offset.setValueAtTime(0.5, 0); 445 src.offset.setValueCurveAtTime([-1, 1], 0.75 * renderDuration, 0.1); 446 // Now cancel the effect of the setTarget. 447 src.offset.cancelAndHoldAtTime(0.5 * renderDuration); 448 449 src.start(); 450 context.startRendering() 451 .then(buffer => { 452 let actual = buffer.getChannelData(0); 453 // Because the setTarget was cancelled, the output should be a 454 // constant. 455 should( 456 actual, 'After cancelling future setValueCurve event, output') 457 .beConstantValueOf(0.5); 458 }) 459 .then(task.done.bind(task)); 460 }); 461 462 audit.define('cancel setValueCurve now', (task, should) => { 463 const context = 464 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); 465 const src = new ConstantSourceNode(context); 466 src.connect(context.destination); 467 468 src.offset.setValueAtTime(0.5, 0); 469 src.offset.setValueCurveAtTime([-1, 1], 0.5 * renderDuration, 0.1); 470 // Now cancel the effect of the setTarget. 471 src.offset.cancelAndHoldAtTime(0.5 * renderDuration); 472 473 src.start(); 474 context.startRendering() 475 .then(buffer => { 476 let actual = buffer.getChannelData(0); 477 // Because the setTarget was cancelled, the output should be a 478 // constant. 479 should( 480 actual, 481 'After cancelling current setValueCurve event starting now, output') 482 .beConstantValueOf(0.5); 483 }) 484 .then(task.done.bind(task)); 485 }); 486 487 audit.define( 488 { 489 label: 'linear, cancel, linear, cancel, linear', 490 description: 'Schedules 3 linear ramps, cancelling 2 of them, ' 491 + 'so that we end up with 2 cancel events next to each other' 492 }, 493 (task, should) => { 494 cancelTest2( 495 should, 496 linearRampTest('1st linearRamp'), 497 {valueThreshold: 0, curveThreshold: 5.9605e-8}, 498 (g, cancelTime, expectedConstant, cancelTime2) => { 499 // Ramp from first cancel time to the end will be cancelled at 500 // second cancel time. 501 const v1 = expectedConstant; 502 const t1 = cancelTime; 503 const v2 = 2; 504 const t2 = renderDuration; 505 g[0].gain.linearRampToValueAtTime(v2, t2); 506 g[2].gain.setValueAtTime(v1, t1); 507 g[2].gain.linearRampToValueAtTime(v2, t2); 508 509 const expectedConstant2 = 510 audioParamLinearRamp(cancelTime2, v1, t1, v2, t2); 511 512 return { 513 constantEndTime: cancelTime, 514 message: `2nd linearRamp(${v2}, ${t2})`, 515 expectedConstant2 516 }; 517 }, 518 (g, cancelTime2, expectedConstant2) => { 519 // Ramp from second cancel time to the end. 520 const v3 = 0; 521 const t3 = renderDuration; 522 g[0].gain.linearRampToValueAtTime(v3, t3); 523 g[3].gain.setValueAtTime(expectedConstant2, cancelTime2); 524 g[3].gain.linearRampToValueAtTime(v3, t3); 525 return { 526 constantEndTime2: cancelTime2, 527 message2: `3rd linearRamp(${v3}, ${t3})`, 528 }; 529 }) 530 .then(() => task.done()); 531 }); 532 533 audit.run(); 534 535 // Common function for doing a linearRamp test. This just does a linear 536 // ramp from 0 to v0 at from time 0 to t0. Then another linear ramp is 537 // scheduled from v0 to 0 from time t0 to t1. This is the ramp that is to 538 // be cancelled. 539 function linearRampTest(message) { 540 return function(g, v0, t0, cancelTime) { 541 g[0].gain.setValueAtTime(0, 0); 542 g[1].gain.setValueAtTime(0, 0); 543 g[0].gain.linearRampToValueAtTime(v0, t0); 544 g[1].gain.linearRampToValueAtTime(v0, t0); 545 546 let v1 = 0; 547 let t1 = renderDuration; 548 g[0].gain.linearRampToValueAtTime(v1, t1); 549 g[1].gain.linearRampToValueAtTime(v1, t1); 550 551 expectedConstant = 552 Math.fround(v0 + (v1 - v0) * (cancelTime - t0) / (t1 - t0)); 553 554 return { 555 expectedConstant: expectedConstant, 556 autoMessage: 557 message + ': linearRampToValue(' + v1 + ', ' + t1 + ')', 558 summary: message, 559 }; 560 } 561 } 562 563 // Run the cancellation test. A set of automations is created and 564 // canceled. 565 // 566 // |testerFunction| is a function that generates the automation to be 567 // tested. It is given an array of 3 gain nodes, the value and time of an 568 // initial linear ramp, and the time where the cancellation should occur. 569 // The function must do the automations for the first two gain nodes. It 570 // must return a dictionary with |expectedConstant| being the value at the 571 // cancellation time, |autoMessage| for message to describe the test, and 572 // |summary| for general summary message to be printed at the end of the 573 // test. 574 // 575 // |thresholdOptions| is a property bag that specifies the error threshold 576 // to use. |thresholdOptions.valueThreshold| is the error threshold for 577 // comparing the actual constant output after cancelling to the expected 578 // value. |thresholdOptions.curveThreshold| is the error threshold for 579 // comparing the actual and expected automation curves before the 580 // cancelation point. 581 // 582 // For cancellation tests, |postCancelTest| is a function that schedules 583 // some automation after the cancellation. It takes 3 arguments: an array 584 // of the gain nodes, the cancellation time, and the expected value at the 585 // cancellation time. This function must return a dictionary consisting 586 // of |constantEndtime| indicating when the held constant from 587 // cancellation stops being constant, |message| giving a summary of what 588 // automation is being used, and |errorThreshold| that is the error 589 // threshold between the expected curve and the actual curve. 590 // 591 function cancelTest( 592 should, testerFunction, thresholdOptions, postCancelTest) { 593 // Create a context with three channels. Channel 0 is the test channel 594 // containing the actual output that includes the cancellation of 595 // events. Channel 1 is the expected data upto the cancellation so we 596 // can verify the cancellation produced the correct result. Channel 2 597 // is for verifying events inserted after the cancellation so we can 598 // verify that automations are correctly generated after the 599 // cancellation point. 600 let context = 601 new OfflineAudioContext(3, renderDuration * sampleRate, sampleRate); 602 603 // Test source is a constant signal 604 let src = context.createBufferSource(); 605 src.buffer = createConstantBuffer(context, 1, 1); 606 src.loop = true; 607 608 // We'll do the automation tests with three gain nodes. One (g0) will 609 // have cancelAndHoldAtTime and the other (g1) will not. g1 is 610 // used as the expected result for that automation up to the 611 // cancellation point. They should be the same. The third node (g2) is 612 // used for testing automations inserted after the cancellation point, 613 // if any. g2 is the expected result from the cancellation point to the 614 // end of the test. 615 616 let g0 = context.createGain(); 617 let g1 = context.createGain(); 618 let g2 = context.createGain(); 619 let v0 = 1; 620 let t0 = 0.01; 621 622 let cancelTime = renderDuration / 2; 623 624 // Test automation here. The tester function is responsible for setting 625 // up the gain nodes with the desired automation for testing. 626 autoResult = testerFunction([g0, g1, g2], v0, t0, cancelTime); 627 let expectedConstant = autoResult.expectedConstant; 628 let autoMessage = autoResult.autoMessage; 629 let summaryMessage = autoResult.summary; 630 631 // Cancel scheduled events somewhere in the middle of the test 632 // automation. 633 g0.gain.cancelAndHoldAtTime(cancelTime); 634 635 let constantEndTime; 636 if (postCancelTest) { 637 postResult = 638 postCancelTest([g0, g1, g2], cancelTime, expectedConstant); 639 constantEndTime = postResult.constantEndTime; 640 } 641 642 // Connect everything together (with a merger to make a two-channel 643 // result). Channel 0 is the test (with cancelAndHoldAtTime) and 644 // channel 1 is the reference (without cancelAndHoldAtTime). 645 // Channel 1 is used to verify that everything up to the cancellation 646 // has the correct values. 647 src.connect(g0); 648 src.connect(g1); 649 src.connect(g2); 650 let merger = context.createChannelMerger(3); 651 g0.connect(merger, 0, 0); 652 g1.connect(merger, 0, 1); 653 g2.connect(merger, 0, 2); 654 merger.connect(context.destination); 655 656 // Go! 657 src.start(); 658 659 return context.startRendering().then(function(buffer) { 660 let actual = buffer.getChannelData(0); 661 let expected = buffer.getChannelData(1); 662 663 // The actual output should be a constant from the cancel time to the 664 // end. We use the last value of the actual output as the constant, 665 // but we also want to compare that with what we thought it should 666 // really be. 667 668 let cancelFrame = Math.ceil(cancelTime * sampleRate); 669 670 // Verify that the curves up to the cancel time are "identical". The 671 // should be but round-off may make them differ slightly due to the 672 // way cancelling is done. 673 let endFrame = Math.floor(cancelTime * sampleRate); 674 should( 675 actual.slice(0, endFrame), 676 autoMessage + ' up to time ' + cancelTime) 677 .beCloseToArray( 678 expected.slice(0, endFrame), 679 {absoluteThreshold: thresholdOptions.curveThreshold}); 680 681 // Verify the output after the cancellation is a constant. 682 let actualTail; 683 let constantEndFrame; 684 685 if (postCancelTest) { 686 constantEndFrame = Math.ceil(constantEndTime * sampleRate); 687 actualTail = actual.slice(cancelFrame, constantEndFrame); 688 } else { 689 actualTail = actual.slice(cancelFrame); 690 } 691 692 let actualConstant = actual[cancelFrame]; 693 694 should( 695 actualTail, 696 'Cancelling ' + autoMessage + ' at time ' + cancelTime) 697 .beConstantValueOf(actualConstant); 698 699 // Verify that the constant is the value we expect. 700 should( 701 actualConstant, 702 'Expected value for cancelling ' + autoMessage + ' at time ' + 703 cancelTime) 704 .beCloseTo( 705 expectedConstant, 706 {threshold: thresholdOptions.valueThreshold}); 707 708 // Verify the curve after the constantEndTime matches our 709 // expectations. 710 if (postCancelTest) { 711 let c2 = buffer.getChannelData(2); 712 should(actual.slice(constantEndFrame), postResult.message) 713 .beCloseToArray( 714 c2.slice(constantEndFrame), 715 {absoluteThreshold: postResult.errorThreshold || 0}); 716 } 717 }); 718 } 719 720 // Similar to cancelTest, but does 2 cancels. 721 function cancelTest2( 722 should, testerFunction, thresholdOptions, 723 postCancelTest, postCancelTest2) { 724 // Channel 0: Actual output that includes the cancellation of events. 725 // Channel 1: Expected data up to the first cancellation. 726 // Channel 2: Expected data from 1st cancellation to 2nd cancellation. 727 // Channel 3: Expected data from 2nd cancellation to the end. 728 const context = 729 new OfflineAudioContext(4, renderDuration * sampleRate, sampleRate); 730 731 const src = context.createConstantSource(); 732 733 // g0: Actual gain which will have cancelAndHoldAtTime called on it 734 // twice. 735 // g1: Expected gain from start to the 1st cancel. 736 // g2: Expected gain from 1st cancel to the 2nd cancel. 737 // g3: Expected gain from the 2nd cancel to the end. 738 const g0 = context.createGain(); 739 const g1 = context.createGain(); 740 const g2 = context.createGain(); 741 const g3 = context.createGain(); 742 const v0 = 1; 743 const t0 = 0.01; 744 745 const cancelTime1 = renderDuration * 0.5; 746 const cancelTime2 = renderDuration * 0.75; 747 748 // Run testerFunction to generate the 1st ramp. 749 const { 750 expectedConstant, autoMessage, summaryMessage} = 751 testerFunction([g0, g1, g2], v0, t0, cancelTime1); 752 753 // 1st cancel, cancelling the 1st ramp. 754 g0.gain.cancelAndHoldAtTime(cancelTime1); 755 756 // Run postCancelTest to generate the 2nd ramp. 757 const { 758 constantEndTime, message, errorThreshold = 0, expectedConstant2} = 759 postCancelTest( 760 [g0, g1, g2], cancelTime1, expectedConstant, cancelTime2); 761 762 // 2nd cancel, cancelling the 2nd ramp. 763 g0.gain.cancelAndHoldAtTime(cancelTime2); 764 765 // Run postCancelTest2 to generate the 3rd ramp. 766 const {constantEndTime2, message2} = 767 postCancelTest2([g0, g1, g2, g3], cancelTime2, expectedConstant2); 768 769 // Connect everything together 770 src.connect(g0); 771 src.connect(g1); 772 src.connect(g2); 773 src.connect(g3); 774 const merger = context.createChannelMerger(4); 775 g0.connect(merger, 0, 0); 776 g1.connect(merger, 0, 1); 777 g2.connect(merger, 0, 2); 778 g3.connect(merger, 0, 3); 779 merger.connect(context.destination); 780 781 // Go! 782 src.start(); 783 784 return context.startRendering().then(function (buffer) { 785 const actual = buffer.getChannelData(0); 786 const expected1 = buffer.getChannelData(1); 787 const expected2 = buffer.getChannelData(2); 788 const expected3 = buffer.getChannelData(3); 789 790 const cancelFrame1 = Math.ceil(cancelTime1 * sampleRate); 791 const cancelFrame2 = Math.ceil(cancelTime2 * sampleRate); 792 793 const constantEndFrame1 = Math.ceil(constantEndTime * sampleRate); 794 const constantEndFrame2 = Math.ceil(constantEndTime2 * sampleRate); 795 796 const actualTail1 = actual.slice(cancelFrame1, constantEndFrame1); 797 const actualTail2 = actual.slice(cancelFrame2, constantEndFrame2); 798 799 const actualConstant1 = actual[cancelFrame1]; 800 const actualConstant2 = actual[cancelFrame2]; 801 802 // Verify first section curve 803 should( 804 actual.slice(0, cancelFrame1), 805 autoMessage + ' up to time ' + cancelTime1) 806 .beCloseToArray( 807 expected1.slice(0, cancelFrame1), 808 {absoluteThreshold: thresholdOptions.curveThreshold}); 809 810 // Verify that a value was held after 1st cancel 811 should( 812 actualTail1, 813 'Cancelling ' + autoMessage + ' at time ' + cancelTime1) 814 .beConstantValueOf(actualConstant1); 815 816 // Verify that held value after 1st cancel was correct 817 should( 818 actualConstant1, 819 'Expected value for cancelling ' + autoMessage + ' at time ' + 820 cancelTime1) 821 .beCloseTo( 822 expectedConstant, 823 {threshold: thresholdOptions.valueThreshold}); 824 825 // Verify middle section curve 826 should(actual.slice(constantEndFrame1, cancelFrame2), message) 827 .beCloseToArray( 828 expected2.slice(constantEndFrame1, cancelFrame2), 829 {absoluteThreshold: errorThreshold}); 830 831 // Verify that a value was held after 2nd cancel 832 should( 833 actualTail2, 834 'Cancelling ' + message + ' at time ' + cancelTime2) 835 .beConstantValueOf(actualConstant2); 836 837 // Verify that held value after 2nd cancel was correct 838 should( 839 actualConstant2, 840 'Expected value for cancelling ' + message + ' at time ' + 841 cancelTime2) 842 .beCloseTo( 843 expectedConstant2, 844 {threshold: thresholdOptions.valueThreshold}); 845 846 // Verify end section curve 847 should(actual.slice(constantEndFrame2), message2) 848 .beCloseToArray( 849 expected3.slice(constantEndFrame2), 850 {absoluteThreshold: errorThreshold || 0}); 851 }); 852 } 853 </script> 854 </body> 855 </html>