<!doctype html>
<html>
  <head>
    <title>k-rate AudioParams with Inputs</title>
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <script src="/webaudio/resources/audit-util.js"></script>
    <script src="/webaudio/resources/audit.js"></script>
  </head>

  <body>
    <script>
      let audit = Audit.createTaskRunner();

      // Must be power of two to eliminate round-off
      const sampleRate = 8192;

      // Arbitrary duration that doesn't need to be too long to verify k-rate
      // automations.  Probably should be at least a few render quanta.
      const testDuration = 8 * RENDER_QUANTUM_FRAMES / sampleRate;

      // Test k-rate GainNode.gain is k-rate
      audit.define(
          {label: 'Gain', description: 'k-rate GainNode.gain'},
          (task, should) => {
            let context = new OfflineAudioContext({
              numberOfChannels: 2,
              sampleRate: sampleRate,
              length: testDuration * sampleRate
            });

            let merger = new ChannelMergerNode(
                context, {numberOfInputs: context.destination.channelCount});
            merger.connect(context.destination);

            let src = new ConstantSourceNode(context);

            createTestSubGraph(context, src, merger, 'GainNode', 'gain');

            src.start();
            context.startRendering()
                .then(buffer => {
                  let actual = buffer.getChannelData(0);
                  let expected = buffer.getChannelData(1);

                  for (let k = 0; k < actual.length;
                       k += RENDER_QUANTUM_FRAMES) {
                    should(
                        actual.slice(k, k + RENDER_QUANTUM_FRAMES),
                        `gain[${k}:${k + RENDER_QUANTUM_FRAMES}]`)
                        .beConstantValueOf(expected[k]);
                  }
                })
                .then(() => task.done());
          });

      // Test k-rate StereoPannerNode.pan is k-rate
      audit.define(
          {label: 'StereoPanner', description: 'k-rate StereoPannerNode.pan'},
          (task, should) => {
            let context = new OfflineAudioContext({
              numberOfChannels: 2,
              sampleRate: sampleRate,
              length: testDuration * sampleRate
            });
            let merger = new ChannelMergerNode(
                context, {numberOfInputs: context.destination.channelCount});
            merger.connect(context.destination);

            let src = new ConstantSourceNode(context);

            createTestSubGraph(
                context, src, merger, 'StereoPannerNode', 'pan', {
                  testModSetup: node => {
                    node.offset.setValueAtTime(-1, 0);
                    node.offset.linearRampToValueAtTime(1, testDuration);
                  }
                });

            src.start();
            context.startRendering()
                .then(buffer => {
                  let actual = buffer.getChannelData(0);
                  let expected = buffer.getChannelData(1);

                  for (let k = 0; k < actual.length; k += 128) {
                    should(actual.slice(k, k + 128), `pan[${k}:${k + 128}]`)
                        .beConstantValueOf(expected[k]);
                  }
                })
                .then(() => task.done());
          });

      audit.run();

      function createTestSubGraph(
          context, src, merger, nodeName, paramName, options) {
        // The test node which has its AudioParam set up for k-rate autmoations.
        let tstNode = new window[nodeName](context);

        if (options && options.setups) {
          options.setups(tstNode);
        }
        tstNode[paramName].automationRate = 'k-rate';

        // Modulating signal for the test node.  Just a linear ramp. This is
        // connected to the AudioParam of the tstNode.
        let tstMod = new ConstantSourceNode(context);
        if (options && options.testModSetup) {
          options.testModSetup(tstMod);
        } else {
          tstMod.offset.linearRampToValueAtTime(context.length, testDuration);
        }

        tstMod.connect(tstNode[paramName]);
        src.connect(tstNode).connect(merger, 0, 0);

        // The ref node is the same type of node as the test node, but uses
        // a-rate automation.  However, the modulating signal is k-rate.  This
        // causes the input to the audio param to be constant over a render,
        // which is basically the same as making the audio param be k-rate.
        let refNode = new window[nodeName](context);
        let refMod = new ConstantSourceNode(context);
        refMod.offset.automationRate = 'k-rate';
        if (options && options.testModSetup) {
          options.testModSetup(refMod);
        } else {
          refMod.offset.linearRampToValueAtTime(context.length, testDuration);
        }

        refMod.connect(refNode[paramName]);
        src.connect(refNode).connect(merger, 0, 1);

        tstMod.start();
        refMod.start();
      }
    </script>
  </body>
</html>
