<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Clamping of 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>
  </head>
  <body>
    <script id="layout-test-code">
      // Some arbitrary sample rate for the offline context.
      let sampleRate = 48000;

      // Duration of test (fairly arbitrary).
      let renderDuration = 1;
      let renderFrames = renderDuration * sampleRate;

      let audit = Audit.createTaskRunner();

      audit.define('clamp', (task, should) => {
        // Test clamping of automations.  Most AudioParam limits are essentially
        // unbounded, so clamping doesn't happen.  For most other AudioParams,
        // the behavior is sufficiently complicated with complicated outputs
        // that testing them is hard.  However the output behavior of the
        // frequency parameter for a BiquadFilter is relatively simple.  Use
        // that as the test.
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        let source = context.createBufferSource();
        source.buffer = createConstantBuffer(context, 1, 1);
        source.loop = true;

        let filter = context.createBiquadFilter();
        filter.type = 'lowpass';

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

        let V0 = 880;
        let T0 = 0;
        filter.frequency.setValueAtTime(V0, T0);

        let V1 = -1000;
        let T1 = renderDuration / 4;
        filter.frequency.linearRampToValueAtTime(V1, T1);

        let V2 = 880;
        let T2 = renderDuration / 2;
        filter.frequency.linearRampToValueAtTime(V2, T2);

        source.start();

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

              // When the cutoff frequency of a lowpass filter is 0, nothing
              // gets through.  Hence the output of the filter between the
              // clamping period should be exactly zero. This tests passes if
              // the output is 0 during the expected range.
              //
              // Compute when the frequency value of the biquad goes to 0.  In
              // general, t = (T0*V1 -T1*V0)/(V1-V0) (using the notation from
              // the spec.)
              let clampStartTime = solveLinearRamp(0, V0, T0, V1, T1);
              let clampEndTime = solveLinearRamp(0, V1, T1, V2, T2);

              let clampStartFrame = Math.ceil(clampStartTime * sampleRate);
              let clampEndFrame = Math.floor(clampEndTime * sampleRate);

              let clampedSignal =
                  result.slice(clampStartFrame, clampEndFrame + 1);
              let expectedSignal = new Float32Array(clampedSignal.length);
              expectedSignal.fill(0);

              // Output should be zero.
              should(
                  clampedSignal,
                  'Clamped signal in frame range [' + clampStartFrame + ', ' +
                      clampEndFrame + ']')
                  .beCloseToArray(expectedSignal, 0);

              // Find the actual clamp range based on the output values.
              let actualClampStart = result.findIndex(x => x === 0);
              let actualClampEnd = actualClampStart +
                  result.slice(actualClampStart).findIndex(x => x != 0);

              // Verify that the expected clamping range is a subset of the
              // actual range.
              should(actualClampStart, 'Actual Clamp start')
                  .beLessThanOrEqualTo(clampStartFrame);
              should(actualClampEnd, 'Actual Clamp end')
                  .beGreaterThanOrEqualTo(clampEndFrame);

            })
            .then(() => task.done());
      });

      audit.run();

      function solveLinearRamp(v, v0, t0, v1, t1) {
        // Solve the linear ramp equation for the time t at which the ramp
        // reaches the value v.  The linear ramp equation (from the spec) is
        //
        //  v(t) = v0 + (v1 - v0) * (t - t0)/(t1 - t0)
        //
        // Find t such that
        //
        //   v = v0 + (v1 - v0) * (t - t0)/(t1 - t0)
        //
        // Then
        //
        //   t = (t0 * v1 - t1 * v0 + (t1 - t0) * v) / (v1 - v0)
        //
        return (t0 * v1 - t1 * v0 + (t1 - t0) * v) / (v1 - v0);
      }
    </script>
  </body>
</html>
