tor-browser

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

convolver-response-1-chan.html (15710B)


      1 <!DOCTYPE html>
      2 <html>
      3  <head>
      4    <title>
      5      Test Convolver Channel Outputs for Response with 1 channel
      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  </head>
     12  <body>
     13    <script id="layout-test-code">
     14      // Test various convolver configurations when the convolver response has
     15      // one channel (mono).
     16 
     17      // This is somewhat arbitrary.  It is the minimum value for which tests
     18      // pass with both FFmpeg and KISS FFT implementations for 256 points.
     19      // The value was similar for each implementation.
     20      const absoluteThreshold = Math.pow(2, -21);
     21 
     22      // Fairly arbitrary sample rate, except that we want the rate to be a
     23      // power of two so that 1/sampleRate is exactly representable as a
     24      // single-precision float.
     25      let sampleRate = 8192;
     26 
     27      // A fairly arbitrary number of frames, except the number of frames should
     28      // be more than a few render quanta.
     29      let renderFrames = 10 * 128;
     30 
     31      let audit = Audit.createTaskRunner();
     32 
     33      // Convolver response
     34      let response;
     35 
     36      audit.define(
     37          {
     38            label: 'initialize',
     39            description: 'Convolver response with one channel'
     40          },
     41          (task, should) => {
     42            // Convolver response
     43            should(
     44                () => {
     45                  response = new AudioBuffer(
     46                      {numberOfChannels: 1, length: 2, sampleRate: sampleRate});
     47                  response.getChannelData(0)[1] = 1;
     48                },
     49                'new AudioBuffer({numberOfChannels: 1, length: 2, sampleRate: ' +
     50                    sampleRate + '})')
     51                .notThrow();
     52 
     53            task.done();
     54          });
     55 
     56      audit.define(
     57          {label: '1-channel input', description: 'produces 1-channel output'},
     58          (task, should) => {
     59            // Create a 3-channel context:  channel 0 = convolver under test,
     60            // channel 1: test that convolver output is not stereo, channel 2:
     61            // expected output.  The context MUST be discrete so that the
     62            // channels don't get mixed in some unexpected way.
     63            let context = new OfflineAudioContext(3, renderFrames, sampleRate);
     64            context.destination.channelInterpretation = 'discrete';
     65 
     66            let src = new OscillatorNode(context);
     67            let conv = new ConvolverNode(
     68                context, {disableNormalization: true, buffer: response});
     69 
     70            // Splitter node to verify that the output of the convolver is mono.
     71            // channelInterpretation must be 'discrete' so we don't do any
     72            // mixing of the input to the node.
     73            let splitter = new ChannelSplitterNode(
     74                context,
     75                {numberOfOutputs: 2, channelInterpretation: 'discrete'});
     76 
     77            // Final merger to feed all of the individual channels into the
     78            // destination.
     79            let merger = new ChannelMergerNode(context, {numberOfInputs: 3});
     80 
     81            src.connect(conv).connect(splitter);
     82            splitter.connect(merger, 0, 0);
     83            splitter.connect(merger, 1, 1);
     84 
     85            // The convolver response is a 1-sample delay.  Use a delay node to
     86            // implement this.
     87            let delay =
     88                new DelayNode(context, {delayTime: 1 / context.sampleRate});
     89            src.connect(delay);
     90            delay.connect(merger, 0, 2);
     91 
     92            merger.connect(context.destination);
     93 
     94            src.start();
     95 
     96            context.startRendering()
     97                .then(audioBuffer => {
     98                  // Extract out the three channels
     99                  let actual = audioBuffer.getChannelData(0);
    100                  let c1 = audioBuffer.getChannelData(1);
    101                  let expected = audioBuffer.getChannelData(2);
    102 
    103                  // c1 is expected to be zero.
    104                  should(c1, '1: Channel 1').beConstantValueOf(0);
    105 
    106                  // The expected and actual results should be identical
    107                  should(actual, 'Convolver output')
    108                    .beCloseToArray(expected,
    109                                    {absoluteThreshold: absoluteThreshold});
    110                })
    111                .then(() => task.done());
    112          });
    113 
    114      audit.define(
    115          {label: '2-channel input', description: 'produces 2-channel output'},
    116          (task, should) => {
    117            downMixTest({numberOfInputs: 2, prefix: '2'}, should)
    118                .then(() => task.done());
    119          });
    120 
    121      audit.define(
    122          {
    123            label: '3-channel input',
    124            description: '3->2 downmix producing 2-channel output'
    125          },
    126          (task, should) => {
    127            downMixTest({numberOfInputs: 3, prefix: '3'}, should)
    128                .then(() => task.done());
    129          });
    130 
    131      audit.define(
    132          {
    133            label: '4-channel input',
    134            description: '4->2 downmix producing 2-channel output'
    135          },
    136          (task, should) => {
    137            downMixTest({numberOfInputs: 4, prefix: '4'}, should)
    138                .then(() => task.done());
    139          });
    140 
    141      audit.define(
    142          {
    143            label: '5.1-channel input',
    144            description: '5.1->2 downmix producing 2-channel output'
    145          },
    146          (task, should) => {
    147            // Scale tolerance by maximum amplitude expected in down-mix
    148            // output.
    149            let threshold = (1.0 + Math.sqrt(0.5) * 2) * absoluteThreshold;
    150 
    151            downMixTest({numberOfInputs: 6, prefix: '5.1',
    152                         absoluteThreshold: threshold}, should)
    153                .then(() => task.done());
    154          });
    155 
    156      audit.define(
    157          {
    158            label: '3-channel input, explicit',
    159            description: '3->2 explicit downmix producing 2-channel output'
    160          },
    161          (task, should) => {
    162            downMixTest(
    163                {
    164                  channelCountMode: 'explicit',
    165                  numberOfInputs: 3,
    166                  prefix: '3 chan downmix explicit'
    167                },
    168                should)
    169                .then(() => task.done());
    170          });
    171 
    172      audit.define(
    173          {
    174            label: '4-channel input, explicit',
    175            description: '4->2 explicit downmix producing 2-channel output'
    176          },
    177          (task, should) => {
    178            downMixTest(
    179                {
    180                  channelCountMode: 'explicit',
    181                  numberOfInputs: 4,
    182                  prefix: '4 chan downmix explicit'
    183                },
    184                should)
    185                .then(() => task.done());
    186          });
    187 
    188      audit.define(
    189          {
    190            label: '5.1-channel input, explicit',
    191            description: '5.1->2 explicit downmix producing 2-channel output'
    192          },
    193          (task, should) => {
    194            // Scale tolerance by maximum amplitude expected in down-mix
    195            // output.
    196            let threshold = (1.0 + Math.sqrt(0.5) * 2) * absoluteThreshold;
    197 
    198            downMixTest(
    199                {
    200                  channelCountMode: 'explicit',
    201                  numberOfInputs: 6,
    202                  prefix: '5.1 chan downmix explicit',
    203                  absoluteThreshold: threshold
    204                },
    205                should)
    206                .then(() => task.done());
    207          });
    208 
    209      audit.define(
    210          {
    211            label: 'mono-upmix-explicit',
    212            description: '1->2 upmix, count mode explicit'
    213          },
    214          (task, should) => {
    215            upMixTest(should, {channelCountMode: 'explicit'})
    216                .then(buffer => {
    217                  let length = buffer.length;
    218                  let input = buffer.getChannelData(0);
    219                  let out0 = buffer.getChannelData(1);
    220                  let out1 = buffer.getChannelData(2);
    221 
    222                  // The convolver is basically a one-sample delay.  Verify that
    223                  // that each channel is delayed by one sample.
    224                  should(out0.slice(1), '1->2 explicit upmix: channel 0')
    225                      .beCloseToArray(
    226                          input.slice(0, length - 1),
    227                          {absoluteThreshold: absoluteThreshold});
    228                  should(out1.slice(1), '1->2 explicit upmix: channel 1')
    229                      .beCloseToArray(
    230                          input.slice(0, length - 1),
    231                          {absoluteThreshold: absoluteThreshold});
    232                })
    233                .then(() => task.done());
    234          });
    235 
    236      audit.define(
    237          {
    238            label: 'mono-upmix-clamped-max',
    239            description: '1->2 upmix, count mode clamped-max'
    240          },
    241          (task, should) => {
    242            upMixTest(should, {channelCountMode: 'clamped-max'})
    243                .then(buffer => {
    244                  let length = buffer.length;
    245                  let input = buffer.getChannelData(0);
    246                  let out0 = buffer.getChannelData(1);
    247                  let out1 = buffer.getChannelData(2);
    248 
    249                  // The convolver is basically a one-sample delay.  With a mono
    250                  // input, the count set to 2, and a mode of 'clamped-max', the
    251                  // output should be mono
    252                  should(out0.slice(1), '1->2 clamped-max upmix: channel 0')
    253                      .beCloseToArray(
    254                          input.slice(0, length - 1),
    255                          {absoluteThreshold: absoluteThreshold});
    256                  should(out1, '1->2 clamped-max upmix: channel 1')
    257                      .beConstantValueOf(0);
    258                })
    259                .then(() => task.done());
    260          });
    261 
    262      function downMixTest(options, should) {
    263        // Create an 4-channel offline context.  The first two channels are for
    264        // the stereo output of the convolver and the next two channels are for
    265        // the reference stereo signal.
    266        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
    267        context.destination.channelInterpretation = 'discrete';
    268 
    269        // Create oscillators for use as the input.  The type and frequency is
    270        // arbitrary except that oscillators must be different.
    271        let src = new Array(options.numberOfInputs);
    272        for (let k = 0; k < src.length; ++k) {
    273          src[k] = new OscillatorNode(
    274              context, {type: 'square', frequency: 440 + 220 * k});
    275        }
    276 
    277        // Merger to combine the oscillators into one output stream.
    278        let srcMerger =
    279            new ChannelMergerNode(context, {numberOfInputs: src.length});
    280 
    281        for (let k = 0; k < src.length; ++k) {
    282          src[k].connect(srcMerger, 0, k);
    283        }
    284 
    285        // Convolver under test.
    286        let conv = new ConvolverNode(context, {
    287          disableNormalization: true,
    288          buffer: response,
    289          channelCountMode: options.channelCountMode
    290        });
    291        srcMerger.connect(conv);
    292 
    293        // Splitter to get individual channels of the convolver output so we can
    294        // feed them (eventually) to the context in the right set of channels.
    295        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    296        conv.connect(splitter);
    297 
    298        // Reference graph consists of a delay node to simulate the response of
    299        // the convolver.  (The convolver response is designed this way.)
    300        let delay = new DelayNode(context, {delayTime: 1 / context.sampleRate});
    301 
    302        // Gain node to mix the sources to stereo in the desired way.  (Could be
    303        // done in the delay node, but let's keep the mixing separated from the
    304        // functionality.)
    305        let gainMixer = new GainNode(
    306            context, {channelCount: 2, channelCountMode: 'explicit'});
    307        srcMerger.connect(gainMixer);
    308 
    309        // Splitter to extract the channels of the reference signal.
    310        let refSplitter =
    311            new ChannelSplitterNode(context, {numberOfOutputs: 2});
    312        gainMixer.connect(delay).connect(refSplitter);
    313 
    314        // Final merger to bring back the individual channels from the convolver
    315        // and the reference in the right order for the destination.
    316        let finalMerger = new ChannelMergerNode(
    317            context, {numberOfInputs: context.destination.channelCount});
    318 
    319        // First two channels are for the convolver output, and the next two are
    320        // for the reference.
    321        splitter.connect(finalMerger, 0, 0);
    322        splitter.connect(finalMerger, 1, 1);
    323        refSplitter.connect(finalMerger, 0, 2);
    324        refSplitter.connect(finalMerger, 1, 3);
    325 
    326        finalMerger.connect(context.destination);
    327 
    328        // Start the sources at last.
    329        for (let k = 0; k < src.length; ++k) {
    330          src[k].start();
    331        }
    332 
    333        return context.startRendering().then(audioBuffer => {
    334          // Extract the various channels out
    335          let actual0 = audioBuffer.getChannelData(0);
    336          let actual1 = audioBuffer.getChannelData(1);
    337          let expected0 = audioBuffer.getChannelData(2);
    338          let expected1 = audioBuffer.getChannelData(3);
    339 
    340          let threshold = options.absoluteThreshold ?
    341              options.absoluteThreshold : absoluteThreshold;
    342 
    343          // Verify that each output channel of the convolver matches
    344          // the delayed signal from the reference
    345          should(actual0, options.prefix + ': Channel 0')
    346              .beCloseToArray(expected0, {absoluteThreshold: threshold});
    347          should(actual1, options.prefix + ': Channel 1')
    348              .beCloseToArray(expected1, {absoluteThreshold: threshold});
    349        });
    350      }
    351 
    352      function upMixTest(should, options) {
    353        // Offline context with 3 channels:  0 = source
    354        // 1 = convolver output, left, 2 = convolver output, right.  Context
    355        // destination must be discrete so that channels don't get mixed in
    356        // unexpected ways.
    357        let context = new OfflineAudioContext(3, renderFrames, sampleRate);
    358        context.destination.channelInterpretation = 'discrete';
    359 
    360        let merger = new ChannelMergerNode(
    361            context, {numberOfInputs: context.destination.maxChannelCount});
    362        merger.connect(context.destination);
    363 
    364        let src = new OscillatorNode(context);
    365 
    366        // Mono response for convolver.  Just a simple 1-frame delay.
    367        let response =
    368            new AudioBuffer({length: 2, sampleRate: context.sampleRate});
    369        response.getChannelData(0)[1] = 1;
    370 
    371        // Set mode to explicit and count to 2 so we manually force the
    372        // convolver to produce stereo output.  Without this, it would be
    373        // mono input with mono response, which produces a mono output.
    374        let conv;
    375 
    376        should(
    377            () => {conv = new ConvolverNode(context, {
    378                     buffer: response,
    379                     disableNormalization: true,
    380                     channelCount: 2,
    381                     channelCountMode: options.channelCountMode
    382                   })},
    383            `new ConvolverNode({channelCountMode: '${
    384                options.channelCountMode}'})`)
    385            .notThrow();
    386 
    387        // Split output of convolver into individual channels.
    388        let convSplit = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    389 
    390        src.connect(conv);
    391        conv.connect(convSplit);
    392 
    393        // Connect signals to destination in the desired way.
    394        src.connect(merger, 0, 0);
    395        convSplit.connect(merger, 0, 1);
    396        convSplit.connect(merger, 1, 2);
    397 
    398        src.start();
    399 
    400        return context.startRendering();
    401      }
    402 
    403      audit.run();
    404    </script>
    405  </body>
    406 </html>