const fftSize = 512;
const windowSize = 64;
const fftSizeTimesTwo = fftSize * 2;

const getArray = () => new Array(fftSize).fill(0);

const AmplitudeSplitter = ({ input, soft, loud, attack, release, threshold }) => {
  const loudProcessor = useFFT(fftSize, windowSize);
  const softProcessor = useFFT(fftSize, windowSize);
  const attackAccumulation = useVal(0);
  const releaseAccumulation = useVal(0);
  const multipliers = useReturnedConst(getArray);
  for (let i = 0; i < input.length; i ++) {
    loudProcessor.receiveSample(input[i]);
    softProcessor.receiveSample(input[i]);

    const loudOut = loudProcessor.getComplexOutput();
    const softOut = softProcessor.getComplexOutput();

    attackAccumulation.current += 1/attack[i];
    releaseAccumulation.current += 1/release[i];

    if (loudProcessor.isProcessingStep()) {
      const attackRate = attackAccumulation.current;
      const releaseRate = releaseAccumulation.current;

      attackAccumulation.current = 0;
      releaseAccumulation.current = 0;

      for (let j = 0; j < fftSizeTimesTwo; j += 2) {
        const multiplierIndex = j / 2;
        const bias = 1 - ((j / fftSizeTimesTwo) * (j / fftSizeTimesTwo));
        const magnitude = Math.sqrt(loudOut[j] * loudOut[j] + loudOut[j+1] * loudOut[j+1]) * (1/(1 + bias));

        if (magnitude < threshold[i] && multipliers[multiplierIndex] > 0) {
          multipliers[multiplierIndex] = Math.max(0, (1 - releaseRate) * multipliers[multiplierIndex]);
        } else if (magnitude > threshold[i] && multipliers[multiplierIndex] < 1) {
          multipliers[multiplierIndex] = Math.min(1, ((1 - attackRate) * multipliers[multiplierIndex]) + (attackRate));
        }
      }

      for (let j = 0; j < fftSizeTimesTwo; j += 2) {
        const multiplierIndex = j / 2;
        loudOut[j]   *= multipliers[multiplierIndex];
        loudOut[j+1] *= multipliers[multiplierIndex];

        softOut[j]   *= (1 - multipliers[multiplierIndex]);
        softOut[j+1] *= (1 - multipliers[multiplierIndex]);
      }
    }

    loud[i] = loudProcessor.produceSample();
    soft[i] = softProcessor.produceSample();
  }
}

this.constructDevice = () => constructPerChannelDevice({
  implementation: AmplitudeSplitter,
  inputs: {
    input: stereo('In'),

    threshold: numberSetting('Threshold', { defaultValue: 2, min: 0.01, max: 100, curve: Curve.Log2 }),
    attack: adsrTimeSetting('Attack', 10),
    release: adsrTimeSetting('Release', 100),
  },
  outputs: {
    loud: stereo('Loud', computeFFTDelay(fftSize, windowSize)),
    soft: stereo('Quiet', computeFFTDelay(fftSize, windowSize)),
  }
});
