///|
const REVERB_REFERENCE_SAMPLE_RATE : Double = 44100.0

///|
const REVERB_INPUT_GAIN : Double = 0.015

///|
const REVERB_OUTPUT_GAIN : Double = 0.12

///|
const REVERB_DEFAULT_SAMPLE_RATE : Double = 48000.0

///|
const REVERB_DEFAULT_DECAY_SECONDS : Double = 2.4

///|
const REVERB_DEFAULT_DAMPING : Double = 0.35

///|
priv struct ReverbComb {
  buffer : FixedArray[Double]
  mut position : Int
  feedback : Double
  damping : Double
  mut filter_state : Double
}

///|
fn ReverbComb::ReverbComb(
  size : Int,
  sample_rate : Double,
  decay_seconds : Double,
  damping : Double,
) -> ReverbComb {
  let delay_seconds = size.to_double() / sample_rate
  let feedback = @math.pow(0.001, delay_seconds / decay_seconds).clamp(
    min=0.0,
    max=0.999,
  )
  {
    buffer: FixedArray::make(size, 0.0),
    position: 0,
    feedback,
    damping,
    filter_state: 0.0,
  }
}

///|
fn reverb_finite_state(value : Double) -> Double {
  if !is_finite(value) || value.abs() < 1.0e-30 {
    0.0
  } else {
    value
  }
}

///|
fn ReverbComb::reset(self : ReverbComb) -> Unit {
  self.buffer.fill(0.0)
  self.position = 0
  self.filter_state = 0.0
}

///|
fn ReverbComb::tick(self : ReverbComb, input : Double) -> Double {
  let delayed = reverb_finite_state(self.buffer[self.position])
  let filtered = reverb_finite_state(
    delayed * (1.0 - self.damping) + self.filter_state * self.damping,
  )
  self.filter_state = filtered
  self.buffer[self.position] = reverb_finite_state(
    input + filtered * self.feedback,
  )
  self.position += 1
  if self.position == self.buffer.length() {
    self.position = 0
  }
  delayed
}

///|
priv struct ReverbAllpass {
  buffer : FixedArray[Double]
  mut position : Int
}

///|
fn ReverbAllpass::ReverbAllpass(size : Int) -> ReverbAllpass {
  { buffer: FixedArray::make(size, 0.0), position: 0, }
}

///|
fn ReverbAllpass::reset(self : ReverbAllpass) -> Unit {
  self.buffer.fill(0.0)
  self.position = 0
}

///|
fn ReverbAllpass::tick(self : ReverbAllpass, input : Double) -> Double {
  let delayed = reverb_finite_state(self.buffer[self.position])
  let output = reverb_finite_state(delayed - input)
  self.buffer[self.position] = reverb_finite_state(input + delayed * 0.5)
  self.position += 1
  if self.position == self.buffer.length() {
    self.position = 0
  }
  output
}

///|
/// Wet-only stereo room reverb with fixed delay lengths and preallocated state.
///
/// The network is eight parallel damped feedback combs followed by four serial
/// allpasses per channel. Delay lengths use the published Freeverb tunings,
/// scaled once for the requested sample rate. `decay_seconds` is the low-
/// frequency T60; `damping` is a normalized high-frequency loss coefficient.
pub struct StereoReverb {
  priv left_combs : Array[ReverbComb]
  priv right_combs : Array[ReverbComb]
  priv left_allpasses : Array[ReverbAllpass]
  priv right_allpasses : Array[ReverbAllpass]
}

///|
fn reverb_scaled_delay(base : Int, sample_rate : Double) -> Int {
  let scaled = (base.to_double() * sample_rate / REVERB_REFERENCE_SAMPLE_RATE)
    .round()
    .to_int()
  if scaled > 0 {
    scaled
  } else {
    1
  }
}

///|
pub fn StereoReverb::StereoReverb(
  sample_rate : Double,
  decay_seconds? : Double = REVERB_DEFAULT_DECAY_SECONDS,
  damping? : Double = REVERB_DEFAULT_DAMPING,
) -> StereoReverb {
  let safe_rate = if is_finite_positive(sample_rate) {
    sample_rate
  } else {
    REVERB_DEFAULT_SAMPLE_RATE
  }
  let safe_decay = if !is_finite(decay_seconds) {
    REVERB_DEFAULT_DECAY_SECONDS
  } else {
    decay_seconds.clamp(min=0.2, max=20.0)
  }
  let safe_damping = if !is_finite(damping) {
    REVERB_DEFAULT_DAMPING
  } else {
    damping.clamp(min=0.0, max=0.95)
  }
  let comb_tunings = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617]
  let allpass_tunings = [556, 441, 341, 225]
  let stereo_spread = reverb_scaled_delay(23, safe_rate)
  {
    left_combs: comb_tunings.map(n => {
      ReverbComb(
        reverb_scaled_delay(n, safe_rate),
        safe_rate,
        safe_decay,
        safe_damping,
      )
    }),
    right_combs: comb_tunings.map(n => {
      ReverbComb(
        reverb_scaled_delay(n, safe_rate) + stereo_spread,
        safe_rate,
        safe_decay,
        safe_damping,
      )
    }),
    left_allpasses: allpass_tunings.map(n => {
      ReverbAllpass(reverb_scaled_delay(n, safe_rate))
    }),
    right_allpasses: allpass_tunings.map(n => {
      ReverbAllpass(reverb_scaled_delay(n, safe_rate) + stereo_spread)
    }),
  }
}

///|
/// Clear the complete late-field state. Ordinary zero-input processing should
/// be used to preserve and drain a tail; reset is for an explicit hard stop.
pub fn StereoReverb::reset(self : StereoReverb) -> Unit {
  for comb in self.left_combs {
    comb.reset()
  }
  for comb in self.right_combs {
    comb.reset()
  }
  for allpass in self.left_allpasses {
    allpass.reset()
  }
  for allpass in self.right_allpasses {
    allpass.reset()
  }
}

///|
fn reverb_channel_tick(
  combs : Array[ReverbComb],
  allpasses : Array[ReverbAllpass],
  input : Double,
) -> Double {
  let mut output = 0.0
  for comb in combs {
    output += comb.tick(input)
  }
  for allpass in allpasses {
    output = allpass.tick(output)
  }
  reverb_finite_state(output * REVERB_OUTPUT_GAIN)
}

///|
/// Render wet stereo only. Inputs and outputs may be reused across calls; no
/// allocation occurs in this audio-rate path.
pub fn StereoReverb::process(
  self : StereoReverb,
  context : DspContext,
  left_input : AudioBuffer,
  right_input : AudioBuffer,
  left_output : AudioBuffer,
  right_output : AudioBuffer,
) -> Unit {
  let mut count = effective_sample_count(context, left_input)
  if right_input.length() < count {
    count = right_input.length()
  }
  if left_output.length() < count {
    count = left_output.length()
  }
  if right_output.length() < count {
    count = right_output.length()
  }
  for i in 0..