<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Processing Of AudioParams of Disconnected AudioNodes
    </title>
    <script src="../../imported/w3c/web-platform-tests/resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      // Arbitrary sample rate.
      let sampleRate = 8000;

      // Arbitrary number of frames for the test, except it must be greater than
      // |automationEndFrame|.
      let renderFrames = 1024;

      // The linear ramp automation ends at this frame. Arbitrary, except it
      // must be strictly less than the render length.
      let automationEndFrame = 128;

      let audit = Audit.createTaskRunner();

      // There should be a test for every single node which has an AudioParam.
      // Source nodes are not included in this because the AudioParams for the
      // source nodes only process when the node has been started.

      audit.define('BiquadFilterNode', async (task, should) => {
        let nodeName = 'BiquadFilterNode'
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'Q',
          initialValue: 2,
          rampFinalValue: 5
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'detune',
          initialValue: 1,
          rampFinalValue: 0.5
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'frequency',
          initialValue: 1000,
          rampFinalValue: 100
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'gain',
          initialValue: -3,
          rampFinalValue: 3
        });
        task.done();
      });

      audit.define('DelayNode', (task, should) => {
        testParamAutomation(should, {
          nodeName: 'DelayNode',
          paramName: 'delayTime',
          initialValue: 0.25,
          rampFinalValue: 0.5
        }).then(() => task.done());
      });

      audit.define('DynamicsCompressorNode', async (task, should) => {
        let nodeName = 'DynamicsCompressorNode';
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'attack',
          initialValue: 0.1,
          rampFinalValue: 0.5
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'knee',
          initialValue: 0,
          rampFinalValue: 25
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'ratio',
          initialValue: 1,
          rampFinalValue: 15
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'release',
          initialValue: 0,
          rampFinalValue: 0.75
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'threshold',
          initialValue: -50,
          rampFinalValue: -10
        });
        task.done();
      });

      audit.define('GainNode', (task, should) => {
        testParamAutomation(should, {
          nodeName: 'GainNode',
          paramName: 'gain',
          initialValue: 1,
          rampFinalValue: 0.5
        }).then(() => task.done());
      });

      audit.define('PannerNode', async (task, should) => {
        let nodeName = 'PannerNode';
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'positionX',
          initialValue: 0.1,
          rampFinalValue: 0.5
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'positionY',
          initialValue: 2,
          rampFinalValue: 30
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'positionZ',
          initialValue: 1,
          rampFinalValue: 15
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'orientationX',
          initialValue: 0.1,
          rampFinalValue: 0.5
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'orientationY',
          initialValue: 2,
          rampFinalValue: 30
        });
        await testParamAutomation(should, {
          nodeName: nodeName,
          paramName: 'orientationZ',
          initialValue: 1,
          rampFinalValue: 15
        });
        task.done();
      });

      audit.define('StereoPannerNode', (task, should) => {
        testParamAutomation(should, {
          nodeName: 'StereoPannerNode',
          paramName: 'pan',
          initialValue: 1,
          rampFinalValue: 0.5
        }).then(() => task.done());
      });

      audit.define('AudioListener', async (task, should) => {
        await testAudioListener(should, {
          paramName: 'positionX',
          initialValue: 1,
          rampFinalValue: 100
        });
        await testAudioListener(should, {
          paramName: 'positionY',
          initialValue: 1,
          rampFinalValue: 200
        });
        await testAudioListener(should, {
          paramName: 'positionZ',
          initialValue: 1,
          rampFinalValue: 300
        });
        await testAudioListener(should, {
          paramName: 'forwardX',
          initialValue: 1,
          rampFinalValue: -100
        });
        await testAudioListener(should, {
          paramName: 'forwardY',
          initialValue: 1,
          rampFinalValue: -200
        });
        await testAudioListener(should, {
          paramName: 'forwardZ',
          initialValue: 1,
          rampFinalValue: -300
        });
        await testAudioListener(should, {
          paramName: 'upX',
          initialValue: 1,
          rampFinalValue: 99
        });
        await testAudioListener(should, {
          paramName: 'upY',
          initialValue: 1,
          rampFinalValue: 42
        });
        await testAudioListener(should, {
          paramName: 'upZ',
          initialValue: 1,
          rampFinalValue: 137
        });
        task.done();
      });

      // Run test of automation processing. |options| is a dictionary that
      // describes the node to test, the AudioParams to be tested and the values
      // for the AudioParam.  The members of the dictionary are:
      //
      //   nodeName       - name of the node (constructor name)
      //   paramName      - name of the AudioParam to be tested
      //   initialValue   - starting value for linear ramp
      //   rampFinalValue - finale value for linear ramp
      //
      // The test is considered to have succeeded if the |.value| of the
      // AudioParam is the final value.
      //
      // A simple graph is created containing the node to be tested, connected
      // to the destination.  A linear ramp automation is scheduled for the
      // specified AudioParam, starting and ending at the values given.
      //
      // Processing is started and after some number of frames, the |.value| of
      // the AudioParam is obtained and compared against the final value.  These
      // should match exactly.
      function testParamAutomation(should, options) {
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        // Create the node to be tested
        let node = new window[options.nodeName](context);
        node.connect(context.destination);

        // A linear ramp starting at frame 0 to frame |automationEndFrame| is
        // used for the test.  This value is fairly arbitrary, but it should be
        // less than the total render frames.

        node[options.paramName].setValueAtTime(options.initialValue, 0);
        node[options.paramName].linearRampToValueAtTime(
            options.rampFinalValue, automationEndFrame / context.sampleRate);

        return context.startRendering().then(function(resultBuffer) {
          // Sanity check: the given ramp final value must not be the default
          // value, otherwise we can't easily tell if the automation was
          // actually run.
          should(
              options.rampFinalValue,
              options.nodeName + '.' + options.paramName + ' ramp final value')
              .notBeEqualTo(node[options.paramName].defaultValue);

          // The actual AudioParam value should be the rampe final value.
          should(
              node[options.paramName].value,
              options.nodeName + '.' + options.paramName + '.value')
              .beEqualTo(options.rampFinalValue);
        });
      }

      function testAudioListener(should, options) {
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        // Create the node to be tested
        let node = new PannerNode(context);
        node.connect(context.destination);

        // A linear ramp starting at frame 0 to frame |automationEndFrame| is
        // used for the test.  This value is fairly arbitrary, but it should be
        // less than the total render frames.

        context.listener[options.paramName].setValueAtTime(
            options.initialValue, 0);
        context.listener[options.paramName].linearRampToValueAtTime(
            options.rampFinalValue, automationEndFrame / context.sampleRate);

        return context.startRendering().then(function(resultBuffer) {
          // Sanity check: the given ramp final value must not be the default
          // value, otherwise we can't easily tell if the automation was
          // actually run.
          should(
              options.rampFinalValue,
              'AudioListener.' + options.paramName + ' ramp final value')
              .notBeEqualTo(context.listener[options.paramName].defaultValue);

          // The actual AudioParam value should be the rampe final value.
          should(
              context.listener[options.paramName].value,
              'AudioListener.' + options.paramName + '.value')
              .beEqualTo(options.rampFinalValue);
        });
      }

      audit.run();
    </script>
  </body>
</html>
