///|
/// Stateful one-pole smoother for click-free control changes.
pub struct ParamSmoother {
  priv mut current : Double
  priv mut target : Double
  priv mut coeff : Double
}

///|
/// Create a parameter smoother from an initial value, smoothing time, and
/// explicit sample rate.
#alias(new)
pub fn ParamSmoother::ParamSmoother(
  initial : Double,
  smoothing_ms : Double,
  sample_rate : Double,
) -> ParamSmoother {
  let normalized = normalize_param_value(initial)
  {
    current: normalized,
    target: normalized,
    coeff: smoothing_coeff(smoothing_ms, sample_rate),
  }
}

///|
/// Create a parameter smoother using the sample rate stored in a DSP context.
pub fn ParamSmoother::from_context(
  initial : Double,
  smoothing_ms : Double,
  context : DspContext,
) -> ParamSmoother {
  ParamSmoother::new(initial, smoothing_ms, context.sample_rate())
}

///|
/// Return the current smoothed value.
pub fn ParamSmoother::current(self : ParamSmoother) -> Double {
  self.current
}

///|
/// Return the current target value.
pub fn ParamSmoother::target(self : ParamSmoother) -> Double {
  self.target
}

///|
/// Immediately snap both the current and target value to a new point.
pub fn ParamSmoother::reset(self : ParamSmoother, value : Double) -> Unit {
  let normalized = normalize_param_value(value)
  self.current = normalized
  self.target = normalized
}

///|
/// Update the smoothing time using an explicit sample rate.
pub fn ParamSmoother::set_smoothing_time(
  self : ParamSmoother,
  smoothing_ms : Double,
  sample_rate : Double,
) -> Unit {
  self.coeff = smoothing_coeff(smoothing_ms, sample_rate)
}

///|
/// Update the smoothing time using the sample rate stored in a DSP context.
pub fn ParamSmoother::set_smoothing_time_from_context(
  self : ParamSmoother,
  smoothing_ms : Double,
  context : DspContext,
) -> Unit {
  self.set_smoothing_time(smoothing_ms, context.sample_rate())
}

///|
/// Set a new target value. Invalid inputs are ignored to keep the state
/// deterministic inside the audio path.
pub fn ParamSmoother::set_target(self : ParamSmoother, value : Double) -> Unit {
  if is_finite(value) {
    self.target = value
  }
}

///|
/// Advance the smoother by one sample.
pub fn ParamSmoother::tick(self : ParamSmoother) -> Double {
  if self.current == self.target {
    return self.current
  }

  self.current = self.target + self.coeff * (self.current - self.target)
  self.current
}

///|
fn smoothing_coeff(smoothing_ms : Double, sample_rate : Double) -> Double {
  if !is_finite(smoothing_ms) ||
    !is_finite(sample_rate) ||
    smoothing_ms <= 0.0 ||
    sample_rate <= 0.0 {
    0.0
  } else {
    @math.exp(-1000.0 / (smoothing_ms * sample_rate))
  }
}

///|
fn normalize_param_value(value : Double) -> Double {
  if is_finite(value) {
    value
  } else {
    0.0
  }
}