tor-browser

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

event-insertion.html (15963B)


      1 <!doctype html>
      2 <html>
      3  <head>
      4    <title>
      5      Test Handling of Event Insertion
      6    </title>
      7    <script src="/resources/testharness.js"></script>
      8    <script src="/resources/testharnessreport.js"></script>
      9    <script src="/webaudio/resources/audit-util.js"></script>
     10    <script src="/webaudio/resources/audit.js"></script>
     11    <script src="/webaudio/resources/audio-param.js"></script>
     12  </head>
     13  <body>
     14    <script id="layout-test-code">
     15      let audit = Audit.createTaskRunner();
     16 
     17      // Use a power of two for the sample rate so there's no round-off in
     18      // computing time from frame.
     19      let sampleRate = 16384;
     20 
     21      audit.define(
     22          {label: 'Insert same event at same time'}, (task, should) => {
     23            // Context for testing.
     24            let context = new OfflineAudioContext(
     25                {length: 16384, sampleRate: sampleRate});
     26 
     27            // The source node to use.  Automations will be scheduled here.
     28            let src = new ConstantSourceNode(context, {offset: 0});
     29            src.connect(context.destination);
     30 
     31            // An array of tests to be done.  Each entry specifies the event
     32            // type and the event time.  The events are inserted in the order
     33            // given (in |values|), and the second event should be inserted
     34            // after the first one, as required by the spec.
     35            let testCases = [
     36              {
     37                event: 'setValueAtTime',
     38                frame: RENDER_QUANTUM_FRAMES,
     39                values: [99, 1],
     40                outputTestFrame: RENDER_QUANTUM_FRAMES,
     41                expectedOutputValue: 1
     42              },
     43              {
     44                event: 'linearRampToValueAtTime',
     45                frame: 2 * RENDER_QUANTUM_FRAMES,
     46                values: [99, 2],
     47                outputTestFrame: 2 * RENDER_QUANTUM_FRAMES,
     48                expectedOutputValue: 2
     49              },
     50              {
     51                event: 'exponentialRampToValueAtTime',
     52                frame: 3 * RENDER_QUANTUM_FRAMES,
     53                values: [99, 3],
     54                outputTestFrame: 3 * RENDER_QUANTUM_FRAMES,
     55                expectedOutputValue: 3
     56              },
     57              {
     58                event: 'setValueCurveAtTime',
     59                frame: 3 * RENDER_QUANTUM_FRAMES,
     60                values: [[3, 4]],
     61                extraArgs: RENDER_QUANTUM_FRAMES / context.sampleRate,
     62                outputTestFrame: 4 * RENDER_QUANTUM_FRAMES,
     63                expectedOutputValue: 4
     64              },
     65              {
     66                event: 'setValueAtTime',
     67                frame: 5 * RENDER_QUANTUM_FRAMES - 1,
     68                values: [99, 1, 5],
     69                outputTestFrame: 5 * RENDER_QUANTUM_FRAMES,
     70                expectedOutputValue: 5
     71              }
     72            ];
     73 
     74            testCases.forEach(entry => {
     75              entry.values.forEach(value => {
     76                let eventTime = entry.frame / context.sampleRate;
     77                let message = eventToString(
     78                    entry.event, value, eventTime, entry.extraArgs);
     79                // This is mostly to print out the event that is getting
     80                // inserted.  It should never ever throw.
     81                should(() => {
     82                  src.offset[entry.event](value, eventTime, entry.extraArgs);
     83                }, message).notThrow();
     84              });
     85            });
     86 
     87            src.start();
     88 
     89            context.startRendering()
     90                .then(audioBuffer => {
     91                  let audio = audioBuffer.getChannelData(0);
     92 
     93                  // Look through the test cases to figure out what the correct
     94                  // output values should be.
     95                  testCases.forEach(entry => {
     96                    let expected = entry.expectedOutputValue;
     97                    let frame = entry.outputTestFrame;
     98                    let time = frame / context.sampleRate;
     99                    should(
    100                        audio[frame], `Output at frame ${frame} (time ${time})`)
    101                        .beEqualTo(expected);
    102                  });
    103                })
    104                .then(() => task.done());
    105          });
    106 
    107      audit.define(
    108          {
    109            label: 'Linear + Expo',
    110            description: 'Different events at same time'
    111          },
    112          (task, should) => {
    113            // Should be a linear ramp up to the event time, and after a
    114            // constant value because the exponential ramp has ended.
    115            let testCase = [
    116              {event: 'linearRampToValueAtTime', value: 2, relError: 0},
    117              {event: 'setValueAtTime', value: 99},
    118              {event: 'exponentialRampToValueAtTime', value: 3},
    119            ];
    120            let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
    121            let prefix = 'Linear+Expo: ';
    122 
    123            testEventInsertion(prefix, should, eventFrame, testCase)
    124                .then(expectConstant(prefix, should, eventFrame, testCase))
    125                .then(() => task.done());
    126          });
    127 
    128      audit.define(
    129          {
    130            label: 'Expo + Linear',
    131            description: 'Different events at same time',
    132          },
    133          (task, should) => {
    134            // Should be an exponential ramp up to the event time, and after a
    135            // constant value because the linear ramp has ended.
    136            let testCase = [
    137              {
    138                event: 'exponentialRampToValueAtTime',
    139                value: 3,
    140                relError: 4.2533e-6
    141              },
    142              {event: 'setValueAtTime', value: 99},
    143              {event: 'linearRampToValueAtTime', value: 2},
    144            ];
    145            let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
    146            let prefix = 'Expo+Linear: ';
    147 
    148            testEventInsertion(prefix, should, eventFrame, testCase)
    149                .then(expectConstant(prefix, should, eventFrame, testCase))
    150                .then(() => task.done());
    151          });
    152 
    153      audit.define(
    154          {
    155            label: 'Linear + SetTarget',
    156            description: 'Different events at same time',
    157          },
    158          (task, should) => {
    159            // Should be a linear ramp up to the event time, and then a
    160            // decaying value.
    161            let testCase = [
    162              {event: 'linearRampToValueAtTime', value: 3, relError: 0},
    163              {event: 'setValueAtTime', value: 100},
    164              {event: 'setTargetAtTime', value: 0, extraArgs: 0.1},
    165            ];
    166            let eventFrame = 2 * RENDER_QUANTUM_FRAMES;
    167            let prefix = 'Linear+SetTarget: ';
    168 
    169            testEventInsertion(prefix, should, eventFrame, testCase)
    170                .then(audioBuffer => {
    171                  let audio = audioBuffer.getChannelData(0);
    172                  let prefix = 'Linear+SetTarget: ';
    173                  let eventTime = eventFrame / sampleRate;
    174                  let expectedValue = methodMap[testCase[0].event](
    175                      (eventFrame - 1) / sampleRate, 1, 0, testCase[0].value,
    176                      eventTime);
    177                  should(
    178                      audio[eventFrame - 1],
    179                      prefix +
    180                          `At time ${
    181                                     (eventFrame - 1) / sampleRate
    182                                   } (frame ${eventFrame - 1}) output`)
    183                      .beCloseTo(
    184                          expectedValue,
    185                          {threshold: testCase[0].relError || 0});
    186 
    187                  // The setValue should have taken effect
    188                  should(
    189                      audio[eventFrame],
    190                      prefix +
    191                          `At time ${eventTime} (frame ${eventFrame}) output`)
    192                      .beEqualTo(testCase[1].value);
    193 
    194                  // The final event is setTarget.  Compute the expected output.
    195                  let actual = audio.slice(eventFrame);
    196                  let expected = new Float32Array(actual.length);
    197                  for (let k = 0; k < expected.length; ++k) {
    198                    let t = (eventFrame + k) / sampleRate;
    199                    expected[k] = audioParamSetTarget(
    200                        t, testCase[1].value, eventTime, testCase[2].value,
    201                        testCase[2].extraArgs);
    202                  }
    203                  should(
    204                      actual,
    205                      prefix +
    206                          `At time ${eventTime} (frame ${
    207                                                         eventFrame
    208                                                       }) and later`)
    209                      .beCloseToArray(expected, {relativeThreshold: 2.6694e-7});
    210                })
    211                .then(() => task.done());
    212          });
    213 
    214      audit.define(
    215          {
    216            label: 'Multiple linear ramps at the same time',
    217            description: 'Verify output'
    218          },
    219          (task, should) => {
    220            testMultipleSameEvents(should, {
    221              method: 'linearRampToValueAtTime',
    222              prefix: 'Multiple linear ramps: ',
    223              threshold: 0
    224            }).then(() => task.done());
    225          });
    226 
    227      audit.define(
    228          {
    229            label: 'Multiple exponential ramps at the same time',
    230            description: 'Verify output'
    231          },
    232          (task, should) => {
    233            testMultipleSameEvents(should, {
    234              method: 'exponentialRampToValueAtTime',
    235              prefix: 'Multiple exponential ramps: ',
    236              threshold: 5.3924e-7
    237            }).then(() => task.done());
    238          });
    239 
    240      audit.run();
    241 
    242      // Takes a list of |testCases| consisting of automation methods and
    243      // schedules them to occur at |eventFrame|. |prefix| is a prefix for
    244      // messages produced by |should|.
    245      //
    246      // Each item in |testCases| is a dictionary with members:
    247      //   event     - the name of automation method to be inserted,
    248      //   value     - the value for the event,
    249      //   extraArgs - extra arguments if the event needs more than the value
    250      //               and time (such as setTargetAtTime).
    251      function testEventInsertion(prefix, should, eventFrame, testCases) {
    252        let context = new OfflineAudioContext(
    253            {length: 4 * RENDER_QUANTUM_FRAMES, sampleRate: sampleRate});
    254 
    255        // The source node to use.  Automations will be scheduled here.
    256        let src = new ConstantSourceNode(context, {offset: 0});
    257        src.connect(context.destination);
    258 
    259        // Initialize value to 1 at the beginning.
    260        src.offset.setValueAtTime(1, 0);
    261 
    262        // Test automations have this event time.
    263        let eventTime = eventFrame / context.sampleRate;
    264 
    265        // Sanity check that context is long enough for the test
    266        should(
    267            eventFrame < context.length,
    268            prefix + 'Context length is long enough for the test')
    269            .beTrue();
    270 
    271        // Automations to be tested.  The first event should be the actual
    272        // output up to the event time.  The last event should be the final
    273        // output from the event time and onwards.
    274        testCases.forEach(entry => {
    275          should(
    276              () => {
    277                src.offset[entry.event](
    278                    entry.value, eventTime, entry.extraArgs);
    279              },
    280              prefix +
    281                  eventToString(
    282                      entry.event, entry.value, eventTime, entry.extraArgs))
    283              .notThrow();
    284        });
    285 
    286        src.start();
    287 
    288        return context.startRendering();
    289      }
    290 
    291      // Verify output of test where the final value of the automation is
    292      // expected to be constant.
    293      function expectConstant(prefix, should, eventFrame, testCases) {
    294        return audioBuffer => {
    295          let audio = audioBuffer.getChannelData(0);
    296 
    297          let eventTime = eventFrame / sampleRate;
    298 
    299          // Compute the expected value of the first automation one frame before
    300          // the event time.  This is a quick check that the correct automation
    301          // was done.
    302          let expectedValue = methodMap[testCases[0].event](
    303              (eventFrame - 1) / sampleRate, 1, 0, testCases[0].value,
    304              eventTime);
    305          should(
    306              audio[eventFrame - 1],
    307              prefix +
    308                  `At time ${
    309                             (eventFrame - 1) / sampleRate
    310                           } (frame ${eventFrame - 1}) output`)
    311              .beCloseTo(expectedValue, {threshold: testCases[0].relError});
    312 
    313          // The last event scheduled is expected to set the value for all
    314          // future times.  Verify that the output has the expected value.
    315          should(
    316              audio.slice(eventFrame),
    317              prefix +
    318                  `At time ${eventTime} (frame ${
    319                                                 eventFrame
    320                                               }) and later, output`)
    321              .beConstantValueOf(testCases[testCases.length - 1].value);
    322        };
    323      }
    324 
    325      // Test output when two events of the same time are scheduled at the same
    326      // time.
    327      function testMultipleSameEvents(should, options) {
    328        let {method, prefix, threshold} = options;
    329 
    330        // Context for testing.
    331        let context =
    332            new OfflineAudioContext({length: 16384, sampleRate: sampleRate});
    333 
    334        let src = new ConstantSourceNode(context);
    335        src.connect(context.destination);
    336 
    337        let initialValue = 1;
    338 
    339        // Informative print
    340        should(() => {
    341          src.offset.setValueAtTime(initialValue, 0);
    342        }, prefix + `setValueAtTime(${initialValue}, 0)`).notThrow();
    343 
    344        let frame = 64;
    345        let time = frame / context.sampleRate;
    346        let values = [2, 7, 10];
    347 
    348        // Schedule two events of the same type at the same time, but with
    349        // different values.
    350 
    351        values.forEach(value => {
    352          // Informative prints to show what we're doing in this test.
    353          should(
    354              () => {
    355                src.offset[method](value, time);
    356              },
    357              prefix +
    358                  eventToString(
    359                      method,
    360                      value,
    361                      time,
    362                      ))
    363              .notThrow();
    364        })
    365 
    366        src.start();
    367 
    368        return context.startRendering().then(audioBuffer => {
    369          let actual = audioBuffer.getChannelData(0);
    370 
    371          // The output should be a ramp from time 0 to the event time.  But we
    372          // only verify the value just before the event time, which should be
    373          // fairly close to values[0].  (But compute the actual expected value
    374          // to be sure.)
    375          let expected = methodMap[method](
    376              (frame - 1) / context.sampleRate, initialValue, 0, values[0],
    377              time);
    378          should(actual[frame - 1], prefix + `Output at frame ${frame - 1}`)
    379              .beCloseTo(expected, {threshold: threshold, precision: 3});
    380 
    381          // Any other values shouldn't show up in the output.  Only the value
    382          // from last event should appear.  We only check the value at the
    383          // event time.
    384          should(
    385              actual[frame], prefix + `Output at frame ${frame} (${time} sec)`)
    386              .beEqualTo(values[values.length - 1]);
    387        });
    388      }
    389 
    390      // Convert an automation method to a string for printing.
    391      function eventToString(method, value, time, extras) {
    392        let string = method + '(';
    393        string += (value instanceof Array) ? `[${value}]` : value;
    394        string += ', ' + time;
    395        if (extras) {
    396          string += ', ' + extras;
    397        }
    398        string += ')';
    399        return string;
    400      }
    401 
    402      // Map between the automation method name and a function that computes the
    403      // output value of the automation method.
    404      const methodMap = {
    405        linearRampToValueAtTime: audioParamLinearRamp,
    406        exponentialRampToValueAtTime: audioParamExponentialRamp,
    407        setValueAtTime: (t, v) => v
    408      };
    409    </script>
    410  </body>
    411 </html>