tor-browser

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

test_convolver-upmixing-1-channel-response.html (6617B)


      1 <!DOCTYPE html>
      2 <title>Test that up-mixing signals in ConvolverNode processing is linear</title>
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script>
      6 const EPSILON = 3.0 * Math.pow(2, -22);
      7 // sampleRate is a power of two so that delay times are exact in base-2
      8 // floating point arithmetic.
      9 const SAMPLE_RATE = 32768;
     10 // Length of stereo convolver input in frames (arbitrary):
     11 const STEREO_FRAMES = 256;
     12 // Length of mono signal in frames.  This is more than two blocks to ensure
     13 // that at least one block will be mono, even if interpolation in the
     14 // DelayNode means that stereo is output one block earlier and later than
     15 // if frames are delayed without interpolation.
     16 const MONO_FRAMES = 384;
     17 // Length of response buffer:
     18 const RESPONSE_FRAMES = 256;
     19 
     20 function test_linear_upmixing(channelInterpretation, initial_mono_frames)
     21 {
     22  let stereo_input_end = initial_mono_frames + STEREO_FRAMES;
     23  // Total length:
     24  let length = stereo_input_end + RESPONSE_FRAMES + MONO_FRAMES + STEREO_FRAMES;
     25  // The first two channels contain signal where some up-mixing occurs
     26  // internally to a ConvolverNode when a stereo signal is added and removed.
     27  // The last two channels are expected to contain the same signal, but mono
     28  // and stereo signals are convolved independently before up-mixing the mono
     29  // output to mix with the stereo output.
     30  let context = new OfflineAudioContext({numberOfChannels: 4,
     31                                         length,
     32                                         sampleRate: SAMPLE_RATE});
     33 
     34  let response = new AudioBuffer({numberOfChannels: 1,
     35                                  length: RESPONSE_FRAMES,
     36                                  sampleRate: context.sampleRate});
     37 
     38  // Two stereo channel splitters will collect test and reference outputs.
     39  let destinationMerger = new ChannelMergerNode(context, {numberOfInputs: 4});
     40  destinationMerger.connect(context.destination);
     41  let testSplitter =
     42      new ChannelSplitterNode(context, {numberOfOutputs: 2});
     43  let referenceSplitter =
     44      new ChannelSplitterNode(context, {numberOfOutputs: 2});
     45  testSplitter.connect(destinationMerger, 0, 0);
     46  testSplitter.connect(destinationMerger, 1, 1);
     47  referenceSplitter.connect(destinationMerger, 0, 2);
     48  referenceSplitter.connect(destinationMerger, 1, 3);
     49 
     50  // A GainNode mixes reference stereo and mono signals because up-mixing
     51  // cannot be performed at a channel splitter.
     52  let referenceGain = new GainNode(context);
     53  referenceGain.connect(referenceSplitter);
     54  referenceGain.channelInterpretation = channelInterpretation;
     55 
     56  // The impulse response for convolution contains two impulses so as to test
     57  // effects in at least two processing blocks.
     58  response.getChannelData(0)[0] = 0.5;
     59  response.getChannelData(0)[response.length - 1] = 0.5;
     60 
     61  let testConvolver = new ConvolverNode(context, {disableNormalization: true,
     62                                                  buffer: response});
     63  testConvolver.channelInterpretation = channelInterpretation;
     64  let referenceMonoConvolver = new ConvolverNode(context,
     65                                                 {disableNormalization: true,
     66                                                  buffer: response});
     67  let referenceStereoConvolver = new ConvolverNode(context,
     68                                                   {disableNormalization: true,
     69                                                    buffer: response});
     70  // No need to set referenceStereoConvolver.channelInterpretation because
     71  // input is either silent or stereo.
     72  testConvolver.connect(testSplitter);
     73  // Mix reference convolver output.
     74  referenceMonoConvolver.connect(referenceGain);
     75  referenceStereoConvolver.connect(referenceGain);
     76 
     77  // The DelayNode initially has a single channel of silence, which is used to
     78  // switch the stereo signal in and out.  The output of the delay node is
     79  // first mono silence (if there is a non-zero initial_mono_frames), then
     80  // stereo, then mono silence, and finally stereo again.  maxDelayTime is
     81  // used to generate the middle mono silence period from the initial silence
     82  // in the DelayNode and then generate the final period of stereo from its
     83  // initial input.
     84  let maxDelayTime = (length - STEREO_FRAMES) / context.sampleRate;
     85  let delay =
     86      new DelayNode(context,
     87                    {maxDelayTime,
     88                     delayTime: initial_mono_frames / context.sampleRate});
     89  // Schedule an increase in the delay to return to mono silence.
     90  delay.delayTime.setValueAtTime(maxDelayTime,
     91                                 stereo_input_end / context.sampleRate);
     92  delay.connect(testConvolver);
     93  delay.connect(referenceStereoConvolver);
     94 
     95  let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
     96  stereoMerger.connect(delay);
     97 
     98  // Three independent signals
     99  let monoSignal = new OscillatorNode(context, {frequency: 440});
    100  let leftSignal = new OscillatorNode(context, {frequency: 450});
    101  let rightSignal = new OscillatorNode(context, {frequency: 460});
    102  monoSignal.connect(testConvolver);
    103  monoSignal.connect(referenceMonoConvolver);
    104  leftSignal.connect(stereoMerger, 0, 0);
    105  rightSignal.connect(stereoMerger, 0, 1);
    106  monoSignal.start();
    107  leftSignal.start();
    108  rightSignal.start();
    109 
    110  return context.startRendering().
    111    then((buffer) => {
    112      let maxDiff = -1.0;
    113      let frameIndex = 0;
    114      let channelIndex = 0;
    115      for (let c = 0; c < 2; ++c) {
    116        let testOutput = buffer.getChannelData(0 + c);
    117        let referenceOutput = buffer.getChannelData(2 + c);
    118        for (var i = 0; i < buffer.length; ++i) {
    119          var diff = Math.abs(testOutput[i] - referenceOutput[i]);
    120          if (diff > maxDiff) {
    121            maxDiff = diff;
    122            frameIndex = i;
    123            channelIndex = c;
    124          }
    125        }
    126      }
    127      assert_approx_equals(buffer.getChannelData(0 + channelIndex)[frameIndex],
    128                           buffer.getChannelData(2 + channelIndex)[frameIndex],
    129                           EPSILON,
    130                           `output at ${frameIndex} ` +
    131                             `in channel ${channelIndex}` );
    132    });
    133 }
    134 
    135 promise_test(() => test_linear_upmixing("speakers", MONO_FRAMES),
    136             "speakers, initially mono");
    137 promise_test(() => test_linear_upmixing("discrete", MONO_FRAMES),
    138             "discrete");
    139 // Gecko uses a separate path for "speakers" up-mixing when the convolver's
    140 // first input is stereo, so test that separately.
    141 promise_test(() => test_linear_upmixing("speakers", 0),
    142             "speakers, initially stereo");
    143 </script>