const LOOKAHEAD_MS = 4;
const LOOKAHEAD_SAMPLES = Math.floor(LOOKAHEAD_MS * (sampleRate / 1000));

const Gate = ({ input, output, detector, threshold, floor, attack, release, hold }) => {
  const appliedGainRef = useVal(0);
  const peakHoldRef = useVal(0);

  const delayedSignal = useDelayedStereo(input, LOOKAHEAD_SAMPLES);

  for (let i = 0; i < delayedSignal[0].length; i ++) {
    const thresholdDB = threshold[i];
    const floorDB = floor[i];
    const attackRate = 1/(attack[i] + LOOKAHEAD_MS);
    const releaseRate = 1/release[i];
    const holdRate = 1/hold[i];

    let rawAmplitude = 0;

    for (let c = 0; c < detector.length; c ++) {
      const channelAmplitude = Math.abs(detector[c][i]);
      if (channelAmplitude > rawAmplitude) {
        rawAmplitude = channelAmplitude;
      }
    }

    peakHoldRef.current = Math.max(peakHoldRef.current * (1 - holdRate), rawAmplitude);

    const amplitudeDB = multiplierToDb(peakHoldRef.current)

    let targetGainDB = amplitudeDB < thresholdDB
      ? floorDB
      : 0;

    const gainDelta = targetGainDB < appliedGainRef.current ? releaseRate : attackRate;

    appliedGainRef.current = Math.min(0, ((1 - gainDelta) * appliedGainRef.current) + (gainDelta * targetGainDB));

    const gain = dbToMultiplier(appliedGainRef.current);

    for (let c = 0; c < delayedSignal.length; c ++) {
      output[c][i] = delayedSignal[c][i] * gain;
    }
  }
}

this.constructDevice = () => constructPerBufferDevice({
  implementation: Gate,
  inputs: {
    input: stereo('In'),
    detector: stereo('Detector'),
    threshold: numberSetting('Threshold', { defaultValue: -20, min: -64, max: 0, unit: InterfaceUnit.LiteralDecibels }),
    floor: numberSetting('Floor', { defaultValue: -40, min: -80, max: 0, unit: InterfaceUnit.LiteralDecibels }),
    attack: adsrTimeSetting('Attack', 0.1),
    hold: adsrTimeSetting('Hold', 10),
    release: adsrTimeSetting('Release', 15),
  },
  outputs: {
    output: stereo('Out', LOOKAHEAD_SAMPLES),
  }
});
