///|
/// Coefficients of a second-order IIR filter in direct form II:
/// `y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]`.
pub struct IirCoeffs2 {
  b0 : Double
  b1 : Double
  b2 : Double
  a1 : Double
  a2 : Double
}

///|
/// Designs a low-pass FIR filter with the window method.
///
/// Parameters:
///
/// * `cutoff_norm` : normalized cutoff frequency in `(0, 0.5)`, i.e. the
///   ratio `fc / fs`. Frequencies above this are attenuated.
/// * `taps` : number of filter taps (odd values give a symmetric, linear-phase
///   filter).
/// * `window_type` : window used to truncate the ideal impulse response.
///
/// Returns normalized coefficients whose DC gain is exactly 1.
pub fn fir_lowpass_coeffs(
  cutoff_norm : Double,
  taps : Int32,
  window_type : WindowType,
) -> Array[Double] {
  if taps <= 1 {
    return [1.0]
  }
  let center = (taps - 1) / 2
  let w = 2.0 * cutoff_norm
  let ideal = Array::makei(taps, fn(i) {
    let k = (i - center).to_double()
    if k == 0.0 {
      w
    } else {
      let arg = @math.PI * w * k
      w * @math.sin(arg) / arg
    }
  })
  let win = window(window_type, taps)
  let mut sum = 0.0
  for i in 0.. Array[Double] {
  convolve(x, h)
}

///|
/// First-order IIR low-pass filter.
///
/// `y[n] = alpha * x[n] + (1 - alpha) * y[n-1]`. Use
/// [`alpha_from_cutoff`](https://mooncakes.io/docs/2227057182/moonbit-dsp/latest/#~2227057182%2Fmoonbit-dsp.alpha_from_cutoff)
/// to pick `alpha` from a normalized cutoff frequency.
pub fn iir_lowpass_1(x : Array[Double], alpha : Double) -> Array[Double] {
  let n = x.length()
  let y = Array::make(n, 0.0)
  let mut prev = 0.0
  for i in 0.. Double {
  1.0 - @math.exp(-2.0 * @math.PI * cutoff_norm)
}

///|
/// Designs a second-order Butterworth low-pass filter with the bilinear
/// transform.
///
/// `cutoff_norm` is the normalized cutoff `fc/fs` in `(0, 0.5)`.
pub fn butterworth_lowpass_2(cutoff_norm : Double) -> IirCoeffs2 {
  let wc = 2.0 * @math.PI * cutoff_norm
  let k = @math.tan(wc / 2.0)
  let sqrt2 = 2.0.sqrt()
  let den = 1.0 + sqrt2 * k + k * k
  let b0 = k * k / den
  {
    b0,
    b1: 2.0 * b0,
    b2: b0,
    a1: 2.0 * (k * k - 1.0) / den,
    a2: (1.0 - sqrt2 * k + k * k) / den,
  }
}

///|
/// Applies a second-order IIR filter in direct form II.
pub fn iir_filter_2(x : Array[Double], c : IirCoeffs2) -> Array[Double] {
  let n = x.length()
  let y = Array::make(n, 0.0)
  let mut x1 = 0.0
  let mut x2 = 0.0
  let mut y1 = 0.0
  let mut y2 = 0.0
  for i in 0..