tor-browser

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

convolver-response-4-chan.html (19753B)


      1 <!DOCTYPE html>
      2 <html>
      3  <head>
      4    <title>
      5      Test Convolver Channel Outputs for Response with 4 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 four channels.
     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 = 3 * Math.pow(2, -22);
     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: 4, length: 8, 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            fourChannelResponseTest({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            fourChannelResponseTest({numberOfInputs: 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            fourChannelResponseTest({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            fourChannelResponseTest({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            fourChannelResponseTest({numberOfInputs: 6, prefix: '5.1',
    106                                     absoluteThreshold: threshold}, should)
    107                .then(() => task.done());
    108          });
    109 
    110      audit.define(
    111          {
    112            label: 'delayed buffer set',
    113            description: 'Delayed set of 4-channel response'
    114          },
    115          (task, should) => {
    116            // Don't really care about the output for this test.  It's to verify
    117            // we don't crash in a debug build when setting the convolver buffer
    118            // after creating the graph.
    119            let context = new OfflineAudioContext(1, renderFrames, sampleRate);
    120            let src = new OscillatorNode(context);
    121            let convolver =
    122                new ConvolverNode(context, {disableNormalization: true});
    123            let buffer = new AudioBuffer({
    124              numberOfChannels: 4,
    125              length: 4,
    126              sampleRate: context.sampleRate
    127            });
    128 
    129            // Impulse responses for the convolver aren't important, as long as
    130            // it's not all zeroes.
    131            for (let k = 0; k < buffer.numberOfChannels; ++k) {
    132              buffer.getChannelData(k).fill(1);
    133            }
    134 
    135            src.connect(convolver).connect(context.destination);
    136 
    137            // Set the buffer after a few render quanta have passed.  The actual
    138            // value must be least one, but is otherwise arbitrary.
    139            context.suspend(512 / context.sampleRate)
    140                .then(() => convolver.buffer = buffer)
    141                .then(() => context.resume());
    142 
    143            src.start();
    144            context.startRendering()
    145                .then(audioBuffer => {
    146                  // Just make sure output is not silent.
    147                  should(
    148                      audioBuffer.getChannelData(0),
    149                      'Output with delayed setting of convolver buffer')
    150                      .notBeConstantValueOf(0);
    151                })
    152                .then(() => task.done());
    153          });
    154 
    155      audit.define(
    156          {
    157            label: 'count 1, 2-channel in',
    158            description: '2->1 downmix because channel count is 1'
    159          },
    160          (task, should) => {
    161            channelCount1ExplicitTest(
    162                {numberOfInputs: 1, prefix: 'Convolver count 1, stereo in'},
    163                should)
    164                .then(() => task.done());
    165          });
    166 
    167      audit.define(
    168          {
    169            label: 'count 1, 4-channel in',
    170            description: '4->1 downmix because channel count is 1'
    171          },
    172          (task, should) => {
    173            channelCount1ExplicitTest(
    174                {numberOfInputs: 4, prefix: 'Convolver count 1, 4-channel in'},
    175                should)
    176                .then(() => task.done());
    177          });
    178 
    179      audit.define(
    180          {
    181            label: 'count 1, 5.1-channel in',
    182            description: '5.1->1 downmix because channel count is 1'
    183          },
    184          (task, should) => {
    185            channelCount1ExplicitTest(
    186                {
    187                  numberOfInputs: 6,
    188                  prefix: 'Convolver count 1, 5.1 channel in'
    189                },
    190                should)
    191                .then(() => task.done());
    192          });
    193 
    194      audit.run();
    195 
    196      function fourChannelResponseTest(options, should) {
    197        // Create an 4-channel offline context.  The first two channels are for
    198        // the stereo output of the convolver and the next two channels are for
    199        // the reference stereo signal.
    200        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
    201        context.destination.channelInterpretation = 'discrete';
    202 
    203        // Create oscillators for use as the input.  The type and frequency is
    204        // arbitrary except that oscillators must be different.
    205        let src = new Array(options.numberOfInputs);
    206        for (let k = 0; k < src.length; ++k) {
    207          src[k] = new OscillatorNode(
    208              context, {type: 'square', frequency: 440 + 220 * k});
    209        }
    210 
    211        // Merger to combine the oscillators into one output stream.
    212        let srcMerger =
    213            new ChannelMergerNode(context, {numberOfInputs: src.length});
    214 
    215        for (let k = 0; k < src.length; ++k) {
    216          src[k].connect(srcMerger, 0, k);
    217        }
    218 
    219        // Convolver under test.
    220        let conv = new ConvolverNode(
    221            context, {disableNormalization: true, buffer: response});
    222        srcMerger.connect(conv);
    223 
    224        // Splitter to get individual channels of the convolver output so we can
    225        // feed them (eventually) to the context in the right set of channels.
    226        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    227        conv.connect(splitter);
    228 
    229        // Reference graph consists of a delays node to simulate the response of
    230        // the convolver.  (The convolver response is designed this way.)
    231        let delay = new Array(4);
    232        for (let k = 0; k < delay.length; ++k) {
    233          delay[k] = new DelayNode(context, {
    234            delayTime: (k + 1) / context.sampleRate,
    235            channelCount: 1,
    236            channelCountMode: 'explicit'
    237          });
    238        }
    239 
    240        // Gain node to mix the sources to stereo in the desired way.  (Could be
    241        // done in the delay node, but let's keep the mixing separated from the
    242        // functionality.)
    243        let gainMixer = new GainNode(
    244            context, {channelCount: 2, channelCountMode: 'explicit'});
    245        srcMerger.connect(gainMixer);
    246 
    247        // Splitter to extract the channels of the reference signal.
    248        let refSplitter =
    249            new ChannelSplitterNode(context, {numberOfOutputs: 2});
    250        gainMixer.connect(refSplitter);
    251 
    252        // Connect the left channel to the first two nodes and the right channel
    253        // to the second two as required for "true" stereo matrix response.
    254        for (let k = 0; k < 2; ++k) {
    255          refSplitter.connect(delay[k], 0, 0);
    256          refSplitter.connect(delay[k + 2], 1, 0);
    257        }
    258 
    259        // Gain nodes to sum the responses to stereo
    260        let gain = new Array(2);
    261        for (let k = 0; k < gain.length; ++k) {
    262          gain[k] = new GainNode(context, {
    263            channelCount: 1,
    264            channelCountMode: 'explicit',
    265            channelInterpretation: 'discrete'
    266          });
    267        }
    268 
    269        delay[0].connect(gain[0]);
    270        delay[2].connect(gain[0]);
    271        delay[1].connect(gain[1]);
    272        delay[3].connect(gain[1]);
    273 
    274        // Final merger to bring back the individual channels from the convolver
    275        // and the reference in the right order for the destination.
    276        let finalMerger = new ChannelMergerNode(
    277            context, {numberOfInputs: context.destination.channelCount});
    278 
    279        // First two channels are for the convolver output, and the next two are
    280        // for the reference.
    281        splitter.connect(finalMerger, 0, 0);
    282        splitter.connect(finalMerger, 1, 1);
    283        gain[0].connect(finalMerger, 0, 2);
    284        gain[1].connect(finalMerger, 0, 3);
    285 
    286        finalMerger.connect(context.destination);
    287 
    288        // Start the sources at last.
    289        for (let k = 0; k < src.length; ++k) {
    290          src[k].start();
    291        }
    292 
    293        return context.startRendering().then(audioBuffer => {
    294          // Extract the various channels out
    295          let actual0 = audioBuffer.getChannelData(0);
    296          let actual1 = audioBuffer.getChannelData(1);
    297          let expected0 = audioBuffer.getChannelData(2);
    298          let expected1 = audioBuffer.getChannelData(3);
    299 
    300          let threshold = options.absoluteThreshold ?
    301              options.absoluteThreshold : absoluteThreshold;
    302 
    303          // Verify that each output channel of the convolver matches
    304          // the delayed signal from the reference
    305          should(actual0, options.prefix + ': Channel 0')
    306              .beCloseToArray(expected0, {absoluteThreshold: threshold});
    307          should(actual1, options.prefix + ': Channel 1')
    308              .beCloseToArray(expected1, {absoluteThreshold: threshold});
    309        });
    310      }
    311 
    312      function fourChannelResponseExplicitTest(options, should) {
    313        // Create an 4-channel offline context.  The first two channels are for
    314        // the stereo output of the convolver and the next two channels are for
    315        // the reference stereo signal.
    316        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
    317        context.destination.channelInterpretation = 'discrete';
    318 
    319        // Create oscillators for use as the input.  The type and frequency is
    320        // arbitrary except that oscillators must be different.
    321        let src = new Array(options.numberOfInputs);
    322        for (let k = 0; k < src.length; ++k) {
    323          src[k] = new OscillatorNode(
    324              context, {type: 'square', frequency: 440 + 220 * k});
    325        }
    326 
    327        // Merger to combine the oscillators into one output stream.
    328        let srcMerger =
    329            new ChannelMergerNode(context, {numberOfInputs: src.length});
    330 
    331        for (let k = 0; k < src.length; ++k) {
    332          src[k].connect(srcMerger, 0, k);
    333        }
    334 
    335        // Convolver under test.
    336        let conv = new ConvolverNode(
    337            context, {disableNormalization: true, buffer: response});
    338        srcMerger.connect(conv);
    339 
    340        // Splitter to get individual channels of the convolver output so we can
    341        // feed them (eventually) to the context in the right set of channels.
    342        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    343        conv.connect(splitter);
    344 
    345        // Reference graph consists of a delays node to simulate the response of
    346        // the convolver.  (The convolver response is designed this way.)
    347        let delay = new Array(4);
    348        for (let k = 0; k < delay.length; ++k) {
    349          delay[k] = new DelayNode(context, {
    350            delayTime: (k + 1) / context.sampleRate,
    351            channelCount: 1,
    352            channelCountMode: 'explicit'
    353          });
    354        }
    355 
    356        // Gain node to mix the sources to stereo in the desired way.  (Could be
    357        // done in the delay node, but let's keep the mixing separated from the
    358        // functionality.)
    359        let gainMixer = new GainNode(
    360            context, {channelCount: 2, channelCountMode: 'explicit'});
    361        srcMerger.connect(gainMixer);
    362 
    363        // Splitter to extract the channels of the reference signal.
    364        let refSplitter =
    365            new ChannelSplitterNode(context, {numberOfOutputs: 2});
    366        gainMixer.connect(refSplitter);
    367 
    368        // Connect the left channel to the first two nodes and the right channel
    369        // to the second two as required for "true" stereo matrix response.
    370        for (let k = 0; k < 2; ++k) {
    371          refSplitter.connect(delay[k], 0, 0);
    372          refSplitter.connect(delay[k + 2], 1, 0);
    373        }
    374 
    375        // Gain nodes to sum the responses to stereo
    376        let gain = new Array(2);
    377        for (let k = 0; k < gain.length; ++k) {
    378          gain[k] = new GainNode(context, {
    379            channelCount: 1,
    380            channelCountMode: 'explicit',
    381            channelInterpretation: 'discrete'
    382          });
    383        }
    384 
    385        delay[0].connect(gain[0]);
    386        delay[2].connect(gain[0]);
    387        delay[1].connect(gain[1]);
    388        delay[3].connect(gain[1]);
    389 
    390        // Final merger to bring back the individual channels from the convolver
    391        // and the reference in the right order for the destination.
    392        let finalMerger = new ChannelMergerNode(
    393            context, {numberOfInputs: context.destination.channelCount});
    394 
    395        // First two channels are for the convolver output, and the next two are
    396        // for the reference.
    397        splitter.connect(finalMerger, 0, 0);
    398        splitter.connect(finalMerger, 1, 1);
    399        gain[0].connect(finalMerger, 0, 2);
    400        gain[1].connect(finalMerger, 0, 3);
    401 
    402        finalMerger.connect(context.destination);
    403 
    404        // Start the sources at last.
    405        for (let k = 0; k < src.length; ++k) {
    406          src[k].start();
    407        }
    408 
    409        return context.startRendering().then(audioBuffer => {
    410          // Extract the various channels out
    411          let actual0 = audioBuffer.getChannelData(0);
    412          let actual1 = audioBuffer.getChannelData(1);
    413          let expected0 = audioBuffer.getChannelData(2);
    414          let expected1 = audioBuffer.getChannelData(3);
    415 
    416          // Verify that each output channel of the convolver matches
    417          // the delayed signal from the reference
    418          should(actual0, options.prefix + ': Channel 0')
    419              .beEqualToArray(expected0);
    420          should(actual1, options.prefix + ': Channel 1')
    421              .beEqualToArray(expected1);
    422        });
    423      }
    424 
    425      function channelCount1ExplicitTest(options, should) {
    426        // Create an 4-channel offline context.  The first two channels are
    427        // for the stereo output of the convolver and the next two channels
    428        // are for the reference stereo signal.
    429        let context = new OfflineAudioContext(4, renderFrames, sampleRate);
    430        context.destination.channelInterpretation = 'discrete';
    431        // Final merger to bring back the individual channels from the
    432        // convolver and the reference in the right order for the
    433        // destination.
    434        let finalMerger = new ChannelMergerNode(
    435            context, {numberOfInputs: context.destination.channelCount});
    436        finalMerger.connect(context.destination);
    437 
    438        // Create source using oscillators
    439        let src = new Array(options.numberOfInputs);
    440        for (let k = 0; k < src.length; ++k) {
    441          src[k] = new OscillatorNode(
    442              context, {type: 'square', frequency: 440 + 220 * k});
    443        }
    444 
    445        // Merger to combine the oscillators into one output stream.
    446        let srcMerger =
    447            new ChannelMergerNode(context, {numberOfInputs: src.length});
    448        for (let k = 0; k < src.length; ++k) {
    449          src[k].connect(srcMerger, 0, k);
    450        }
    451 
    452        // Convolver under test
    453        let conv = new ConvolverNode(context, {
    454          channelCount: 1,
    455          channelCountMode: 'explicit',
    456          disableNormalization: true,
    457          buffer: response
    458        });
    459        srcMerger.connect(conv);
    460 
    461        // Splitter to extract the channels of the test signal.
    462        let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
    463        conv.connect(splitter);
    464 
    465        // Reference convolver, with a gain node to do the desired mixing.  The
    466        // gain node should do the same thing that the convolver under test
    467        // should do.
    468        let gain = new GainNode(
    469            context, {channelCount: 1, channelCountMode: 'explicit'});
    470        let convRef = new ConvolverNode(
    471            context, {disableNormalization: true, buffer: response});
    472 
    473        srcMerger.connect(gain).connect(convRef);
    474 
    475        // Splitter to extract the channels of the reference signal.
    476        let refSplitter =
    477            new ChannelSplitterNode(context, {numberOfOutputs: 2});
    478 
    479        convRef.connect(refSplitter);
    480 
    481        // Merge all the channels into one
    482        splitter.connect(finalMerger, 0, 0);
    483        splitter.connect(finalMerger, 1, 1);
    484        refSplitter.connect(finalMerger, 0, 2);
    485        refSplitter.connect(finalMerger, 1, 3);
    486 
    487        // Start sources and render!
    488        for (let k = 0; k < src.length; ++k) {
    489          src[k].start();
    490        }
    491 
    492        return context.startRendering().then(buffer => {
    493          // The output from the test convolver should be identical to
    494          // the reference result.
    495          let testOut0 = buffer.getChannelData(0);
    496          let testOut1 = buffer.getChannelData(1);
    497          let refOut0 = buffer.getChannelData(2);
    498          let refOut1 = buffer.getChannelData(3);
    499 
    500          should(testOut0, `${options.prefix}: output 0`)
    501              .beEqualToArray(refOut0);
    502          should(testOut1, `${options.prefix}: output 1`)
    503              .beEqualToArray(refOut1);
    504        })
    505      }
    506    </script>
    507  </body>
    508 </html>