<!doctype html>
<html>
  <head>
    <title>Panner Node Automation</title>
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <script src="../../resources/audit-util.js"></script>
    <script src="../../resources/audit.js"></script>
  </head>

  <body>
    <script>
      // Use a power-of-two to eliminate some round-off; otherwise, this isn't
      // really important.
      const sampleRate = 16384;

      // Render enough for the test; we don't need a lot.
      const renderFrames = 2048;

      // Initial panner positionX and final positionX for listener.
      const positionX = 2000;

      const audit = Audit.createTaskRunner();

      // Test that listener.positionX.value setter does the right thing.
      audit.define('Set Listener.positionX.value', (task, should) => {
        const context = new OfflineAudioContext(2, renderFrames, sampleRate);

        createGraph(context);

        // Frame at which the listener instantaneously moves to a new location.
        const moveFrame = 512;

        context.suspend(moveFrame / context.sampleRate)
            .then(() => {
              context.listener.positionX.value = positionX;
            })
            .then(() => context.resume());

        verifyOutput(context, moveFrame, should, 'listenr.positionX.value')
            .then(() => task.done());
      });

      // Test that listener.positionX.setValueAtTime() does the right thing.
      audit.define('Listener.positionX.setValue', (task, should) => {
        const context = new OfflineAudioContext(2, renderFrames, sampleRate);

        createGraph(context);

        // Frame at which the listener instantaneously moves to a new location.
        const moveFrame = 512;

        context.listener.positionX.setValueAtTime(
            positionX, moveFrame / context.sampleRate);

        verifyOutput(
            context, moveFrame, should, 'listener.positionX.setValueATTime')
            .then(() => task.done());
      });

      // Test that listener.setPosition() does the right thing.
      audit.define('Listener.setPosition', (task, should) => {
        const context = new OfflineAudioContext(2, renderFrames, sampleRate);

        createGraph(context);

        // Frame at which the listener instantaneously moves to a new location.
        const moveFrame = 512;

        context.suspend(moveFrame / context.sampleRate)
            .then(() => {
              context.listener.setPosition(positionX, 0, 0);
            })
            .then(() => context.resume());

        verifyOutput(context, moveFrame, should, 'listener.setPostion')
            .then(() => task.done());
      });

      audit.run();


      // Create the basic graph for testing which consists of an oscillator node
      // connected to a panner node.
      function createGraph(context) {
        const listener = context.listener;

        listener.positionX.value = 0;
        listener.positionY.value = 0;
        listener.positionZ.value = 0;

        const src = new OscillatorNode(context);

        const panner = new PannerNode(context, {
          distanceModel: 'linear',
          refDistance: 1,
          maxDistance: 3000,
          positionX: positionX,
          positionY: 0,
          positionZ: 0
        });
        src.connect(panner).connect(context.destination);

        src.start();
      }


      // Verify the output from the panner is correct.
      function verifyOutput(context, moveFrame, should, prefix) {
        return context.startRendering().then(resultBuffer => {
          // Get the outputs (left and right)
          const c0 = resultBuffer.getChannelData(0);
          const c1 = resultBuffer.getChannelData(1);

          // The src/listener set up is such that audio should only come
          // from the right for until |moveFrame|.  Hence the left channel
          // should be 0 (or very nearly 0).
          const zero = new Float32Array(moveFrame);

          should(
              c0.slice(0, moveFrame), `${prefix}: output0[0:${moveFrame - 1}]`)
              .beCloseToArray(zero, {absoluteThreshold: 1e-16});
          should(
              c1.slice(0, moveFrame), `${prefix}: output1[0:${moveFrame - 1}]`)
              .notBeConstantValueOf(0);

          // At |moveFrame| and beyond, the listener and source are at the
          // same position, so the outputs from the left and right should be
          // identical, and the left channel should not be 0 anymore.

          should(c0.slice(moveFrame), `${prefix}: output0[${moveFrame}:]`)
              .notBeConstantValueOf(0);
          should(c1.slice(moveFrame), `${prefix}: output1[${moveFrame}:]`)
              .beCloseToArray(c0.slice(moveFrame));
        });
      }
    </script>
  </body>
</html>