const Compressor = ({ input, output, detector, threshold, ratio: inverseRatio, attack, release, gain: gainDB, gainReduction, detectorLevel, makeupGain }) => {
  const appliedGainRef = useVal(0);

  for (let i = 0; i < input[0].length; i ++) {
    const thresholdDB = threshold[i];
    const ratio = 1/inverseRatio[i];

    const makeupGainAmount = makeupGain ? (-thresholdDB) * ((1 - ratio) / 2) : 0;

    let rawAmplitude = 0;

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

    const amplitudeDB = multiplierToDb(rawAmplitude)

    const attackRate = 1/attack[i];
    const releaseRate = 1/release[i];

    const targetGainDB = amplitudeDB > thresholdDB
      ? (thresholdDB - amplitudeDB) * (1 - ratio)
      : 0;

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

    appliedGainRef.current = ((1 - gainDelta) * appliedGainRef.current) + (gainDelta * targetGainDB);
    gainReduction[i] = appliedGainRef.current;

    const gain = dbToMultiplier(appliedGainRef.current + gainDB[i] + makeupGainAmount);

    detectorLevel[0] = Math.max(
      rawAmplitude,
      detectorLevel[0]
    ) * (1 - (7/sampleRate));

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

const CornerUI = (ctx, settings) => {
  const width = ctx.canvas.width;
  const height = ctx.canvas.height;

  ctx.fillStyle = gray100;
  roundedRect(ctx, 1, 1, ctx.canvas.width - 2, ctx.canvas.height - 2, 4, false, true);
  ctx.clip();
  ctx.fill();

  const threshold = settings.threshold;
  const thresholdProportion = 1 - ((threshold - 12) / (-64 - 12));

  const ratio = (settings.ratio);

  const thresholdY = height * (1 - thresholdProportion);
  const thresholdX = width * (thresholdProportion);

  const ratioEndY = (thresholdY * (1 - (1/ratio)));

  ctx.strokeStyle = gray300;

  ctx.beginPath();
  for (let i = 12; i >= -64; i -= 12) {
    const y = ((i - 12) / (-64 - 12)) * height;
    ctx.moveTo(0, y);
    ctx.lineTo(width, y);

    const x = (1 - ((i - 12) / (-64 - 12))) * width;
    ctx.moveTo(x, 0);
    ctx.lineTo(x, height);
  }
  ctx.stroke();

  ctx.strokeStyle = gray600;

  ctx.lineWidth = 2;
  ctx.fillStyle = '#fff';

  ctx.beginPath();
  ctx.moveTo(0, height);
  ctx.lineTo(thresholdX, thresholdY);
  ctx.lineTo(width, ratioEndY);
  ctx.stroke();

  const detectorLevel = 1 - ((multiplierToDb(settings.detectorLevel) - 12) / (-64 - 12));
  const outputLevel = detectorLevel > thresholdProportion
    ? thresholdProportion + ((detectorLevel - thresholdProportion) / ratio)
    : detectorLevel;

  ctx.beginPath();
  ctx.arc(
    detectorLevel * width,
    (1 - outputLevel) * height,
    6,
    0,
    2 * Math.PI
  );
  ctx.fill();
}

this.constructDevice = () => constructPerBufferDevice({
  implementation: Compressor,
  inputs: {
    input: stereo('In'),
    detector: stereo('Detector'),
    gain: numberSetting('Gain', { defaultValue: 0, min: -12, max: 12 }),
    threshold: numberSetting('Threshold', { defaultValue: -20, min: -64, max: 0, unit: InterfaceUnit.LiteralDecibels }),
    ratio: numberSetting('Ratio', { defaultValue: 8, min: 1, max: 64, curve: Curve.Log2 }),
    attack: adsrTimeSetting('Attack', 1),
    release: adsrTimeSetting('Release', 30),
  },
  constants: {
    makeupGain: booleanConstant('Makeup Gain', false)
  },
  outputs: {
    output: stereo('Out'),
  },
  hidden: {
    gainReduction: dbSetting('Gain Reduction', {})
  },
  sharedArrays: {
    detectorLevel: 1
  },
  layout: themed(
    DeviceLayoutTheme.Skeuomorphic,
    row([
      meter('input', 4),
      col([
        titled.knob('threshold'),
        titled.knob('ratio'),
        titled.knob('gain')
      ]),
      spacer(.5),
      canvas(
        'corner',
        CornerUI,
        200,
        200
      ),
      spacer(.5),
      col([
        titled.knob('attack'),
        titled.knob('release'),
        titled.checkbox('makeupGain', 'Makeup')
      ]),
      meter('output', 4, true),
    ])
  )
});
