const constructArray = (length) => new Float64Array(Math.round(length));
const msToFadeMultiplier = (ms) => Math.pow(0.1, 1.0 / (ms + 1));

const Limiter = ({ input, output, release, gain, smoothing }) => {  
  const attackSamples = useReturnedConst(msToSamples, 3, sampleRate);
  const attackConst = useReturnedConst(msToFadeMultiplier, attackSamples)
  const threshold = 1;
  const appliedGain = useVal(1);
  const smoothedTargetGain = useVal(1);
  const delayedSignal = useDelayedStereo(input, attackSamples);

  for (let i = 0; i < delayedSignal[0].length; i ++) {
    let max = 0;
    const releaseConst = msToFadeMultiplier(release[i]);
    
    for (let c = 0; c < delayedSignal.length; c ++) {
      max = Math.max(gain[i] * Math.abs(input[c][i]), max);
    }

    let targetGain = 1;
    
    if (max > threshold) {
      targetGain = threshold / max;
    }

    if (targetGain < appliedGain.current) {
      smoothedTargetGain.current = Math.min(smoothedTargetGain.current, (targetGain - smoothing[i] * appliedGain.current) * (1/(1-smoothing[i])));
    } else {
      smoothedTargetGain.current = targetGain;
    }

    if (smoothedTargetGain.current < appliedGain.current) {
      // Attacking
      appliedGain.current = (
        attackConst * (appliedGain.current - smoothedTargetGain.current) + 
        smoothedTargetGain.current
      );

      if (targetGain > appliedGain.current) {
        appliedGain.current = targetGain;
      }
    } else {
      // Releasing
      appliedGain.current = (
        releaseConst * (appliedGain.current - smoothedTargetGain.current) + 
        smoothedTargetGain.current
      );
    }

    for (let c = 0; c < delayedSignal.length; c ++) {
      let tempOutput = delayedSignal[c][i] * appliedGain.current * gain[i];
      if (tempOutput > threshold) {
        tempOutput = threshold;
      } else if (tempOutput < -threshold) {
        tempOutput = -threshold;
      }
      output[c][i] = tempOutput;
    }
  }
}

this.constructDevice = () => constructPerBufferDevice({
  implementation: Limiter,
  
  inputs: {
    input: stereo('In'),
    detector: stereo('Detector'),
    gain: dbSetting('Gain', { defaultValue: 0, min: -24, max: 24 }),
    release: adsrTimeSetting('Release', 300),
    smoothing: numberSetting('Smoothing', { defaultValue: 0.1, min: 0.001, max: 0.9 })
  },
  constants: {
    makeupGain: booleanConstant('Makeup Gain', false)
  },
  outputs: {
    output: stereo('Out', Math.round(msToSamples(3, sampleRate))),
  },
  hidden: {
    gainReduction: dbSetting('Gain Reduction', {})
  },
  sharedArrays: {
    detectorLevel: 1
  },
  layout: themed(
    DeviceLayoutTheme.Skeuomorphic,
    row([
      meter('input', 4),
      col([
        titled.knob('gain', 'Gain', 100),
        row([
          titled.knob('release', 'Release', 40),
          titled.knob('smoothing', 'Smooth', 40)
        ])
      ]),
      meter('output', 4, true),
    ])
  )
});
