<!doctype html>
<html>
  <head>
    <title>k-rate AudioParams with inputs for DelayNode</title>
    <script src="/resources/testharness.js"></script>
    <script src="/resources/testharnessreport.js"></script>
    <script src="/webaudio/resources/audit.js"></script>
    <script src="/webaudio/resources/audit-util.js"></script>
  </head>

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

      // Power-of-two to eliminate round-off in computing time and frames, but
      // is otherwise arbitrary.
      const sampleRate = 8192;

      // Arbitrary duration except it must be greater than or equal to 1.
      const testDuration = 1.5;

      audit.define(
          {label: 'delayTime', description: `DelayNode delayTime k-rate input`},
          async (task, should) => {
            // Two channels:  0 = test result, 1 = expected result.
            let context = new OfflineAudioContext({
              numberOfChannels: 2,
              sampleRate: sampleRate,
              length: testDuration * sampleRate
            });

            let merger = new ChannelMergerNode(
                context, {numberOfInputs: context.destination.channelCount});
            merger.connect(context.destination);

            // Test the DelayNode by having a reference node (refNode) that uses
            // k-rate automations of delayTime. The test node (testNode) sets
            // delayTime to k-rate with a connected input that has the same
            // automation vlaues as the reference node.  The test passes if the
            // output from each node is identical to each other.

            // Just some non-constant source.
            let src = new OscillatorNode(context);

            // The end value and time for the linear ramp.  These values are
            // chosen so that the delay advances faster than real time.
            let endValue = 1.125;
            let endTime = 1;

            let refNode;

            should(
                () => refNode = new DelayNode(context),
                `refNode = new DelayNode(context)`)
                .notThrow();

            should(
                () => refNode.delayTime.automationRate = 'k-rate',
                `refNode.delayTime.automationRate = 'k-rate'`)
                .notThrow();

            should(
                () => refNode.delayTime.setValueAtTime(0, 0),
                `refNode.delayTime.setValueAtTime(0, 0)`)
                .notThrow();

            should(
                () => refNode.delayTime.linearRampToValueAtTime(
                    endValue, endTime),
                `refNode.delayTime.linearRampToValueAtTime(${endValue}, ${
                    endTime})`)
                .notThrow();

            let testNode;

            should(
                () => testNode = new DelayNode(context),
                `testNode = new DelayNode(context)`)
                .notThrow();

            should(
                () => testNode.delayTime.automationRate = 'k-rate',
                `testNode.delayTime.automationRate = 'k-rate'`)
                .notThrow();

            let testMod;

            should(
                () => testMod = new ConstantSourceNode(context),
                `testMod = new ConstantSourceNode(context)`)
                .notThrow();

            should(
                () => testMod.offset.setValueAtTime(0, 0),
                `testMod.offset.setValueAtTime(0, 0)`)
                .notThrow();

            should(
                () => testMod.offset.linearRampToValueAtTime(endValue, endTime),
                `testMod.offset.linearRampToValueAtTime(${endValue}, ${
                    endTime})`)
                .notThrow();

            should(
                () => testMod.connect(testNode.delayTime),
                `testMod.connect(testNode.delayTime)`)
                .notThrow();

            // Connect up everything and go!
            src.connect(testNode).connect(merger, 0, 0);
            src.connect(refNode).connect(merger, 0, 1);

            src.start();
            testMod.start();

            const buffer = await context.startRendering();
            let expected = buffer.getChannelData(0);
            let actual = buffer.getChannelData(1);

            // Quick sanity check that output isn't zero.  This means we messed
            // up the connections or automations or the buffer source.
            should(expected, `Expected k-rate delayTime AudioParam with input`)
                .notBeConstantValueOf(0);
            should(actual, `Actual k-rate delayTime AudioParam with input`)
                .notBeConstantValueOf(0);

            // Quick sanity check.  The amount of delay after one render is
            // endValue * 128 / sampleRate.  But after 1 render, time has
            // advanced 128/sampleRate.  Hence, the delay exceeds the time by
            // (endValue - 1)*128/sampleRate sec or (endValue - 1)*128 frames.
            // This means the output must be EXACTLY zero for this many frames
            // in the second render.
            let zeroFrames = (endValue - 1) * RENDER_QUANTUM_FRAMES;
            should(
                actual.slice(
                    RENDER_QUANTUM_FRAMES, RENDER_QUANTUM_FRAMES + zeroFrames),
                `output[${RENDER_QUANTUM_FRAMES}, ${
                    RENDER_QUANTUM_FRAMES + zeroFrames - 1}]`)
                .beConstantValueOf(0);
            should(
                actual.slice(
                    RENDER_QUANTUM_FRAMES + zeroFrames,
                    2 * RENDER_QUANTUM_FRAMES),
                `output[${RENDER_QUANTUM_FRAMES + zeroFrames}, ${
                    2 * RENDER_QUANTUM_FRAMES - 1}]`)
                .notBeConstantValueOf(0);

            // The expected and actual results must be EXACTLY the same.
            should(actual, `k-rate delayTime AudioParam with input`)
                .beCloseToArray(expected, {absoluteThreshold: 0});
          });

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