///|
/// Compute lowpass biquad filter coefficients (RBJ Audio EQ Cookbook).
pub fn lowpass_coefficients(
  cutoff_hz : Float,
  sample_rate : Int,
  q? : Float = 0.707,
) -> BiquadState {
  let pi = Float::from_double(@math.PI)
  let sr = Float::from_int(sample_rate)
  let two : Float = 2.0
  let w0 = two * pi * cutoff_hz / sr
  let cos_w0 = @math.cosf(w0)
  let sin_w0 = @math.sinf(w0)
  let alpha = sin_w0 / (two * q)
  let one : Float = 1.0
  let b0 = (one - cos_w0) / two
  let b1 = one - cos_w0
  let b2 = (one - cos_w0) / two
  let a0 = one + alpha
  let neg_two : Float = -2.0
  let a1 = neg_two * cos_w0
  let a2 = one - alpha
  BiquadState::{
    b0: b0 / a0,
    b1: b1 / a0,
    b2: b2 / a0,
    a1: a1 / a0,
    a2: a2 / a0,
    z1: 0.0,
    z2: 0.0,
  }
}

///|
/// Compute highpass biquad filter coefficients (RBJ Audio EQ Cookbook).
pub fn highpass_coefficients(
  cutoff_hz : Float,
  sample_rate : Int,
  q? : Float = 0.707,
) -> BiquadState {
  let pi = Float::from_double(@math.PI)
  let sr = Float::from_int(sample_rate)
  let two : Float = 2.0
  let w0 = two * pi * cutoff_hz / sr
  let cos_w0 = @math.cosf(w0)
  let sin_w0 = @math.sinf(w0)
  let alpha = sin_w0 / (two * q)
  let one : Float = 1.0
  let b0 = (one + cos_w0) / two
  let neg_one_plus_cos = -(one + cos_w0)
  let b1 = neg_one_plus_cos
  let b2 = (one + cos_w0) / two
  let a0 = one + alpha
  let neg_two : Float = -2.0
  let a1 = neg_two * cos_w0
  let a2 = one - alpha
  BiquadState::{
    b0: b0 / a0,
    b1: b1 / a0,
    b2: b2 / a0,
    a1: a1 / a0,
    a2: a2 / a0,
    z1: 0.0,
    z2: 0.0,
  }
}

///|
/// Process one sample through a biquad filter (Direct Form II Transposed).
pub fn biquad_process(state : BiquadState, input : Float) -> Float {
  let output = state.b0 * input + state.z1
  state.z1 = state.b1 * input - state.a1 * output + state.z2
  state.z2 = state.b2 * input - state.a2 * output
  output
}

///|
/// Create a new delay effect.
pub fn new_delay(
  delay_ms : Float,
  sample_rate : Int,
  feedback? : Float = 0.3,
  mix? : Float = 0.5,
) -> DelayState {
  let sr = Float::from_int(sample_rate)
  let thousand : Float = 1000.0
  let delay_samples = (delay_ms / thousand * sr).to_int()
  let buf_size = if delay_samples > 0 { delay_samples } else { 1 }
  DelayState::{
    buffer: FixedArray::make(buf_size, (0.0 : Float)),
    write_pos: 0,
    delay_samples: buf_size,
    feedback,
    mix,
  }
}

///|
/// Process one sample through the delay effect.
/// Ring buffer: write_pos always points to the oldest sample (delay_samples ago).
pub fn delay_process(state : DelayState, input : Float) -> Float {
  let delayed = state.buffer[state.write_pos]
  state.buffer[state.write_pos] = input + delayed * state.feedback
  state.write_pos = (state.write_pos + 1) % state.delay_samples
  let one : Float = 1.0
  input * (one - state.mix) + delayed * state.mix
}

///|
/// Process one sample through an effect node.
pub fn effect_process(node : EffectNode, sample : Float) -> Float {
  match node {
    Lowpass(state) => biquad_process(state, sample)
    Highpass(state) => biquad_process(state, sample)
    Delay(state) => delay_process(state, sample)
  }
}

///|
/// Process one sample through an entire effects chain.
pub fn effects_chain_process(
  effects : Array[EffectNode],
  sample : Float,
) -> Float {
  let mut result = sample
  for effect in effects {
    result = effect_process(effect, result)
  }
  result
}