use crate::dsp::parameter::Parameter;
use crate::sim::components::CircuitComponent;
const R_ON: f32 = 0.1;
const R_OFF: f32 = 50_000_000.0;
pub struct Switch {
state: bool,
conductance: Parameter,
r_on: f32,
r_off: f32,
}
impl Switch {
pub fn new() -> Self {
Self {
state: false,
conductance: Parameter::with_smoothing(1.0 / R_OFF, 0.005),
r_on: R_ON,
r_off: R_OFF,
}
}
pub fn set_state(&mut self, on: bool) {
self.state = on;
if on {
self.conductance.set(1.0 / self.r_on);
} else {
self.conductance.set(1.0 / self.r_off);
}
}
pub fn toggle(&mut self) {
self.set_state(!self.state);
}
pub fn is_on(&self) -> bool {
self.state
}
pub fn current_resistance(&self) -> f32 {
1.0 / self.conductance.value
}
pub fn current_conductance(&self) -> f32 {
self.conductance.value
}
}
impl Default for Switch {
fn default() -> Self {
Self::new()
}
}
impl CircuitComponent for Switch {
fn prepare(&mut self, _sample_rate: f32) {
if self.state {
self.conductance.reset(1.0 / self.r_on);
} else {
self.conductance.reset(1.0 / self.r_off);
}
}
fn process_block(&mut self, input: &[f32], output: &mut [f32]) {
let len = input.len().min(output.len());
for i in 0..len {
self.conductance.step();
let gain = self.r_on * self.conductance.value;
output[i] = input[i] * gain;
}
}
fn update_parameters(&mut self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_switch_on_passes_signal() {
let mut sw = Switch::new();
sw.set_state(true);
sw.prepare(44100.0);
let input = [1.0f32; 64];
let mut output = [0.0f32; 64];
sw.process_block(&input, &mut output);
for &s in &output {
assert!(
(s - 1.0).abs() < 0.01,
"ON switch should pass signal, got {s}"
);
}
}
#[test]
fn test_switch_off_blocks_signal() {
let mut sw = Switch::new();
sw.prepare(44100.0);
let input = [1.0f32; 64];
let mut output = [0.0f32; 64];
sw.process_block(&input, &mut output);
for &s in &output {
assert!(s.abs() < 1e-4, "OFF switch should block signal, got {s}");
}
}
#[test]
fn test_toggle() {
let mut sw = Switch::new();
assert!(!sw.is_on());
sw.toggle();
assert!(sw.is_on());
sw.toggle();
assert!(!sw.is_on());
}
#[test]
fn test_smooth_transition_on_to_off() {
let mut sw = Switch::new();
sw.set_state(true);
sw.prepare(44100.0);
sw.set_state(false);
let block_size = 512;
let input = vec![1.0f32; block_size];
let mut output = vec![0.0f32; block_size];
sw.process_block(&input, &mut output);
for i in 1..block_size {
assert!(
output[i] <= output[i - 1] + 1e-7,
"non-monotonic decrease at sample {i}: {} -> {}",
output[i - 1],
output[i]
);
}
assert!(
output[0] > 0.9,
"transition should start near 1.0, got {}",
output[0]
);
}
#[test]
fn test_smooth_transition_off_to_on() {
let mut sw = Switch::new();
sw.prepare(44100.0);
sw.set_state(true);
let block_size = 512;
let input = vec![1.0f32; block_size];
let mut output = vec![0.0f32; block_size];
sw.process_block(&input, &mut output);
for i in 1..block_size {
assert!(
output[i] >= output[i - 1] - 1e-7,
"non-monotonic increase at sample {i}: {} -> {}",
output[i - 1],
output[i]
);
}
}
#[test]
fn test_current_resistance_on() {
let mut sw = Switch::new();
sw.set_state(true);
sw.prepare(44100.0);
let r = sw.current_resistance();
assert!(
(r - R_ON).abs() < 0.01,
"ON resistance should be ~{R_ON}, got {r}"
);
}
#[test]
fn test_current_resistance_off() {
let mut sw = Switch::new();
sw.prepare(44100.0);
let r = sw.current_resistance();
assert!(
(r - R_OFF).abs() / R_OFF < 0.01,
"OFF resistance should be ~{R_OFF}, got {r}"
);
}
#[test]
fn test_no_nan_or_inf() {
let mut sw = Switch::new();
sw.prepare(44100.0);
let input = vec![0.7f32; 64];
let mut output = vec![0.0f32; 64];
for _ in 0..10 {
sw.toggle();
sw.process_block(&input, &mut output);
for &s in &output {
assert!(!s.is_nan(), "output contains NaN");
assert!(!s.is_infinite(), "output contains Inf");
}
}
}
}