use crate::sim::components::CircuitComponent;
use crate::sim::wdf::adaptors::SeriesAdaptor;
use crate::sim::wdf::components::{WdfCapacitor, WdfComponent, WdfInductor, WdfResistor};
pub struct RcLowPass {
adaptor: SeriesAdaptor<WdfResistor, WdfCapacitor>,
sample_rate: f32,
}
impl RcLowPass {
pub fn new(resistance: f32, capacitance: f32) -> Self {
let res = WdfResistor::new(resistance);
let cap = WdfCapacitor::new(capacitance);
let adaptor = SeriesAdaptor::new(res, cap);
Self {
adaptor,
sample_rate: 44100.0,
}
}
pub fn from_cutoff(cutoff_hz: f32, resistance: f32) -> Self {
let c = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz * resistance);
Self::new(resistance, c)
}
pub fn cutoff_hz(&self) -> f32 {
let r = self.adaptor.left.resistance();
let c = self.adaptor.right.capacitance();
1.0 / (2.0 * std::f32::consts::PI * r * c)
}
}
impl CircuitComponent for RcLowPass {
fn prepare(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.adaptor.right.set_sample_rate(sample_rate);
self.adaptor.right.reset();
self.adaptor.update_impedance();
}
fn process_block(&mut self, input: &[f32], output: &mut [f32]) {
let len = input.len().min(output.len());
for i in 0..len {
let b_tree = self.adaptor.reflected_cached();
let a_root = input[i] - b_tree;
let diff = a_root - (self.adaptor.b_left + self.adaptor.b_right);
let a_c = self.adaptor.b_right + (1.0 - self.adaptor.gamma) * diff;
output[i] = a_c + self.adaptor.b_right;
self.adaptor.incident(a_root);
}
}
fn update_parameters(&mut self) {}
}
pub struct RcHighPass {
adaptor: SeriesAdaptor<WdfCapacitor, WdfResistor>,
sample_rate: f32,
}
impl RcHighPass {
pub fn new(resistance: f32, capacitance: f32) -> Self {
let cap = WdfCapacitor::new(capacitance);
let res = WdfResistor::new(resistance);
let adaptor = SeriesAdaptor::new(cap, res);
Self {
adaptor,
sample_rate: 44100.0,
}
}
pub fn from_cutoff(cutoff_hz: f32, resistance: f32) -> Self {
let c = 1.0 / (2.0 * std::f32::consts::PI * cutoff_hz * resistance);
Self::new(resistance, c)
}
pub fn cutoff_hz(&self) -> f32 {
let r = self.adaptor.right.resistance();
let c = self.adaptor.left.capacitance();
1.0 / (2.0 * std::f32::consts::PI * r * c)
}
}
impl CircuitComponent for RcHighPass {
fn prepare(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.adaptor.left.set_sample_rate(sample_rate);
self.adaptor.left.reset();
self.adaptor.update_impedance();
}
fn process_block(&mut self, input: &[f32], output: &mut [f32]) {
let len = input.len().min(output.len());
for i in 0..len {
let b_tree = self.adaptor.reflected_cached();
let a_root = input[i] - b_tree;
let diff = a_root - (self.adaptor.b_left + self.adaptor.b_right);
let a_r = self.adaptor.b_right + (1.0 - self.adaptor.gamma) * diff;
output[i] = a_r + self.adaptor.b_right;
self.adaptor.incident(a_root);
}
}
fn update_parameters(&mut self) {}
}
pub struct RlcBandPass {
adaptor: SeriesAdaptor<SeriesAdaptor<WdfInductor, WdfCapacitor>, WdfResistor>,
sample_rate: f32,
}
impl RlcBandPass {
pub fn new(resistance: f32, inductance: f32, capacitance: f32) -> Self {
let ind = WdfInductor::new(inductance);
let cap = WdfCapacitor::new(capacitance);
let inner = SeriesAdaptor::new(ind, cap);
let res = WdfResistor::new(resistance);
let adaptor = SeriesAdaptor::new(inner, res);
Self {
adaptor,
sample_rate: 44100.0,
}
}
pub fn from_frequency(center_hz: f32, q: f32, resistance: f32) -> Self {
let omega0 = 2.0 * std::f32::consts::PI * center_hz;
let l = q * resistance / omega0;
let c = 1.0 / (q * resistance * omega0);
Self::new(resistance, l, c)
}
pub fn resonant_hz(&self) -> f32 {
let l = self.adaptor.left.left.inductance();
let c = self.adaptor.left.right.capacitance();
1.0 / (2.0 * std::f32::consts::PI * (l * c).sqrt())
}
}
impl CircuitComponent for RlcBandPass {
fn prepare(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.adaptor.left.left.set_sample_rate(sample_rate);
self.adaptor.left.right.set_sample_rate(sample_rate);
self.adaptor.left.left.reset();
self.adaptor.left.right.reset();
self.adaptor.left.update_impedance();
self.adaptor.update_impedance();
}
fn process_block(&mut self, input: &[f32], output: &mut [f32]) {
let len = input.len().min(output.len());
for i in 0..len {
let b_tree = self.adaptor.reflected_cached();
let a_root = input[i] - b_tree;
let diff = a_root - (self.adaptor.b_left + self.adaptor.b_right);
let a_r = self.adaptor.b_right + (1.0 - self.adaptor.gamma) * diff;
output[i] = a_r + self.adaptor.b_right;
self.adaptor.incident(a_root);
}
}
fn update_parameters(&mut self) {}
}
pub struct RlcLowPass {
adaptor: SeriesAdaptor<SeriesAdaptor<WdfResistor, WdfInductor>, WdfCapacitor>,
sample_rate: f32,
}
impl RlcLowPass {
pub fn new(resistance: f32, inductance: f32, capacitance: f32) -> Self {
let res = WdfResistor::new(resistance);
let ind = WdfInductor::new(inductance);
let inner = SeriesAdaptor::new(res, ind);
let cap = WdfCapacitor::new(capacitance);
let adaptor = SeriesAdaptor::new(inner, cap);
Self {
adaptor,
sample_rate: 44100.0,
}
}
pub fn from_cutoff(cutoff_hz: f32, zeta: f32, resistance: f32) -> Self {
let omega0 = 2.0 * std::f32::consts::PI * cutoff_hz;
let l = resistance / (2.0 * zeta * omega0);
let c = 2.0 * zeta / (resistance * omega0);
Self::new(resistance, l, c)
}
pub fn resonant_hz(&self) -> f32 {
let l = self.adaptor.left.right.inductance();
let c = self.adaptor.right.capacitance();
1.0 / (2.0 * std::f32::consts::PI * (l * c).sqrt())
}
}
impl CircuitComponent for RlcLowPass {
fn prepare(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.adaptor.left.right.set_sample_rate(sample_rate);
self.adaptor.right.set_sample_rate(sample_rate);
self.adaptor.left.right.reset();
self.adaptor.right.reset();
self.adaptor.left.update_impedance();
self.adaptor.update_impedance();
}
fn process_block(&mut self, input: &[f32], output: &mut [f32]) {
let len = input.len().min(output.len());
for i in 0..len {
let b_tree = self.adaptor.reflected_cached();
let a_root = input[i] - b_tree;
let diff = a_root - (self.adaptor.b_left + self.adaptor.b_right);
let a_c = self.adaptor.b_right + (1.0 - self.adaptor.gamma) * diff;
output[i] = a_c + self.adaptor.b_right;
self.adaptor.incident(a_root);
}
}
fn update_parameters(&mut self) {}
}
pub enum RlcFilter {
RcLow(RcLowPass),
RcHigh(RcHighPass),
RlcBand(RlcBandPass),
RlcLow(RlcLowPass),
}
impl CircuitComponent for RlcFilter {
fn prepare(&mut self, sample_rate: f32) {
match self {
RlcFilter::RcLow(f) => f.prepare(sample_rate),
RlcFilter::RcHigh(f) => f.prepare(sample_rate),
RlcFilter::RlcBand(f) => f.prepare(sample_rate),
RlcFilter::RlcLow(f) => f.prepare(sample_rate),
}
}
fn process_block(&mut self, input: &[f32], output: &mut [f32]) {
match self {
RlcFilter::RcLow(f) => f.process_block(input, output),
RlcFilter::RcHigh(f) => f.process_block(input, output),
RlcFilter::RlcBand(f) => f.process_block(input, output),
RlcFilter::RlcLow(f) => f.process_block(input, output),
}
}
fn update_parameters(&mut self) {
match self {
RlcFilter::RcLow(f) => f.update_parameters(),
RlcFilter::RcHigh(f) => f.update_parameters(),
RlcFilter::RlcBand(f) => f.update_parameters(),
RlcFilter::RlcLow(f) => f.update_parameters(),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::needless_range_loop)]
use super::*;
#[test]
fn test_rc_lowpass_dc_passthrough() {
let mut lpf = RcLowPass::new(1000.0, 100e-9);
lpf.prepare(192000.0);
let n = 19200; let input = vec![1.0f32; n];
let mut output = vec![0.0f32; n];
lpf.process_block(&input, &mut output);
let last = output[n - 1];
assert!(
(last - 1.0).abs() < 0.05,
"DC should pass through LPF: last={last}"
);
}
#[test]
fn test_rc_lowpass_attenuates_high_freq() {
let r = 1000.0;
let c = 100e-9;
let fc = 1.0 / (2.0 * std::f32::consts::PI * r * c); let fs = 192000.0;
let test_freq = fc * 10.0;
let mut lpf = RcLowPass::new(r, c);
lpf.prepare(fs);
let n = (8.0 * fs / test_freq) as usize; let mut input = vec![0.0f32; n];
let mut output = vec![0.0f32; n];
for i in 0..n {
input[i] = (2.0 * std::f32::consts::PI * test_freq * i as f32 / fs).sin();
}
lpf.process_block(&input, &mut output);
let peak_out: f32 = output[n / 2..]
.iter()
.map(|x| x.abs())
.fold(0.0f32, f32::max);
assert!(
peak_out < 0.2,
"High freq should be attenuated: peak_out={peak_out}"
);
}
#[test]
fn test_rc_highpass_blocks_dc() {
let mut hpf = RcHighPass::new(10000.0, 1e-6);
hpf.prepare(44100.0);
let n = 44100; let input = vec![1.0f32; n];
let mut output = vec![0.0f32; n];
hpf.process_block(&input, &mut output);
let last_avg: f32 = output[n - 1000..].iter().sum::<f32>() / 1000.0;
assert!(
last_avg.abs() < 0.01,
"DC should be blocked by HPF: last_avg={last_avg}"
);
}
#[test]
fn test_rc_highpass_passes_high_freq() {
let r = 10000.0;
let c = 1e-6;
let _fc = 1.0 / (2.0 * std::f32::consts::PI * r * c); let fs = 44100.0;
let mut hpf = RcHighPass::new(r, c);
hpf.prepare(fs);
let test_freq = 1000.0;
let n = (4.0 * fs / test_freq) as usize;
let mut input = vec![0.0f32; n];
let mut output = vec![0.0f32; n];
for i in 0..n {
input[i] = (2.0 * std::f32::consts::PI * test_freq * i as f32 / fs).sin();
}
hpf.process_block(&input, &mut output);
let peak_out: f32 = output[n / 2..]
.iter()
.map(|x| x.abs())
.fold(0.0f32, f32::max);
assert!(
peak_out > 0.5,
"High freq should pass through HPF: peak_out={peak_out}"
);
}
#[test]
fn test_rlc_bandpass_peak_at_resonance() {
let f0 = 1000.0; let q = 5.0;
let r = 100.0;
let mut bpf = RlcBandPass::from_frequency(f0, q, r);
bpf.prepare(192000.0);
let fs = 192000.0;
let test_freqs = [200.0, 1000.0, 5000.0];
let mut peaks = Vec::new();
for &freq in &test_freqs {
bpf.prepare(fs);
let n = (8.0 * fs / freq) as usize;
let mut input = vec![0.0f32; n];
let mut output = vec![0.0f32; n];
for i in 0..n {
input[i] = (2.0 * std::f32::consts::PI * freq * i as f32 / fs).sin();
}
bpf.process_block(&input, &mut output);
let peak: f32 = output[n / 2..]
.iter()
.map(|x| x.abs())
.fold(0.0f32, f32::max);
peaks.push(peak);
}
assert!(
peaks[1] > peaks[0],
"BPF should peak at resonance: at_200Hz={}, at_1kHz={}",
peaks[0],
peaks[1]
);
assert!(
peaks[1] > peaks[2],
"BPF should peak at resonance: at_5kHz={}, at_1kHz={}",
peaks[2],
peaks[1]
);
}
#[test]
fn test_rlc_lowpass_second_order() {
let mut lpf = RlcLowPass::from_cutoff(1000.0, 0.707, 600.0);
lpf.prepare(192000.0);
let fs = 192000.0;
let n = 19200;
let input = vec![1.0f32; n];
let mut output = vec![0.0f32; n];
lpf.process_block(&input, &mut output);
let dc_out = output[n - 1];
assert!(
(dc_out - 1.0).abs() < 0.1,
"DC should pass through 2nd-order LPF: dc_out={dc_out}"
);
lpf.prepare(fs);
let test_freq = 10000.0;
let n2 = (8.0 * fs / test_freq) as usize;
let mut input2 = vec![0.0f32; n2];
let mut output2 = vec![0.0f32; n2];
for i in 0..n2 {
input2[i] = (2.0 * std::f32::consts::PI * test_freq * i as f32 / fs).sin();
}
lpf.process_block(&input2, &mut output2);
let peak_high: f32 = output2[n2 / 2..]
.iter()
.map(|x| x.abs())
.fold(0.0f32, f32::max);
assert!(
peak_high < 0.05,
"10kHz should be heavily attenuated by 2nd-order LPF: peak={peak_high}"
);
}
#[test]
fn test_rlc_filter_enum() {
let mut filter = RlcFilter::RcLow(RcLowPass::new(1000.0, 100e-9));
filter.prepare(44100.0);
let input = [0.5f32; 64];
let mut output = [0.0f32; 64];
filter.process_block(&input, &mut output);
assert!(output.iter().all(|x| x.is_finite()));
}
#[test]
fn test_rc_lowpass_frequency_response_accuracy() {
let r = 1000.0;
let c = 100e-9;
let fc = 1.0 / (2.0 * std::f32::consts::PI * r * c);
let fs = 192000.0;
let mut lpf = RcLowPass::new(r, c);
lpf.prepare(fs);
let n = (16.0 * fs / fc) as usize; let mut input = vec![0.0f32; n];
let mut output = vec![0.0f32; n];
for i in 0..n {
input[i] = (2.0 * std::f32::consts::PI * fc * i as f32 / fs).sin();
}
lpf.process_block(&input, &mut output);
let peak: f32 = output[n * 3 / 4..]
.iter()
.map(|x| x.abs())
.fold(0.0f32, f32::max);
let expected = 1.0 / 2.0f32.sqrt();
let error = (peak - expected).abs();
assert!(
error < 0.05,
"At cutoff freq ({fc:.0} Hz): peak={peak:.4}, expected={expected:.4}, error={error:.4}"
);
}
#[test]
fn test_no_nan_or_inf() {
let filters: Vec<Box<dyn CircuitComponent>> = vec![
Box::new(RcLowPass::new(1000.0, 100e-9)),
Box::new(RcHighPass::new(10000.0, 1e-6)),
Box::new(RlcBandPass::from_frequency(1000.0, 5.0, 100.0)),
Box::new(RlcLowPass::from_cutoff(1000.0, 0.707, 600.0)),
];
for mut filter in filters {
filter.prepare(44100.0);
let mut input = vec![0.0f32; 1024];
input[0] = 1.0;
let mut output = vec![0.0f32; 1024];
filter.process_block(&input, &mut output);
for (i, &s) in output.iter().enumerate() {
assert!(s.is_finite(), "Non-finite output at sample {i}: {s}");
}
}
}
}