<!DOCTYPE html>
<title>Test that DelayNode output channelCount matches that of the delayed input</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
// See https://github.com/WebAudio/web-audio-api/issues/25

// sampleRate is a power of two so that delay times are exact in base-2
// floating point arithmetic.
const SAMPLE_RATE = 32768;
// Arbitrary delay time in frames (but this is assumed a multiple of block
// size below):
const DELAY_FRAMES = 3 * 128;
// Implementations may apply interpolation to input samples, which can spread
// the effect of input with larger channel counts over neighbouring blocks.
// This test ignores enough neighbouring blocks to ignore the effects of
// filter radius of up to this number of frames:
const INTERPOLATION_GRACE = 128;
// Number of frames of DelayNode output that are known to be stereo:
const STEREO_FRAMES = 128;
// The delay will be increased at this frame to switch DelayNode output back
// to mono.
const MONO_OUTPUT_START_FRAME =
  DELAY_FRAMES + INTERPOLATION_GRACE + STEREO_FRAMES;
// Number of frames of output that are known to be mono after the known stereo
// and interpolation grace.
const MONO_FRAMES = 128;
// Total length allows for interpolation after effects of stereo input are
// finished and one block to test return to mono output:
const TOTAL_LENGTH =
  MONO_OUTPUT_START_FRAME + INTERPOLATION_GRACE + MONO_FRAMES;
// maxDelayTime, is a multiple of block size, because the Gecko implementation
// once had a bug with delayTime = maxDelayTime in this situation:
const MAX_DELAY_FRAMES = TOTAL_LENGTH + INTERPOLATION_GRACE;

promise_test(() => {
  let context = new OfflineAudioContext({numberOfChannels: 1,
                                         length: TOTAL_LENGTH,
                                         sampleRate: SAMPLE_RATE});

  // Only channel 1 of the splitter is connected to the destination.
  let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
  splitter.connect(context.destination, 1);

  // A gain node has channelCountMode "max" and channelInterpretation
  // "speakers", and so will up-mix a mono input when there is stereo input.
  let gain = new GainNode(context);
  gain.connect(splitter);

  // The delay node initially outputs a single channel of silence, when it
  // does not have enough signal in its history to output what it has
  // previously received.  After the delay period, it will then output the
  // stereo signal it received.
  let delay =
      new DelayNode(context,
                    {maxDelayTime: MAX_DELAY_FRAMES / context.sampleRate,
                     delayTime: DELAY_FRAMES / context.sampleRate});
  // Schedule an increase in the delay to return to mono silent output from
  // the unfilled portion of the DelayNode's buffer.
  delay.delayTime.setValueAtTime(MAX_DELAY_FRAMES / context.sampleRate,
                                 MONO_OUTPUT_START_FRAME / context.sampleRate);
  delay.connect(gain);

  let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
  stereoMerger.connect(delay);

  let leftOffset = 0.125;
  let rightOffset = 0.5;
  let leftSource = new ConstantSourceNode(context, {offset: leftOffset});
  let rightSource = new ConstantSourceNode(context, {offset: rightOffset});
  leftSource.start();
  rightSource.start();
  leftSource.connect(stereoMerger, 0, 0);
  rightSource.connect(stereoMerger, 0, 1);
  // Connect a mono source directly to the gain, so that even stereo silence
  // will be detected in channel 1 of the gain output because it will cause
  // the mono source to be up-mixed.
  let monoOffset = 0.25
  let monoSource = new ConstantSourceNode(context, {offset: monoOffset});
  monoSource.start();
  monoSource.connect(gain);

  return context.startRendering().
    then((buffer) => {
      let output = buffer.getChannelData(0);

      function assert_samples_equal(startIndex, length, expected, description)
      {
        for (let i = startIndex; i < startIndex + length; ++i) {
          assert_equals(output[i], expected, description + ` at ${i}`);
        }
      }

      assert_samples_equal(0, DELAY_FRAMES - INTERPOLATION_GRACE,
                           0, "Initial mono");
      assert_samples_equal(DELAY_FRAMES + INTERPOLATION_GRACE, STEREO_FRAMES,
                           monoOffset + rightOffset, "Stereo");
      assert_samples_equal(MONO_OUTPUT_START_FRAME + INTERPOLATION_GRACE,
                           MONO_FRAMES,
                           0, "Final mono");
    });
});

</script>
