tor-browser

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

k-rate-panner.html (8539B)


      1 <!doctype html>
      2 <html>
      3  <head>
      4    <title>Test k-rate AudioParams of PannerNode</title>
      5    <script src="/resources/testharness.js"></script>
      6    <script src="/resources/testharnessreport.js"></script>
      7  </head>
      8  <body>
      9    <script>
     10 
     11      function assert_not_constant(arr, description) {
     12        const first = arr[0];
     13        for (let i = 1; i < arr.length; ++i) {
     14          if (Math.abs(arr[i] - first) > Number.EPSILON) {
     15            // If any element differs from the first by more than a negligible
     16            // amount, the array is not constant, and the assertion passes.
     17            return;
     18          }
     19        }
     20        assert_unreached(`${description}: unexpectedly constant`);
     21      }
     22 
     23      function assert_all_close(arr, value, description) {
     24        for (const x of arr) {
     25          assert_approx_equals(x, value, Number.EPSILON, description);
     26        }
     27      }
     28 
     29      function assert_all_constant(arr, value, description) {
     30        for (const x of arr) {
     31          assert_equals(x, value, description);
     32        }
     33      }
     34 
     35 
     36      // Represents the 'k-rate' AudioParam automation rate.
     37      const K_RATE = 'k-rate';
     38      // Defines the size of one audio processing block (render quantum)
     39      // in frames.
     40      const BLOCK = 128;
     41      // Arbitrary sample rate and duration.
     42      const SAMPLE_RATE = 8000;
     43 
     44      // Define a test where we verify that a k-rate audio param produces
     45      // different results from an a-rate audio param for each of the audio
     46      // params of a biquad.
     47      //
     48      // Each entry gives the name of the AudioParam, an initial value to be
     49      // used with setValueAtTime, and a final value to be used with
     50      // linearRampToValueAtTime. (See |doTest| for details as well.)
     51 
     52      const pannerParams = [
     53        {name: 'positionX', initial: 0, final: 1000},
     54        {name: 'positionY', initial: 0, final: 1000},
     55        {name: 'orientationX', initial: 1, final: 10},
     56        {name: 'orientationY', initial: 1, final: 10},
     57        {name: 'orientationZ', initial: 1, final: 10},
     58      ];
     59 
     60      pannerParams.forEach(param => {
     61        promise_test(async t => {
     62          const testDuration = (5 * BLOCK) / SAMPLE_RATE;
     63          const context = new OfflineAudioContext({
     64            numberOfChannels: 3,
     65            sampleRate: SAMPLE_RATE,
     66            length: testDuration * SAMPLE_RATE,
     67          });
     68 
     69          const merger = new ChannelMergerNode(context, {numberOfInputs: 3});
     70          merger.connect(context.destination);
     71          // Graph: ConstantSource → Panner → destination
     72          const source = new ConstantSourceNode(context);
     73          const commonOpts = {
     74            distanceModel: 'inverse',
     75            coneOuterAngle: 360,
     76            coneInnerAngle: 0,
     77            positionX: 1,
     78            positionY: 1,
     79            positionZ: 1,
     80            orientationX: 0,
     81            orientationY: 1,
     82            orientationZ: 1,
     83          };
     84 
     85          const kRatePanner = new PannerNode(context, commonOpts);
     86          const aRatePanner = new PannerNode(context, commonOpts);
     87 
     88          // Switch only the k‑rate node’s target param to k‑rate
     89          // automation
     90          const kRateParam = kRatePanner[param.name];
     91          kRateParam.automationRate = K_RATE;
     92          assert_equals(kRateParam.automationRate, K_RATE,
     93              `${param.name}.automationRate should be k‑rate`);
     94 
     95          // Identical automation on both nodes
     96          [kRatePanner, aRatePanner].forEach(panner => {
     97            panner[param.name].setValueAtTime(param.initial, 0);
     98            panner[param.name].linearRampToValueAtTime(param.final,
     99                testDuration);
    100          });
    101 
    102          // Build routing: source → both panners
    103          source.connect(kRatePanner);
    104          source.connect(aRatePanner);
    105 
    106          // k‑rate result → channel‑0; a‑rate → channel‑1
    107          kRatePanner.connect(merger, 0, 0);
    108          aRatePanner.connect(merger, 0, 1);
    109 
    110          // Difference channel: k‑rate – a‑rate
    111          const inverter = new GainNode(context, {gain: -1});
    112          kRatePanner.connect(merger, 0, 2);
    113          aRatePanner.connect(inverter).connect(merger, 0, 2);
    114 
    115          source.start();
    116 
    117          const buffer = await context.startRendering();
    118 
    119          const kData = buffer.getChannelData(0);
    120          const aData = buffer.getChannelData(1);
    121          const diff = buffer.getChannelData(2);
    122 
    123          // The difference signal must NOT be constant zero.
    124          assert_not_constant(diff, `Panner ${param.name} – diff`);
    125 
    126          // Verify that the k‑rate output is constant over each render quantum
    127          for (let k = 0; k < kData.length; k += BLOCK) {
    128            const slice = kData.slice(k, k + BLOCK);
    129            assert_all_close(slice, slice[0],
    130                `Panner ${param.name} k‑rate frames [` +
    131                `${k}, ${k + slice.length - 1}]`
    132            );
    133          }
    134 
    135          // (No strict requirement on a‑rate slice variability, so we skip.)
    136        }, `Panner k‑rate vs a‑rate – ${param.name}`);
    137      });
    138 
    139      // Test k-rate automation of the listener.  The intial and final
    140      // automation values are pretty arbitrary, except that they should be such
    141      // that the panner and listener produces non-constant output.
    142      const listenerParams = [
    143        {name: 'positionX', initial: [1, 0], final: [1000, 1]},
    144        {name: 'positionY', initial: [1, 0], final: [1000, 1]},
    145        {name: 'positionZ', initial: [1, 0], final: [1000, 1]},
    146        {name: 'forwardX', initial: [-1, 0], final: [1, 1]},
    147        {name: 'forwardY', initial: [-1, 0], final: [1, 1]},
    148        {name: 'forwardZ', initial: [-1, 0], final: [1, 1]},
    149        {name: 'upX', initial: [-1, 0], final: [1000, 1]},
    150        {name: 'upY', initial: [-1, 0], final: [1000, 1]},
    151        {name: 'upZ', initial: [-1, 0], final: [1000, 1]},
    152      ];
    153 
    154      listenerParams.forEach(param => {
    155        promise_test(async t => {
    156          const testDuration = (5 * BLOCK) / SAMPLE_RATE;
    157          const context = new OfflineAudioContext({
    158            numberOfChannels: 1,
    159            sampleRate: SAMPLE_RATE,
    160            length: testDuration * SAMPLE_RATE,
    161          });
    162 
    163          const source = new ConstantSourceNode(context);
    164          const panner = new PannerNode(context, {
    165            distanceModel: 'inverse',
    166            coneOuterAngle: 360,
    167            coneInnerAngle: 10,
    168            positionX: 10,
    169            positionY: 10,
    170            positionZ: 10,
    171            orientationX: 1,
    172            orientationY: 1,
    173            orientationZ: 1,
    174          });
    175          source.connect(panner).connect(context.destination);
    176          source.start();
    177 
    178          const listener = context.listener;
    179 
    180        // Set listener properties to "random" values so that motion on one of
    181        // the attributes actually changes things relative to the panner
    182        // location.  And the up and forward directions should have a simple
    183        // relationship between them.
    184          listener.positionX.value = -1;
    185          listener.positionY.value = 1;
    186          listener.positionZ.value = -1;
    187          listener.forwardX.value = -1;
    188          listener.forwardY.value = 1;
    189          listener.forwardZ.value = -1;
    190          // Make the up vector not parallel or perpendicular to the forward and
    191          // position vectors so that automations of the up vector produce
    192          // noticeable differences.
    193          listener.upX.value = 1;
    194          listener.upY.value = 1;
    195          listener.upZ.value = 2;
    196 
    197          const audioParam = listener[param.name];
    198          audioParam.automationRate = K_RATE;
    199          assert_equals(
    200            audioParam.automationRate,
    201            K_RATE,
    202            `Listener ${param.name}.automationRate`
    203          );
    204 
    205          audioParam.setValueAtTime(...param.initial);
    206          audioParam.linearRampToValueAtTime(...param.final);
    207 
    208          const buffer = await context.startRendering();
    209          const data = buffer.getChannelData(0);
    210 
    211          assert_not_constant(data, `Listener ${param.name}`);
    212          for (let k = 0; k < data.length; k += BLOCK) {
    213            const slice = data.slice(
    214              k,
    215              Math.min(k + BLOCK, data.length)
    216            );
    217            assert_all_constant(
    218              slice,
    219              slice[0],
    220                `Listener ${param.name} frames [${k}, ` +
    221                `${k + slice.length - 1}]`
    222            );
    223          }
    224        }, `Listener k-rate ${param.name}`);
    225      });
    226    </script>
    227  </body>
    228 </html>