tor-browser

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

convolver-response-2-chan.html (14059B)


      1 <!DOCTYPE html>
      2 <html>
      3  <head>
      4    <title>
      5      Test Convolver Channel Outputs for Response with 2 channels
      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      // a stereo response.
     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: 2, length: 4, sampleRate: sampleRate});
     47                  // Each channel of the response is a simple impulse (with
     48                  // different delay) so that we can use a DelayNode to simulate
     49                  // the convolver output.  Channel k is delayed by k+1 frames.
     50                  for (let k = 0; k < response.numberOfChannels; ++k) {
     51                    response.getChannelData(k)[k + 1] = 1;
     52                  }
     53                },
     54                'new AudioBuffer({numberOfChannels: 2, length: 4, sampleRate: ' +
     55                    sampleRate + '})')
     56                .notThrow();
     57 
     58            task.done();
     59          });
     60 
     61      audit.define(
     62          {label: '1-channel input', description: 'produces 2-channel output'},
     63          (task, should) => {
     64            stereoResponseTest({numberOfInputs: 1, prefix: '1'}, should)
     65                .then(() => task.done());
     66          });
     67 
     68      audit.define(
     69          {label: '2-channel input', description: 'produces 2-channel output'},
     70          (task, should) => {
     71            stereoResponseTest({numberOfInputes: 2, prefix: '2'}, should)
     72                .then(() => task.done());
     73          });
     74 
     75      audit.define(
     76          {
     77            label: '3-channel input',
     78            description: '3->2 downmix producing 2-channel output'
     79          },
     80          (task, should) => {
     81            stereoResponseTest({numberOfInputs: 3, prefix: '3'}, should)
     82                .then(() => task.done());
     83          });
     84 
     85      audit.define(
     86          {
     87            label: '4-channel input',
     88            description: '4->2 downmix producing 2-channel output'
     89          },
     90          (task, should) => {
     91            stereoResponseTest({numberOfInputs: 4, prefix: '4'}, should)
     92                .then(() => task.done());
     93          });
     94 
     95      audit.define(
     96          {
     97            label: '5.1-channel input',
     98            description: '5.1->2 downmix producing 2-channel output'
     99          },
    100          (task, should) => {
    101            // Scale tolerance by maximum amplitude expected in down-mix
    102            // output.
    103            let threshold = (1.0 + Math.sqrt(0.5) * 2) * absoluteThreshold;
    104 
    105            stereoResponseTest({numberOfInputs: 6, prefix: '5.1',
    106                                absoluteThreshold: threshold}, should)
    107                .then(() => task.done());
    108          });
    109 
    110      audit.define(
    111          {
    112            label: '2-channel input, explicit mode',
    113            description: 'produces 2-channel output'
    114          },
    115          (task, should) => {
    116            stereoResponseExplicitTest(
    117                {
    118                  numberOfInputes: 2,
    119                  prefix: '2-in explicit mode'
    120                },
    121                should)
    122                .then(() => task.done());
    123          });
    124 
    125      audit.define(
    126          {
    127            label: '3-channel input explicit mode',
    128            description: '3->1 downmix producing 2-channel output'
    129          },
    130          (task, should) => {
    131            stereoResponseExplicitTest(
    132                {
    133                  numberOfInputs: 3,
    134                  prefix: '3-in explicit'
    135                },
    136                should)
    137                .then(() => task.done());
    138          });
    139 
    140      audit.define(
    141          {
    142            label: '4-channel input explicit mode',
    143            description: '4->1 downmix producing 2-channel output'
    144          },
    145          (task, should) => {
    146            stereoResponseExplicitTest(
    147                {
    148                  numberOfInputs: 4,
    149                  prefix: '4-in explicit'
    150                },
    151                should)
    152                .then(() => task.done());
    153          });
    154 
    155      audit.define(
    156          {
    157            label: '5.1-channel input explicit mode',
    158            description: '5.1->1 downmix producing 2-channel output'
    159          },
    160          (task, should) => {
    161            // Scale tolerance by maximum amplitude expected in down-mix
    162            // output.
    163            let threshold = (Math.sqrt(0.5) * 2 + 2.0) * absoluteThreshold;
    164 
    165            stereoResponseExplicitTest(
    166                {
    167                  numberOfInputs: 6,
    168                  prefix: '5.1-in explicit',
    169                  absoluteThreshold: threshold
    170                },
    171                should)
    172                .then(() => task.done());
    173          });
    174 
    175      function stereoResponseTest(options, should) {
    176        // Create an 4-channel offline context.  The first two channels are for
    177        // the stereo output of the convolver and the next two channels are for
    178        // the reference stereo signal.
    179        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
    180        context.destination.channelInterpretation = 'discrete';
    181 
    182        // Create oscillators for use as the input.  The type and frequency is
    183        // arbitrary except that oscillators must be different.
    184        let src = new Array(options.numberOfInputs);
    185        for (let k = 0; k < src.length; ++k) {
    186          src[k] = new OscillatorNode(
    187              context, {type: 'square', frequency: 440 + 220 * k});
    188        }
    189 
    190        // Merger to combine the oscillators into one output stream.
    191        let srcMerger =
    192            new ChannelMergerNode(context, {numberOfInputs: src.length});
    193 
    194        for (let k = 0; k < src.length; ++k) {
    195          src[k].connect(srcMerger, 0, k);
    196        }
    197 
    198        // Convolver under test.
    199        let conv = new ConvolverNode(
    200            context, {disableNormalization: true, buffer: response});
    201        srcMerger.connect(conv);
    202 
    203        // Splitter to get individual channels of the convolver output so we can
    204        // feed them (eventually) to the context in the right set of channels.
    205        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    206        conv.connect(splitter);
    207 
    208        // Reference graph consists of a delays node to simulate the response of
    209        // the convolver.  (The convolver response is designed this way.)
    210        let delay = new Array(2);
    211        for (let k = 0; k < delay.length; ++k) {
    212          delay[k] = new DelayNode(context, {
    213            delayTime: (k + 1) / context.sampleRate,
    214            channelCount: 1,
    215            channelCountMode: 'explicit'
    216          });
    217        }
    218 
    219        // Gain node to mix the sources to stereo in the desired way.  (Could be
    220        // done in the delay node, but let's keep the mixing separated from the
    221        // functionality.)
    222        let gainMixer = new GainNode(
    223            context, {channelCount: 2, channelCountMode: 'explicit'});
    224        srcMerger.connect(gainMixer);
    225 
    226        // Splitter to extract the channels of the reference signal.
    227        let refSplitter =
    228            new ChannelSplitterNode(context, {numberOfOutputs: 2});
    229        gainMixer.connect(refSplitter);
    230 
    231        // Connect each channel to the delay nodes
    232        for (let k = 0; k < delay.length; ++k) {
    233          refSplitter.connect(delay[k], k);
    234        }
    235 
    236        // Final merger to bring back the individual channels from the convolver
    237        // and the reference in the right order for the destination.
    238        let finalMerger = new ChannelMergerNode(
    239            context, {numberOfInputs: context.destination.channelCount});
    240 
    241        // First two channels are for the convolver output, and the next two are
    242        // for the reference.
    243        splitter.connect(finalMerger, 0, 0);
    244        splitter.connect(finalMerger, 1, 1);
    245        delay[0].connect(finalMerger, 0, 2);
    246        delay[1].connect(finalMerger, 0, 3);
    247 
    248        finalMerger.connect(context.destination);
    249 
    250        // Start the sources at last.
    251        for (let k = 0; k < src.length; ++k) {
    252          src[k].start();
    253        }
    254 
    255        return context.startRendering().then(audioBuffer => {
    256          // Extract the various channels out
    257          let actual0 = audioBuffer.getChannelData(0);
    258          let actual1 = audioBuffer.getChannelData(1);
    259          let expected0 = audioBuffer.getChannelData(2);
    260          let expected1 = audioBuffer.getChannelData(3);
    261 
    262          let threshold = options.absoluteThreshold ?
    263              options.absoluteThreshold : absoluteThreshold;
    264 
    265          // Verify that each output channel of the convolver matches
    266          // the delayed signal from the reference
    267          should(actual0, options.prefix + ': Channel 0')
    268              .beCloseToArray(expected0, {absoluteThreshold: threshold});
    269          should(actual1, options.prefix + ': Channel 1')
    270              .beCloseToArray(expected1, {absoluteThreshold: threshold});
    271        });
    272      }
    273 
    274      function stereoResponseExplicitTest(options, should) {
    275        // Create an 4-channel offline context.  The first two channels are for
    276        // the stereo output of the convolver and the next two channels are for
    277        // the reference stereo signal.
    278        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
    279        context.destination.channelInterpretation = 'discrete';
    280 
    281        // Create oscillators for use as the input.  The type and frequency is
    282        // arbitrary except that oscillators must be different.
    283        let src = new Array(options.numberOfInputs);
    284        for (let k = 0; k < src.length; ++k) {
    285          src[k] = new OscillatorNode(
    286              context, {type: 'square', frequency: 440 + 220 * k});
    287        }
    288 
    289        // Merger to combine the oscillators into one output stream.
    290        let srcMerger =
    291            new ChannelMergerNode(context, {numberOfInputs: src.length});
    292 
    293        for (let k = 0; k < src.length; ++k) {
    294          src[k].connect(srcMerger, 0, k);
    295        }
    296 
    297        // Convolver under test.
    298        let conv = new ConvolverNode(context, {
    299          channelCount: 1,
    300          channelCountMode: 'explicit',
    301          disableNormalization: true,
    302          buffer: response
    303        });
    304        srcMerger.connect(conv);
    305 
    306        // Splitter to get individual channels of the convolver output so we can
    307        // feed them (eventually) to the context in the right set of channels.
    308        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    309        conv.connect(splitter);
    310 
    311        // Reference graph consists of a delays node to simulate the response of
    312        // the convolver.  (The convolver response is designed this way.)
    313        let delay = new Array(2);
    314        for (let k = 0; k < delay.length; ++k) {
    315          delay[k] = new DelayNode(context, {
    316            delayTime: (k + 1) / context.sampleRate,
    317            channelCount: 1,
    318            channelCountMode: 'explicit'
    319          });
    320        }
    321 
    322        // Gain node to mix the sources in the same way as the convolver.
    323        let gainMixer = new GainNode(
    324            context, {channelCount: 1, channelCountMode: 'explicit'});
    325        srcMerger.connect(gainMixer);
    326 
    327        // Connect each channel to the delay nodes
    328        for (let k = 0; k < delay.length; ++k) {
    329          gainMixer.connect(delay[k]);
    330        }
    331 
    332        // Final merger to bring back the individual channels from the convolver
    333        // and the reference in the right order for the destination.
    334        let finalMerger = new ChannelMergerNode(
    335            context, {numberOfInputs: context.destination.channelCount});
    336 
    337        // First two channels are for the convolver output, and the next two are
    338        // for the reference.
    339        splitter.connect(finalMerger, 0, 0);
    340        splitter.connect(finalMerger, 1, 1);
    341        delay[0].connect(finalMerger, 0, 2);
    342        delay[1].connect(finalMerger, 0, 3);
    343 
    344        finalMerger.connect(context.destination);
    345 
    346        // Start the sources at last.
    347        for (let k = 0; k < src.length; ++k) {
    348          src[k].start();
    349        }
    350 
    351        return context.startRendering().then(audioBuffer => {
    352          // Extract the various channels out
    353          let actual0 = audioBuffer.getChannelData(0);
    354          let actual1 = audioBuffer.getChannelData(1);
    355          let expected0 = audioBuffer.getChannelData(2);
    356          let expected1 = audioBuffer.getChannelData(3);
    357 
    358          let threshold = options.absoluteThreshold ?
    359              options.absoluteThreshold : absoluteThreshold;
    360 
    361          // Verify that each output channel of the convolver matches
    362          // the delayed signal from the reference
    363          should(actual0, options.prefix + ': Channel 0')
    364              .beCloseToArray(expected0, {absoluteThreshold: threshold});
    365          should(actual1, options.prefix + ': Channel 1')
    366              .beCloseToArray(expected1, {absoluteThreshold: threshold});
    367        });
    368      }
    369 
    370      audit.run();
    371    </script>
    372  </body>
    373 </html>