<!doctype html>
<html>
  <head>
    <title>Test k-rate AudioParams of PannerNode</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>
    <script src="automation-rate-testing.js"></script>
  </head>

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

      // Define a test where we verify that a k-rate audio param produces
      // different results from an a-rate audio param for each of the audio
      // params of a biquad.
      //
      // Each entry gives the name of the AudioParam, an initial value to be
      // used with setValueAtTime, and a final value to be used with
      // linearRampToValueAtTime. (See |doTest| for details as well.)

      [{name: 'positionX', initial: 0, final: 1000},
       {name: 'positionY', initial: 0, final: 1000},
       {name: 'orientationX', initial: 1, final: 10},
       {name: 'orientationY', initial: 1, final: 10},
       {name: 'orientationZ', initial: 1, final: 10},
      ].forEach(paramProperty => {
        audit.define('Panner k-rate ' + paramProperty.name, (task, should) => {
          // Arbitrary sample rate and duration.
          let sampleRate = 8000;
          let testDuration = 5 * 128 / sampleRate;
          let context = new OfflineAudioContext({
            numberOfChannels: 3,
            sampleRate: sampleRate,
            length: testDuration * sampleRate
          });

          doTest(context, should, {
            sourceNodeName: 'ConstantSourceNode',
            verifyPieceWiseConstant: true,
            nodeName: 'PannerNode',
            // Make the source directional so orientation matters, and set some
            // defaults for the position and orientation so that we're not on an
            // axis where the azimuth and elevation might be constant when
            // moving one of the AudioParams.
            nodeOptions: {
              distanceModel: 'inverse',
              coneOuterAngle: 360,
              coneInnerAngle: 0,
              positionX: 1,
              positionY: 1,
              positionZ: 1,
              orientationX: 0,
              orientationY: 1,
              orientationZ: 1
            },
            prefix: `k-rate ${paramProperty.name}`,
            // Just set the frequency to k-rate
            rateSettings: [
              {name: paramProperty.name, value: 'k-rate'},
            ],
            // Automate just the given AudioParam
            automations: [{
              name: paramProperty.name,
              methods: [
                {name: 'setValueAtTime', options: [paramProperty.initial, 0]}, {
                  name: 'linearRampToValueAtTime',
                  options: [paramProperty.final, testDuration]
                }
              ]
            }]
          }).then(() => task.done());
        });
      });

      // Test k-rate automation of the listener.  The intial and final
      // automation values are pretty arbitrary, except that they should be such
      // that the panner and listener produces non-constant output.
      [{name: 'positionX', initial: [1, 0], final: [1000, 1]},
       {name: 'positionY', initial: [1, 0], final: [1000, 1]},
       {name: 'positionZ', initial: [1, 0], final: [1000, 1]},
       {name: 'forwardX', initial: [-1, 0], final: [1, 1]},
       {name: 'forwardY', initial: [-1, 0], final: [1, 1]},
       {name: 'forwardZ', initial: [-1, 0], final: [1, 1]},
       {name: 'upX', initial: [-1, 0], final: [1000, 1]},
       {name: 'upY', initial: [-1, 0], final: [1000, 1]},
       {name: 'upZ', initial: [-1, 0], final: [1000, 1]},
      ].forEach(paramProperty => {
        audit.define(
            'Listener k-rate ' + paramProperty.name, (task, should) => {
              // Arbitrary sample rate and duration.
              let sampleRate = 8000;
              let testDuration = 5 * 128 / sampleRate;
              let context = new OfflineAudioContext({
                numberOfChannels: 1,
                sampleRate: sampleRate,
                length: testDuration * sampleRate
              });

              doListenerTest(context, should, {
                param: paramProperty.name,
                initial: paramProperty.initial,
                final: paramProperty.final
              }).then(() => task.done());
            });
      });

      audit.run();

      function doListenerTest(context, should, options) {
        let src = new ConstantSourceNode(context);
        let panner = new PannerNode(context, {
          distanceModel: 'inverse',
          coneOuterAngle: 360,
          coneInnerAngle: 10,
          positionX: 10,
          positionY: 10,
          positionZ: 10,
          orientationX: 1,
          orientationY: 1,
          orientationZ: 1
        });

        src.connect(panner).connect(context.destination);

        src.start();

        let listener = context.listener;

        // Set listener properties to "random" values so that motion on one of
        // the attributes actually changes things relative to the panner
        // location.  And the up and forward directions should have a simple
        // relationship between them.
        listener.positionX.value = -1;
        listener.positionY.value = 1;
        listener.positionZ.value = -1;
        listener.forwardX.value = -1;
        listener.forwardY.value = 1;
        listener.forwardZ.value = -1;
        // Make the up vector not parallel or perpendicular to the forward and
        // position vectors so that automations of the up vector produce
        // noticeable differences.
        listener.upX.value = 1;
        listener.upY.value = 1;
        listener.upZ.value = 2;

        let audioParam = listener[options.param];
        audioParam.automationRate = 'k-rate';

        let prefix = `Listener ${options.param}`;
        should(audioParam.automationRate, prefix + '.automationRate')
            .beEqualTo('k-rate');
        should(() => {
          audioParam.setValueAtTime(...options.initial);
        }, prefix + `.setValueAtTime(${options.initial})`).notThrow();
        should(() => {
          audioParam.linearRampToValueAtTime(...options.final);
        }, prefix + `.linearRampToValueAtTime(${options.final})`).notThrow();

        return context.startRendering().then(renderedBuffer => {
          let prefix = `Listener k-rate ${options.param}: `;
          let output = renderedBuffer.getChannelData(0);
          // Sanity check that the output isn't constant.
          should(output, prefix + `Output`).notBeConstantValueOf(output[0]);

          // Verify that the output is constant over each render quantum
          for (let k = 0; k < output.length; k += 128) {
            should(
                output.slice(k, k + 128), prefix + `Output [${k}, ${k + 127}]`)
                .beConstantValueOf(output[k]);
          }
        });
      }
    </script>
  </body>
</html>
