use crate::error::SignalError;
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BiquadCoefficients<T: Numeric = f64> {
feed_forward: [T; 3],
feedback: [T; 2],
dt: T,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum Design {
LowPass,
HighPass,
BandPass,
Notch,
}
impl<T: Numeric> BiquadCoefficients<T> {
pub fn new(feed_forward: [T; 3], feedback: [T; 2], dt: T) -> Result<Self, SignalError> {
for weight in feed_forward.into_iter().chain(feedback) {
if !weight.is_finite() {
return Err(SignalError::NonFinite);
}
}
if !dt.is_finite() {
return Err(SignalError::NonFinite);
}
if dt <= T::ZERO {
return Err(SignalError::NonPositiveTimestep);
}
Ok(Self {
feed_forward,
feedback,
dt,
})
}
pub fn low_pass(cutoff_hz: T, quality_factor: T, dt: T) -> Result<Self, SignalError> {
Self::build(Design::LowPass, cutoff_hz, quality_factor, dt)
}
pub fn high_pass(cutoff_hz: T, quality_factor: T, dt: T) -> Result<Self, SignalError> {
Self::build(Design::HighPass, cutoff_hz, quality_factor, dt)
}
pub fn band_pass(center_hz: T, quality_factor: T, dt: T) -> Result<Self, SignalError> {
Self::build(Design::BandPass, center_hz, quality_factor, dt)
}
pub fn notch(center_hz: T, quality_factor: T, dt: T) -> Result<Self, SignalError> {
Self::build(Design::Notch, center_hz, quality_factor, dt)
}
#[inline]
#[must_use]
pub fn feed_forward(&self) -> [T; 3] {
self.feed_forward
}
#[inline]
#[must_use]
pub fn feedback(&self) -> [T; 2] {
self.feedback
}
#[inline]
#[must_use]
pub fn timestep(&self) -> T {
self.dt
}
#[must_use]
pub fn magnitude_at(&self, frequency_hz: T) -> T {
let (input_real, input_imaginary, output_real, output_imaginary) =
self.response_parts(frequency_hz);
input_real.hypot(input_imaginary) / output_real.hypot(output_imaginary)
}
#[must_use]
pub fn magnitude_in_decibels_at(&self, frequency_hz: T) -> T {
T::from_f64(20.0 / core::f64::consts::LN_10) * self.magnitude_at(frequency_hz).ln()
}
#[must_use]
pub fn phase_at(&self, frequency_hz: T) -> T {
let (input_real, input_imaginary, output_real, output_imaginary) =
self.response_parts(frequency_hz);
input_imaginary.atan2(input_real) - output_imaginary.atan2(output_real)
}
#[must_use]
pub fn delay_at(&self, frequency_hz: T) -> T {
if frequency_hz == T::ZERO {
return T::ZERO;
}
-self.phase_at(frequency_hz) / (T::TWO * T::PI * frequency_hz)
}
#[must_use]
pub fn is_stable(&self) -> bool {
self.feedback[1].abs() < T::ONE && self.feedback[0].abs() < T::ONE + self.feedback[1]
}
#[must_use]
fn response_parts(&self, frequency_hz: T) -> (T, T, T, T) {
let angle = T::TWO * T::PI * frequency_hz * self.dt;
let cosine = angle.cos();
let sine = angle.sin();
let double_cosine = (T::TWO * angle).cos();
let double_sine = (T::TWO * angle).sin();
let [newest_input, previous_input, earlier_input] = self.feed_forward;
let [previous_output, earlier_output] = self.feedback;
(
newest_input + previous_input * cosine + earlier_input * double_cosine,
-(previous_input * sine + earlier_input * double_sine),
T::ONE + previous_output * cosine + earlier_output * double_cosine,
-(previous_output * sine + earlier_output * double_sine),
)
}
fn check_design(frequency_hz: T, quality_factor: T, dt: T) -> Result<(), SignalError> {
if !frequency_hz.is_finite() || !quality_factor.is_finite() || !dt.is_finite() {
return Err(SignalError::NonFinite);
}
if dt <= T::ZERO {
return Err(SignalError::NonPositiveTimestep);
}
if quality_factor <= T::ZERO {
return Err(SignalError::NonPositiveQualityFactor);
}
if frequency_hz <= T::ZERO || frequency_hz * dt >= T::HALF {
return Err(SignalError::FrequencyOutOfRange);
}
Ok(())
}
fn build(
design: Design,
frequency_hz: T,
quality_factor: T,
dt: T,
) -> Result<Self, SignalError> {
Self::check_design(frequency_hz, quality_factor, dt)?;
let angle = T::TWO * T::PI * frequency_hz * dt;
let cosine = angle.cos();
let alpha = match design {
Design::LowPass | Design::HighPass => angle.sin() / (T::TWO * quality_factor),
Design::BandPass | Design::Notch => (angle / (T::TWO * quality_factor)).tan(),
};
let feed_forward = match design {
Design::LowPass => {
let shared = T::ONE - cosine;
[shared * T::HALF, shared, shared * T::HALF]
}
Design::HighPass => {
let shared = T::ONE + cosine;
[shared * T::HALF, -shared, shared * T::HALF]
}
Design::BandPass => [alpha, T::ZERO, -alpha],
Design::Notch => [T::ONE, -(T::TWO * cosine), T::ONE],
};
Ok(Self::from_unnormalized(
feed_forward,
[T::ONE + alpha, -(T::TWO * cosine), T::ONE - alpha],
dt,
))
}
#[must_use]
fn from_unnormalized(feed_forward: [T; 3], feedback: [T; 3], dt: T) -> Self {
let leading = feedback[0];
Self {
feed_forward: [
feed_forward[0] / leading,
feed_forward[1] / leading,
feed_forward[2] / leading,
],
feedback: [feedback[1] / leading, feedback[2] / leading],
dt,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Biquad<T: Numeric = f64> {
coefficients: BiquadCoefficients<T>,
first_state: T,
second_state: T,
last_output: T,
}
impl<T: Numeric> Biquad<T> {
#[must_use]
pub fn new(coefficients: BiquadCoefficients<T>) -> Self {
Self {
coefficients,
first_state: T::ZERO,
second_state: T::ZERO,
last_output: T::ZERO,
}
}
#[inline]
#[must_use]
pub fn filter(&mut self, input: T) -> T {
let feed_forward = self.coefficients.feed_forward();
let feedback = self.coefficients.feedback();
let output = feed_forward[0] * input + self.first_state;
self.first_state = feed_forward[1] * input - feedback[0] * output + self.second_state;
self.second_state = feed_forward[2] * input - feedback[1] * output;
self.last_output = output;
output
}
#[inline]
pub fn set_coefficients(&mut self, coefficients: BiquadCoefficients<T>) {
self.coefficients = coefficients;
}
pub fn settle_to(&mut self, value: T) {
let feed_forward = self.coefficients.feed_forward();
let feedback = self.coefficients.feedback();
let steady = value * (feed_forward[0] + feed_forward[1] + feed_forward[2])
/ (T::ONE + feedback[0] + feedback[1]);
self.second_state = feed_forward[2] * value - feedback[1] * steady;
self.first_state = feed_forward[1] * value - feedback[0] * steady + self.second_state;
self.last_output = steady;
}
#[inline]
pub fn reset(&mut self) {
self.first_state = T::ZERO;
self.second_state = T::ZERO;
self.last_output = T::ZERO;
}
#[inline]
#[must_use]
pub fn coefficients(&self) -> BiquadCoefficients<T> {
self.coefficients
}
#[inline]
#[must_use]
pub fn value(&self) -> T {
self.last_output
}
}