tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>