tor-browser

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

test_convolverNodeChannelInterpretationChanges.html (7323B)


      1 <!DOCTYPE html>
      2 <title>Test up-mixing in ConvolverNode after ChannelInterpretation change</title>
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script>
      6 // This test is not in wpt because it requires that multiple changes to the
      7 // nodes in an AudioContext during a single event will be processed by the
      8 // audio thread in a single transaction.  Gecko provides that, but this is not
      9 // currently required by the Web Audio API.
     10 
     11 const EPSILON = Math.pow(2, -23);
     12 // sampleRate is a power of two so that delay times are exact in base-2
     13 // floating point arithmetic.
     14 const SAMPLE_RATE = 32768;
     15 // Length of initial mono signal in frames, if the test has an initial mono
     16 // signal.  This is more than one block to ensure that at least one block
     17 // will be mono, even if interpolation in the DelayNode means that stereo is
     18 // output one block earlier than if frames are delayed without interpolation.
     19 const MONO_FRAMES = 256;
     20 // Length of response buffer.  This is greater than 1 to ensure that the
     21 // convolver has stereo output at least one block after stereo input is
     22 // disconnected.
     23 const RESPONSE_FRAMES = 2;
     24 
     25 function test_interpretation_change(t, initialInterpretation, initialMonoFrames)
     26 {
     27  let context = new AudioContext({sampleRate: SAMPLE_RATE});
     28 
     29  // Three independent signals.  These are constant so that results are
     30  // independent of the timing of the `ended` event.
     31  let monoOffset = 0.25
     32  let monoSource = new ConstantSourceNode(context, {offset: monoOffset});
     33  let leftOffset = 0.125;
     34  let rightOffset = 0.5;
     35  let leftSource = new ConstantSourceNode(context, {offset: leftOffset});
     36  let rightSource = new ConstantSourceNode(context, {offset: rightOffset});
     37  monoSource.start();
     38  leftSource.start();
     39  rightSource.start();
     40 
     41  let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
     42  leftSource.connect(stereoMerger, 0, 0);
     43  rightSource.connect(stereoMerger, 0, 1);
     44 
     45  // The DelayNode initially has a single channel of silence, and so the
     46  // output of the delay node is first mono silence (if there is a non-zero
     47  // initialMonoFrames), then stereo.  In Gecko, this triggers a convolver
     48  // configuration that is different for different channelInterpretations.
     49  let delay =
     50      new DelayNode(context,
     51                    {maxDelayTime: MONO_FRAMES / context.sampleRate,
     52                     delayTime: initialMonoFrames / context.sampleRate});
     53  stereoMerger.connect(delay);
     54 
     55  // Two convolvers with the same impulse response.  The test convolver will
     56  // process a mix of stereo and mono signals.  The reference convolver will
     57  // always process stereo, including the up-mixed mono signal.
     58  let response = new AudioBuffer({numberOfChannels: 1,
     59                                  length: RESPONSE_FRAMES,
     60                                  sampleRate: context.sampleRate});
     61  response.getChannelData(0)[response.length - 1] = 1;
     62 
     63  let testConvolver = new ConvolverNode(context,
     64                                        {disableNormalization: true,
     65                                         buffer: response});
     66  testConvolver.channelInterpretation = initialInterpretation;
     67  let referenceConvolver = new ConvolverNode(context,
     68                                             {disableNormalization: true,
     69                                              buffer: response});
     70  // No need to set referenceConvolver.channelInterpretation because
     71  // input is always stereo, due to up-mixing at gain node.
     72  let referenceMixer = new GainNode(context);
     73  referenceMixer.channelCount = 2;
     74  referenceMixer.channelCountMode = "explicit";
     75  referenceMixer.channelInterpretation = initialInterpretation;
     76  referenceMixer.connect(referenceConvolver);
     77 
     78  delay.connect(testConvolver);
     79  delay.connect(referenceMixer);
     80 
     81  monoSource.connect(testConvolver);
     82  monoSource.connect(referenceMixer);
     83 
     84  // A timer sends 'ended' when the convolvers are known to be processing
     85  // stereo.
     86  let timer = new ConstantSourceNode(context);
     87  timer.start();
     88  timer.stop((initialMonoFrames + 1) / context.sampleRate);
     89 
     90  timer.onended = t.step_func(() => {
     91    let changedInterpretation =
     92        initialInterpretation == "speakers" ? "discrete" : "speakers";
     93 
     94    // Switch channelInterpretation in test and reference paths.
     95    testConvolver.channelInterpretation = changedInterpretation;
     96    referenceMixer.channelInterpretation = changedInterpretation;
     97 
     98    // Disconnect the stereo input from both test and reference convolvers.
     99    // The disconnected convolvers will continue to output stereo for at least
    100    // one frame.  The test convolver will up-mix its mono input into its two
    101    // buffers.
    102    delay.disconnect();
    103 
    104    // Capture the outputs in a script processor.
    105    //
    106    // The first two channels contain signal where some up-mixing occurs
    107    // internally to the test convolver.
    108    //
    109    // The last two channels are expected to contain the same signal, but
    110    // up-mixing was performed at a GainNode prior to convolution.
    111    //
    112    // Two stereo splitters will collect test and reference outputs.
    113    let testSplitter =
    114        new ChannelSplitterNode(context, {numberOfOutputs: 2});
    115    let referenceSplitter =
    116        new ChannelSplitterNode(context, {numberOfOutputs: 2});
    117    testConvolver.connect(testSplitter);
    118    referenceConvolver.connect(referenceSplitter);
    119 
    120    let outputMerger = new ChannelMergerNode(context, {numberOfInputs: 4});
    121    testSplitter.connect(outputMerger, 0, 0);
    122    testSplitter.connect(outputMerger, 1, 1);
    123    referenceSplitter.connect(outputMerger, 0, 2);
    124    referenceSplitter.connect(outputMerger, 1, 3);
    125 
    126    let processor = context.createScriptProcessor(256, 4, 0);
    127    outputMerger.connect(processor);
    128 
    129    processor.onaudioprocess = t.step_func_done((e) => {
    130      e.target.onaudioprocess = null;
    131      outputMerger.disconnect();
    132 
    133      // The test convolver output is stereo for the first block.
    134      let length = 128;
    135 
    136      let buffer = e.inputBuffer;
    137      let maxDiff = -1.0;
    138      let frameIndex = 0;
    139      let channelIndex = 0;
    140      for (let c = 0; c < 2; ++c) {
    141        let testOutput = buffer.getChannelData(0 + c);
    142        let referenceOutput = buffer.getChannelData(2 + c);
    143        for (var i = 0; i < length; ++i) {
    144          var diff = Math.abs(testOutput[i] - referenceOutput[i]);
    145          if (diff > maxDiff) {
    146            maxDiff = diff;
    147            frameIndex = i;
    148            channelIndex = c;
    149          }
    150        }
    151      }
    152      assert_approx_equals(buffer.getChannelData(0 + channelIndex)[frameIndex],
    153                           buffer.getChannelData(2 + channelIndex)[frameIndex],
    154                           EPSILON,
    155                           `output at ${frameIndex} ` +
    156                             `in channel ${channelIndex}` );
    157    });
    158  });
    159 }
    160 
    161 async_test((t) => test_interpretation_change(t, "speakers", MONO_FRAMES),
    162           "speakers to discrete, initially mono");
    163 async_test((t) => test_interpretation_change(t, "discrete", MONO_FRAMES),
    164           "discrete to speakers");
    165 // Gecko uses a separate path for "speakers" initial up-mixing when the
    166 // convolver's first input is stereo, so test that separately.
    167 async_test((t) => test_interpretation_change(t, "speakers", 0),
    168           "speakers to discrete, initially stereo");
    169 </script>