<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Automation Following setValueCurveAtTime Automations
    </title>
    <script src="../../imported/w3c/web-platform-tests/resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
    <script src="../resources/audio-param.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let sampleRate = 12800;
      // Some short duration because we don't need to run the test for very
      // long.
      let testDurationFrames = 256;
      let testDurationSec = testDurationFrames / sampleRate;
      let curveDuration = testDurationSec / 2;

      let audit = Audit.createTaskRunner();

      // Configuration for each test.
      //
      // Required options:
      //   automation - Name of automation method to test
      //   time       - Time for the automation method.
      // Optional options:
      //   extraDuration - extra time for the duration of the setValueCurve
      //                   duration.  Default is 0. This should not be on a
      //                   sample frame boundary. This is for testing that
      //                   curves that don't end on a frame boundary are handled
      //                   correctly.
      //   threshold     - Error threshold for the test; default is 0.
      let testConfigs = [
        {
          automation: 'linearRampToValueAtTime',
          time: testDurationSec,
          threshold: 3.9737e-8
        },
        {
          automation: 'linearRampToValueAtTime',
          time: testDurationSec,
          extraDuration: 0.5 / sampleRate,
          threshold: 1.8141e-8
        },
        {
          automation: 'exponentialRampToValueAtTime',
          time: testDurationSec,
          threshold: 3.9737e-8
        },
        {
          automation: 'exponentialRampToValueAtTime',
          time: testDurationSec,
          extraDuration: 0.5 / sampleRate,
          threshold: 7.8294e-8
        },
        {
          automation: 'setTargetAtTime',
          time: curveDuration,
          threshold: 1.5895e-7
        },
        {
          automation: 'setTargetAtTime',
          time: curveDuration + 0.5 / sampleRate,
          extraDuration: 0.5 / sampleRate,
          threshold: 1.3278e-7
        }
      ];

      // Define tests from the configs
      for (k in testConfigs) {
        audit.define(k + ': ' + testConfigs[k].automation, (function(config) {
                       return (task, should) => {
                         runTest(should, config).then(() => task.done());
                       };
                     })(testConfigs[k]));
      }

      audit.run();

      function runTest(should, options) {
        // For the test, use a gain node with a constant input to test the
        // automations.
        let context =
            new OfflineAudioContext(1, testDurationFrames, sampleRate);
        let source = context.createBufferSource();
        source.buffer = createConstantBuffer(context, 1, 1);
        source.loop = true;

        let gain = context.createGain();

        // Any valid curve is ok.  We only use the last value for testing.
        let curve = [0, 2, 0.3];
        let actualDuration = curveDuration + (options.extraDuration || 0);
        gain.gain.setValueCurveAtTime(
            Float32Array.from(curve), 0, actualDuration);

        // Run the desired test automation.  The extra parameter (0.01) is only
        // used for setTargetAtTime tests; it's ignored for other tests.
        let automationValue = 2;
        gain.gain[options.automation](automationValue, options.time, 0.01);

        source.connect(gain);
        gain.connect(context.destination);

        source.start();

        return context.startRendering().then(function(resultBuffer) {
          let result = resultBuffer.getChannelData(0);

          // Only need to verify that the ramp started at the right
          // value. Figure the nearest sample frame to the end curve.
          let curveEndFrame = Math.ceil(actualDuration * sampleRate);

          let expectedResult = curve[curve.length - 1];

          // Determine the expected value after the end of the setValueCurve
          // event.
          if (options.automation == 'linearRampToValueAtTime') {
            expectedResult = audioParamLinearRamp(
                curveEndFrame / sampleRate, curve[curve.length - 1],
                actualDuration, automationValue, testDurationSec);
          } else if (options.automation == 'exponentialRampToValueAtTime') {
            expectedResult = audioParamExponentialRamp(
                curveEndFrame / sampleRate, curve[curve.length - 1],
                actualDuration, automationValue, testDurationSec);
          } else if (options.automation == 'setTargetAtTime') {
            expectedResult = audioParamSetTarget(
                curveEndFrame / sampleRate, curve[curve.length - 1],
                actualDuration, automationValue, 0.01);
          }

          let message = 'setValueCurve(..., ' + 0 + ', ' + actualDuration +
              ').' + options.automation + '(2, ' + testDurationSec;

          if (options.automation == 'setTargetAtTime')
            message += ', 0.01';
          message += ')';

          should(
              result[curveEndFrame],
              message + ': value at time ' + curveEndFrame / sampleRate)
              .beCloseTo(expectedResult, {threshold: options.threshold || 0});
        });
      }

      function linearRampValue(t, t0, v0, t1, v1) {
        return v0 + (v1 - v0) * (t - t0) / (t1 - t0);
      }

      function exponentialRampValue(t, t0, v0, t1, v1) {
        return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
      }

      function setTargetValue(t, t0, v0, v1, timeConstant) {
        return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant)
      }
    </script>
  </body>
</html>
