// Schroeder Reverb implementing the first diagram here: https://ccrma.stanford.edu/~jos/pasp/Example_Schroeder_Reverberators.html
const sampleRateMultiplier = (sampleRate / 25000);

const Reverb71 = ({ input, inputLevel, reverbLevel, output, time, mix, width }) => {
  const reverb = useStereoSignal();

  const allPass1 = useBiQuadFilter();
  const allPass2 = useBiQuadFilter();
  const allPass3 = useBiQuadFilter();

  const delay1 = useDelay();
  const delay2 = useDelay();
  const delay3 = useDelay();
  const delay4 = useDelay();

  const monoInput = useMonoSignal();
  const delay1Time = useMonoSignal();
  const delay2Time = useMonoSignal();
  const delay3Time = useMonoSignal();
  const delay4Time = useMonoSignal();

  for (let i = 0; i < time.length; i ++) {
    delay1Time[i] = time[i] * sampleRateMultiplier * 901;
    delay2Time[i] = time[i] * sampleRateMultiplier * 778;
    delay3Time[i] = time[i] * sampleRateMultiplier * 1011;
    delay4Time[i] = time[i] * sampleRateMultiplier * 1123;
  }

  for (let i = 0; i < input[0].length; i++) {
    monoInput[i] = 0;
    for (let c = 0; c < input.length; c ++) {
      monoInput[i] += input[c][i];
    }
  }

  const allpassed = allPass3(
    allPass2(
      allPass1(
        monoInput,
        BiQuadFilterMode.AllPass,
        useConstantBuffer(125),
        useConstantBuffer(.7),
        useConstantBuffer(0)
      ),
      BiQuadFilterMode.AllPass,
      useConstantBuffer(42),
      useConstantBuffer(.7),
      useConstantBuffer(0)
    ),
    BiQuadFilterMode.AllPass,
    useConstantBuffer(12),
    useConstantBuffer(.7),
    useConstantBuffer(0)
  );

  const delayed1 = delay1(allpassed, delay1Time, useConstantBuffer(0.805));
  const delayed2 = delay2(allpassed, delay2Time, useConstantBuffer(0.827));
  const delayed3 = delay3(allpassed, delay3Time, useConstantBuffer(0.783));
  const delayed4 = delay4(allpassed, delay4Time, useConstantBuffer(0.764));

  for (let i = 0; i < input[0].length; i++) {
    for (let c = 0; c < input.length; c ++) {
      const widthScale = (2 * ((c / (input.length - 1)) - .5)) * width[i];
      const delayed = (
        (1 / 8) * (
          (((widthScale * -1.00) + 1) / 2) * delayed1[i] +
          (((widthScale * -0.33) + 1) / 2) * delayed2[i] +
          (((widthScale *  0.33) + 1) / 2) * delayed3[i] +
          (((widthScale *  1.00) + 1) / 2) * delayed4[i]
        )
      );
      reverb[c][i] = delayed;
      output[c][i] = delayed * mix[i] + input[c][i] * (1 - mix[i]);
    }
  }

  writeSignalLevel(input, inputLevel);
  writeSignalLevel(reverb, reverbLevel);
};

const RenderReverbVis = (ctx, settings) => {
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;
  ctx.fillStyle = `${orange500}22`;
  if (!ctx.hasRendered) {
    ctx.hasRendered = true;
    ctx.fillRect(0, 0, w, h);
  }
  const imgData = ctx.getImageData(1, 0, w-1, h);
  ctx.putImageData(imgData, 0, 0);
  ctx.clearRect(w-1, 0, 1, h);
  ctx.fillRect(w-1, 0, 1, h);
  const signalHeight = h/2;

  const leftInputHeight = (settings.inputLevel[0]) * (signalHeight);
  const rightInputHeight = (settings.inputLevel[0]) * (signalHeight);

  ctx.fillStyle = `rgba(255,255,255,${settings.mix})`;
  const leftOutputHeight = (settings.reverbLevel[0]) * (signalHeight);
  const rightOutputHeight = (settings.reverbLevel[0]) * (signalHeight);
  ctx.fillRect(
    w - 1,
    (h/2) - leftOutputHeight,
    1,
    leftOutputHeight + rightOutputHeight
  );

  ctx.strokeStyle = orange500;
  ctx.beginPath();
  ctx.moveTo(
    w - 2,
    ctx.lastLeft === null ? (h/2) : ctx.lastLeft
  );
  ctx.lineTo(
    w - 1,
    (h/2) - leftInputHeight,
  );
  ctx.closePath();
  ctx.stroke();
  ctx.lastLeft = (h/2) - leftInputHeight;

  ctx.beginPath();
  ctx.moveTo(
    w - 2,
    ctx.lastRight === null ? (h/2) : ctx.lastRight
  );
  ctx.lineTo(
    w - 1,
    (h/2) + rightInputHeight,
  );
  ctx.closePath();
  ctx.stroke();
  ctx.lastRight = (h/2) + rightInputHeight;
}

this.constructDevice = () => constructPerBufferDevice({
  implementation: Reverb71,
  inputs: {
    input: stereo('In'),
    time: numberSetting('Time', { defaultValue: 1, min: .1, max: 10, curve: Curve.Log2 }),
    mix: numberSetting('Mix', { defaultValue: 1, min: 0, max: 1 }),
    width: numberSetting('Width', { defaultValue: 1, min: 0, max: 1 })
  },
  outputs: {
    output: stereo('Out'),
  },
  sharedArrays: {
    inputLevel: 1,
    reverbLevel: 1,
  },
  layout: themed(
    DeviceLayoutTheme.Dark,
    col([
      canvas(
        'visualizer',
        RenderReverbVis,
        250,
        100
      ),
      spacer(0.25),
      row([
        titled.knob('time'),
        titled.knob('mix'),
        titled.knob('width')
      ])
    ])
  )
});
