// Copyright (c) 2021 Weird Constructor // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. // // The implementation of this Biquad Filter has been adapted from // SamiPerttu, Copyright (c) 2020, under the MIT License. // See also: https://github.com/SamiPerttu/fundsp/blob/master/src/filter.rs // // You will find a float type agnostic version in SamiPerttu's code. // I converted this to pure f32 for no good reason, other than making // the code more readable (for me). use std::f32::consts::*; #[derive(Copy, Clone, Debug, Default)] pub struct BiquadCoefs { pub a1: f32, pub a2: f32, pub b0: f32, pub b1: f32, pub b2: f32, } // TODO: // https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp#L339 // more coeffs from there ^^^^^^^^^^^^^ ? impl BiquadCoefs { /// Returns settings for a Butterworth lowpass filter. /// Cutoff is the -3 dB point of the filter in Hz. #[inline] pub fn butter_lowpass(sample_rate: f32, cutoff: f32) -> BiquadCoefs { let f = (cutoff * PI / sample_rate).tan(); let a0r = 1.0 / (1.0 + SQRT_2 * f + f * f); let a1 = (2.0 * f * f - 2.0) * a0r; let a2 = (1.0 - SQRT_2 * f + f * f) * a0r; let b0 = f * f * a0r; let b1 = 2.0 * b0; let b2 = b0; BiquadCoefs { a1, a2, b0, b1, b2 } } /// Returns settings for a constant-gain bandpass resonator. /// The center frequency is given in Hz. /// Bandwidth is the difference in Hz between -3 dB points of the filter response. /// The overall gain of the filter is independent of bandwidth. pub fn resonator(sample_rate: f32, center: f32, bandwidth: f32) -> BiquadCoefs { let r = (-PI * bandwidth / sample_rate).exp(); let a1 = -2.0 * r * (TAU * center / sample_rate).cos(); let a2 = r * r; let b0 = (1.0 - r * r).sqrt() * 0.5; let b1 = 0.0; let b2 = -b0; BiquadCoefs { a1, a2, b0, b1, b2 } } // /// Frequency response at frequency `omega` expressed as fraction of sampling rate. // pub fn response(&self, omega: f64) -> Complex64 { // let z1 = Complex64::from_polar(1.0, -TAU * omega); // let z2 = Complex64::from_polar(1.0, -2.0 * TAU * omega); // (re(self.b0) + re(self.b1) * z1 + re(self.b2) * z2) // / (re(1.0) + re(self.a1) * z1 + re(self.a2) * z2) // } } /// 2nd order IIR filter implemented in normalized Direct Form I. #[derive(Debug, Copy, Clone, Default)] pub struct Biquad { coefs: BiquadCoefs, x1: f32, x2: f32, y1: f32, y2: f32, } impl Biquad { pub fn new() -> Self { Default::default() } #[inline] pub fn coefs(&self) -> &BiquadCoefs { &self.coefs } #[inline] pub fn set_coefs(&mut self, coefs: BiquadCoefs) { self.coefs = coefs; } pub fn reset(&mut self) { self.x1 = 0.0; self.x2 = 0.0; self.y1 = 0.0; self.y2 = 0.0; } #[inline] pub fn tick(&mut self, input: f32) -> f32 { let x0 = input; let y0 = self.coefs.b0 * x0 + self.coefs.b1 * self.x1 + self.coefs.b2 * self.x2 - self.coefs.a1 * self.y1 - self.coefs.a2 * self.y2; self.x2 = self.x1; self.x1 = x0; self.y2 = self.y1; self.y1 = y0; y0 // Transposed Direct Form II would be: // y0 = b0 * x0 + s1 // s1 = s2 + b1 * x0 - a1 * y0 // s2 = b2 * x0 - a2 * y0 } } #[derive(Copy, Clone)] pub struct ButterLowpass { biquad: Biquad, sample_rate: f32, cutoff: f32, } impl ButterLowpass { pub fn new(sample_rate: f32, cutoff: f32) -> Self { let mut this = ButterLowpass { biquad: Biquad::new(), sample_rate, cutoff: 0.0, }; this.set_cutoff(cutoff); this } pub fn set_cutoff(&mut self, cutoff: f32) { self.biquad .set_coefs(BiquadCoefs::butter_lowpass(self.sample_rate, cutoff)); self.cutoff = cutoff; } fn set_sample_rate(&mut self, srate: f32) { self.sample_rate = srate; self.reset(); self.biquad.reset(); self.set_cutoff(self.cutoff); } fn reset(&mut self) { self.biquad.reset(); self.set_cutoff(self.cutoff); } #[inline] fn tick(&mut self, input: f32) -> f32 { self.biquad.tick(input) } } // Loosely adapted from https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp // Copyright (c) 2019-2020 Andrew Belt and Befaco contributors // Under GPLv-3.0-or-later // // Which was originally taken from https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp // Copyright (c) 2020 jatinchowdhury18 /// Implements oversampling with a ratio of 4 and a 4 times cascade /// of Butterworth lowpass filters. #[derive(Debug, Copy, Clone, Default)] pub struct Oversampling4x4 { filters: [Biquad; 4], buffer: [f32; 4], } impl Oversampling4x4 { pub fn new() -> Self { let mut this = Self { filters: [Biquad::new(); 4], buffer: [0.0; 4], }; this.set_sample_rate(44100.0); this } pub fn reset(&mut self) { self.buffer = [0.0; 4]; for filt in &mut self.filters { filt.reset(); } } pub fn set_sample_rate(&mut self, srate: f32) { let cutoff = 0.98 * (srate / 2.0); for filt in &mut self.filters { filt.set_coefs(BiquadCoefs::butter_lowpass(srate, cutoff)); } } #[inline] pub fn upsample(&mut self, v: f32) { self.buffer[0] = 4.0 * v; self.buffer[1] = 0.0; self.buffer[2] = 0.0; self.buffer[3] = 0.0; for s in &mut self.buffer { for filt in &mut self.filters { *s = filt.tick(*s); } } } #[inline] pub fn resample_buffer(&mut self) -> &mut [f32; 4] { &mut self.buffer } #[inline] pub fn downsample(&mut self) -> f32 { let mut ret = 0.0; for s in &mut self.buffer { ret = *s; for filt in &mut self.filters { ret = filt.tick(ret); } } ret } }