tor-browser

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

automation-rate-testing.js (12980B)


      1 // Test k-rate vs a-rate AudioParams.
      2 //
      3 // |options| describes how the testing of the AudioParam should be done:
      4 //
      5 //   sourceNodeName: name of source node to use for testing; defaults to
      6 //                   'OscillatorNode'.  If set to 'none', then no source node
      7 //                   is created for testing and it is assumed that the AudioNode
      8 //                   under test are sources and need to be started.
      9 //   verifyPieceWiseConstant: if true, verify that the k-rate output is
     10 //                            piecewise constant for each render quantum.
     11 //   nodeName:  name of the AudioNode to be tested
     12 //   nodeOptions:  options to be used in the AudioNode constructor
     13 //
     14 //   prefix: Prefix for all output messages (to make them unique for
     15 //           testharness)
     16 //
     17 //   rateSettings: A vector of dictionaries specifying how to set the automation
     18 //                 rate(s):
     19 //       name: Name of the AudioParam
     20 //       value: The automation rate for the AudioParam given by |name|.
     21 //
     22 //   automations: A vector of dictionaries specifying how to automate each
     23 //                AudioParam:
     24 //       name: Name of the AudioParam
     25 //
     26 //       methods: A vector of dictionaries specifying the automation methods to
     27 //                be used for testing:
     28 //           name: Automation method to call
     29 //           options: Arguments for the automation method
     30 //
     31 // Testing is somewhat rudimentary.  We create two nodes of the same type.  One
     32 // node uses the default automation rates for each AudioParam (expecting them to
     33 // be a-rate).  The second node sets the automation rate of AudioParams to
     34 // "k-rate".  The set is speciified by |options.rateSettings|.
     35 //
     36 // For both of these nodes, the same set of automation methods (given by
     37 // |options.automations|) is applied.  A simple oscillator is connected to each
     38 // node which in turn are connected to different channels of an offline context.
     39 // Channel 0 is the k-rate node output; channel 1, the a-rate output; and
     40 // channel 2, the difference between the outputs.
     41 //
     42 // Success is declared if the difference signal is not exactly zero.  This means
     43 // the automations did different things, as expected.
     44 //
     45 // The promise from |startRendering| is returned.
     46 function doTest(context, should, options) {
     47  let merger = new ChannelMergerNode(
     48      context, {numberOfInputs: context.destination.channelCount});
     49  merger.connect(context.destination);
     50 
     51  let src = null;
     52 
     53  // Skip creating a source to drive the graph if |sourceNodeName| is 'none'.
     54  // If |sourceNodeName| is given, use that, else default to OscillatorNode.
     55  if (options.sourceNodeName !== 'none') {
     56    src = new window[options.sourceNodeName || 'OscillatorNode'](context);
     57  }
     58 
     59  let kRateNode = new window[options.nodeName](context, options.nodeOptions);
     60  let aRateNode = new window[options.nodeName](context, options.nodeOptions);
     61  let inverter = new GainNode(context, {gain: -1});
     62 
     63  // Set kRateNode filter to use k-rate params.
     64  options.rateSettings.forEach(setting => {
     65    kRateNode[setting.name].automationRate = setting.value;
     66    // Mostly for documentation in the output.  These should always
     67    // pass.
     68    should(
     69        kRateNode[setting.name].automationRate,
     70        `${options.prefix}: Setting ${
     71                                      setting.name
     72                                    }.automationRate to "${setting.value}"`)
     73        .beEqualTo(setting.value);
     74  });
     75 
     76  // Run through all automations for each node separately. (Mostly to keep
     77  // output of automations together.)
     78  options.automations.forEach(param => {
     79    param.methods.forEach(method => {
     80      // Most for documentation in the output.  These should never throw.
     81      let message = `${param.name}.${method.name}(${method.options})`
     82      should(() => {
     83        kRateNode[param.name][method.name](...method.options);
     84      }, options.prefix + ': k-rate node: ' + message).notThrow();
     85    });
     86  });
     87  options.automations.forEach(param => {
     88    param.methods.forEach(method => {
     89      // Most for documentation in the output.  These should never throw.
     90      let message = `${param.name}.${method.name}(${method.options})`
     91      should(() => {
     92        aRateNode[param.name][method.name](...method.options);
     93      }, options.prefix + ': a-rate node:' + message).notThrow();
     94    });
     95  });
     96 
     97  // Connect the source, if specified.
     98  if (src) {
     99    src.connect(kRateNode);
    100    src.connect(aRateNode);
    101  }
    102 
    103  // The k-rate result is channel 0, and the a-rate result is channel 1.
    104  kRateNode.connect(merger, 0, 0);
    105  aRateNode.connect(merger, 0, 1);
    106 
    107  // Compute the difference between the a-rate and k-rate results and send
    108  // that to channel 2.
    109  kRateNode.connect(merger, 0, 2);
    110  aRateNode.connect(inverter).connect(merger, 0, 2);
    111 
    112  if (src) {
    113    src.start();
    114  } else {
    115    // If there's no source, then assume the test nodes are sources and start
    116    // them.
    117    kRateNode.start();
    118    aRateNode.start();
    119  }
    120 
    121  return context.startRendering().then(renderedBuffer => {
    122    let kRateOutput = renderedBuffer.getChannelData(0);
    123    let aRateOutput = renderedBuffer.getChannelData(1);
    124    let diff = renderedBuffer.getChannelData(2);
    125 
    126    // Some informative messages to print out values of the k-rate and
    127    // a-rate outputs.  These should always pass.
    128    should(
    129        kRateOutput, `${options.prefix}: Output of k-rate ${options.nodeName}`)
    130        .beEqualToArray(kRateOutput);
    131    should(
    132        aRateOutput, `${options.prefix}: Output of a-rate ${options.nodeName}`)
    133        .beEqualToArray(aRateOutput);
    134 
    135    // The real test.  If k-rate AudioParam is working correctly, the
    136    // k-rate result MUST differ from the a-rate result.
    137    should(
    138        diff,
    139        `${
    140           options.prefix
    141         }: Difference between a-rate and k-rate ${options.nodeName}`)
    142        .notBeConstantValueOf(0);
    143 
    144    if (options.verifyPieceWiseConstant) {
    145      // Verify that the output from the k-rate parameter is step-wise
    146      // constant.
    147      for (let k = 0; k < kRateOutput.length; k += 128) {
    148        should(
    149            kRateOutput.slice(k, k + 128),
    150            `${options.prefix} k-rate output [${k}: ${k + 127}]`)
    151            .beConstantValueOf(kRateOutput[k]);
    152      }
    153    }
    154  });
    155 }
    156 
    157 // Test k-rate vs a-rate AudioParams.
    158 //
    159 // |options| describes how the testing of the AudioParam should be done:
    160 //
    161 //   sourceNodeName: name of source node to use for testing; defaults to
    162 //                   'OscillatorNode'.  If set to 'none', then no source node
    163 //                   is created for testing and it is assumed that the AudioNode
    164 //                   under test are sources and need to be started.
    165 //   verifyPieceWiseConstant: if true, verify that the k-rate output is
    166 //                            piecewise constant for each render quantum.
    167 //   nodeName:  name of the AudioNode to be tested
    168 //   nodeOptions:  options to be used in the AudioNode constructor
    169 //
    170 //   prefix: Prefix for all output messages (to make them unique for
    171 //           testharness)
    172 //
    173 //   rateSettings: A vector of dictionaries specifying how to set the automation
    174 //                 rate(s):
    175 //       name: Name of the AudioParam
    176 //       value: The automation rate for the AudioParam given by |name|.
    177 //
    178 //   automations: A vector of dictionaries specifying how to automate each
    179 //                AudioParam:
    180 //       name: Name of the AudioParam
    181 //
    182 //       methods: A vector of dictionaries specifying the automation methods to
    183 //                be used for testing:
    184 //           name: Automation method to call
    185 //           options: Arguments for the automation method
    186 //
    187 // Testing is somewhat rudimentary.  We create two nodes of the same type.  One
    188 // node uses the default automation rates for each AudioParam (expecting them to
    189 // be a-rate).  The second node sets the automation rate of AudioParams to
    190 // "k-rate".  The set is speciified by |options.rateSettings|.
    191 //
    192 // For both of these nodes, the same set of automation methods (given by
    193 // |options.automations|) is applied.  A simple oscillator is connected to each
    194 // node which in turn are connected to different channels of an offline context.
    195 // Channel 0 is the k-rate node output; channel 1, the a-rate output; and
    196 // channel 2, the difference between the outputs.
    197 //
    198 // Success is declared if the difference signal is not exactly zero.  This means
    199 // the automations did different things, as expected.
    200 //
    201 // The promise from |startRendering| is returned.
    202 function doTest_W3TH(context, options) {
    203  const merger = new ChannelMergerNode(context, {
    204    numberOfInputs: context.destination.channelCount
    205  });
    206  merger.connect(context.destination);
    207 
    208  let src = null;
    209 
    210  // Skip creating a source to drive the graph if |sourceNodeName| is 'none'.
    211  // If |sourceNodeName| is given, use that, else default to OscillatorNode.
    212  if (options.sourceNodeName !== 'none') {
    213    src = new window[options.sourceNodeName || 'OscillatorNode'](context);
    214  }
    215 
    216  const kRateNode = new window[options.nodeName](context, options.nodeOptions);
    217  const aRateNode = new window[options.nodeName](context, options.nodeOptions);
    218  const inverter = new GainNode(context, { gain: -1 });
    219 
    220  // Set kRateNode filter to use k-rate params.
    221  options.rateSettings.forEach(setting => {
    222    kRateNode[setting.name].automationRate = setting.value;
    223    // Mostly for documentation in the output. These should always
    224    // pass.
    225    assert_equals(
    226        kRateNode[setting.name].automationRate, setting.value,
    227        `${options.prefix}: Setting ${setting.name}.automationRate ` +
    228            `to "${setting.value}"`);
    229  });
    230 
    231  // Run through all automations for each node separately. (Mostly to keep
    232  // output of automations together.)
    233  options.automations.forEach(param => {
    234    param.methods.forEach(method => {
    235      // Mostly for documentation in the output.  These should never throw.
    236      const message = `${param.name}.${method.name}(${method.options})`;
    237      try {
    238        kRateNode[param.name][method.name](...method.options);
    239      } catch (e) {
    240        assert_unreached(
    241            `${options.prefix}: k-rate node: ${message} threw: ${e.message}`);
    242      }
    243    });
    244  });
    245  options.automations.forEach(param => {
    246    param.methods.forEach(method => {
    247      // Mostly for documentation in the output.  These should never throw.
    248      const message = `${param.name}.${method.name}(${method.options})`;
    249      try {
    250        aRateNode[param.name][method.name](...method.options);
    251      } catch (e) {
    252        assert_unreached(
    253            `${options.prefix}: a-rate node: ${message} threw: ${e.message}`);
    254      }
    255    });
    256  });
    257 
    258  // Connect the source, if specified.
    259  if (src) {
    260    src.connect(kRateNode);
    261    src.connect(aRateNode);
    262  }
    263 
    264  // The k-rate result is channel 0, and the a-rate result is channel 1.
    265  kRateNode.connect(merger, 0, 0);
    266  aRateNode.connect(merger, 0, 1);
    267 
    268  // Compute the difference between the a-rate and k-rate results and send
    269  // that to channel 2.
    270  kRateNode.connect(merger, 0, 2);
    271  aRateNode.connect(inverter).connect(merger, 0, 2);
    272 
    273  if (src) {
    274    src.start();
    275  } else {
    276    // If there's no source, then assume the test nodes are sources and start
    277    // them.
    278    kRateNode.start();
    279    aRateNode.start();
    280  }
    281 
    282  return context.startRendering().then(renderedBuffer => {
    283    const kRateOutput = renderedBuffer.getChannelData(0);
    284    const aRateOutput = renderedBuffer.getChannelData(1);
    285    const diff = renderedBuffer.getChannelData(2);
    286 
    287    // Some informative messages to print out values of the k-rate and
    288    // a-rate outputs.  These should always pass.
    289    // (In testharness, assertions only report on failure,
    290    // so we sanity-check types.)
    291    assert_true(
    292        kRateOutput instanceof Float32Array,
    293        `${options.prefix}: Output of k-rate `+
    294            `${options.nodeName} is Float32Array`);
    295    assert_true(
    296        aRateOutput instanceof Float32Array,
    297        `${options.prefix}: Output of a-rate `+
    298            `${options.nodeName} is Float32Array`);
    299 
    300    // The real test.  If k-rate AudioParam is working correctly, the
    301    // k-rate result MUST differ from the a-rate result.
    302    let allZero = true;
    303    for (let i = 0; i < diff.length; ++i) {
    304      if (diff[i] !== 0) {allZero = false; break;}
    305    }
    306    assert_false(
    307        allZero,
    308        `${options.prefix}: Difference between a-rate and k-rate` +
    309            `${options.nodeName} must not be identically 0`);
    310 
    311    if (options.verifyPieceWiseConstant) {
    312      // Verify that the output from the k-rate parameter is step-wise
    313      // constant.
    314      for (let k = 0; k < kRateOutput.length; k += 128) {
    315        const end = Math.min(k + 128, kRateOutput.length);
    316        const v0 = kRateOutput[k];
    317        for (let i = k + 1; i < end; ++i) {
    318          assert_equals(
    319              kRateOutput[i],
    320              v0,
    321              `${options.prefix} k-rate output [${k}: ${end - 1}]` +
    322                  `should be piecewise constant`,
    323          );
    324        }
    325      }
    326    }
    327  });
    328 }