<!DOCTYPE html>
<html>
  <head>
    <title>
      Test DelayNode Has No Dezippering
    </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>
  </head>
  <body>
    <script id="layout-test-code">
      // The sample rate must be a power of two to avoid any round-off errors in
      // computing when to suspend a context on a rendering quantum boundary.
      // Otherwise this is pretty arbitrary.
      let sampleRate = 16384;

      let audit = Audit.createTaskRunner();

      audit.define(
          {label: 'test0', description: 'Test DelayNode has no dezippering'},
          (task, should) => {
            let context = new OfflineAudioContext(1, sampleRate, sampleRate);

            // Simple integer ramp for testing delay node
            let buffer = new AudioBuffer(
                {length: context.length, sampleRate: context.sampleRate});
            let rampData = buffer.getChannelData(0);
            for (let k = 0; k < rampData.length; ++k) {
              rampData[k] = k + 1;
            }

            // |delay0Frame| is the initial delay in frames. |delay1Frame| is
            // the new delay in frames.  These must be integers.
            let delay0Frame = 64;
            let delay1Frame = 16;

            let src = new AudioBufferSourceNode(context, {buffer: buffer});
            let delay = new DelayNode(
                context, {delayTime: delay0Frame / context.sampleRate});

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

            // After a render quantum, change the delay to |delay1Frame|.
            context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate)
                .then(() => {
                  delay.delayTime.value = delay1Frame / context.sampleRate;
                })
                .then(() => context.resume());

            src.start();
            context.startRendering()
                .then(renderedBuffer => {
                  let renderedData = renderedBuffer.getChannelData(0);

                  // The first |delay0Frame| frames should be zero.
                  should(
                      renderedData.slice(0, delay0Frame),
                      'output[0:' + (delay0Frame - 1) + ']')
                      .beConstantValueOf(0);

                  // Now we have the ramp should show up from the delay.
                  let ramp0 =
                      new Float32Array(RENDER_QUANTUM_FRAMES - delay0Frame);
                  for (let k = 0; k < ramp0.length; ++k) {
                    ramp0[k] = rampData[k];
                  }

                  should(
                      renderedData.slice(delay0Frame, RENDER_QUANTUM_FRAMES),
                      'output[' + delay0Frame + ':' +
                          (RENDER_QUANTUM_FRAMES - 1) + ']')
                      .beEqualToArray(ramp0);

                  // After one rendering quantum, the delay is changed to
                  // |delay1Frame|.
                  let ramp1 =
                      new Float32Array(context.length - RENDER_QUANTUM_FRAMES);
                  for (let k = 0; k < ramp1.length; ++k) {
                    // ramp1[k] = 1 + k + RENDER_QUANTUM_FRAMES - delay1Frame;
                    ramp1[k] =
                        rampData[k + RENDER_QUANTUM_FRAMES - delay1Frame];
                  }
                  should(
                      renderedData.slice(RENDER_QUANTUM_FRAMES),
                      'output[' + RENDER_QUANTUM_FRAMES + ':]')
                      .beEqualToArray(ramp1);
                })
                .then(() => task.done());
          });

      audit.define(
          {label: 'test1', description: 'Test value setter and setValueAtTime'},
          (task, should) => {
            testWithAutomation(should, {prefix: '', threshold: 6.5819e-5})
                .then(() => task.done());
          });

      audit.define(
          {label: 'test2', description: 'Test value setter and modulation'},
          (task, should) => {
            testWithAutomation(should, {
              prefix: 'With modulation: ',
              modulator: true
            }).then(() => task.done());
          });

      // Compare .value setter with setValueAtTime, Optionally allow modulation
      // of |delayTime|.
      function testWithAutomation(should, options) {
        let prefix = options.prefix;
        // Channel 0 is the output of delay node using the setter and channel 1
        // is the output using setValueAtTime.
        let context = new OfflineAudioContext(2, sampleRate, sampleRate);

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

        let src = new OscillatorNode(context);

        // |delay0Frame| is the initial delay value in frames. |delay1Frame| is
        // the new delay in frames. The values here are constrained only by the
        // constraints for a DelayNode.  These are pretty arbitrary except we
        // wanted them to be fractional so as not be on a frame boundary to
        // test interpolation compared with |setValueAtTime()|..
        let delay0Frame = 3.1;
        let delay1Frame = 47.2;

        let delayTest = new DelayNode(
            context, {delayTime: delay0Frame / context.sampleRate});
        let delayRef = new DelayNode(
            context, {delayTime: delay0Frame / context.sampleRate});

        src.connect(delayTest).connect(merger, 0, 0);
        src.connect(delayRef).connect(merger, 0, 1);

        if (options.modulator) {
          // Fairly arbitrary modulation of the delay time, with a peak
          // variation of 10 ms.
          let mod = new OscillatorNode(context, {frequency: 1000});
          let modGain = new GainNode(context, {gain: .01});
          mod.connect(modGain);
          modGain.connect(delayTest.delayTime);
          modGain.connect(delayRef.delayTime);
          mod.start();
        }

        // The time at which the delay time of |delayTest| node will be
        // changed.  This MUST be on a render quantum boundary, but is
        // otherwise arbitrary.
        let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;

        // Schedule the delay change on |delayRef| and also apply the value
        // setter for |delayTest| at |changeTime|.
        delayRef.delayTime.setValueAtTime(
            delay1Frame / context.sampleRate, changeTime);
        context.suspend(changeTime)
            .then(() => {
              delayTest.delayTime.value = delay1Frame / context.sampleRate;
            })
            .then(() => context.resume());

        src.start();

        return context.startRendering().then(renderedBuffer => {
          let actual = renderedBuffer.getChannelData(0);
          let expected = renderedBuffer.getChannelData(1);

          let match = should(actual, prefix + '.value setter output')
                          .beCloseToArray(
                              expected, {absoluteThreshold: options.threshold});
          should(
              match,
              prefix + '.value setter output matches setValueAtTime output')
              .beTrue();
        });
      }

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