pub struct WaveguideString {
delay_fwd: Vec<f32>,
delay_bck: Vec<f32>,
write_fwd: usize,
write_bck: usize,
sample_rate: f32,
pub tension: f32, pub damping: f32, pub brightness: f32, pub dispersion: f32, pub length: f32, pub excite: bool,
pub excite_pos: f32,
noise_seed: u64,
lp_state: f32,
ap_state: f32,
}
const MAX_DELAY: usize = 4096;
impl WaveguideString {
pub fn new(sample_rate: f32) -> Self {
Self {
delay_fwd: vec![0.0; MAX_DELAY],
delay_bck: vec![0.0; MAX_DELAY],
write_fwd: 0,
write_bck: 0,
sample_rate,
tension: 0.5,
damping: 0.995, brightness: 0.35, dispersion: 0.12, length: (sample_rate / 220.0).clamp(2.0, MAX_DELAY as f32 - 2.0),
excite: false,
excite_pos: 0.5,
noise_seed: 1_234_567,
lp_state: 0.0,
ap_state: 0.0,
}
}
pub fn set_freq(&mut self, hz: f32) {
let hz = hz.max(10.0);
let scaled = hz * 2.0f32.powf((self.tension - 0.5) * 2.0);
self.length = (self.sample_rate / scaled).clamp(2.0, MAX_DELAY as f32 - 2.0);
}
fn lcg_noise(&mut self) -> f32 {
self.noise_seed = self
.noise_seed
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(self.noise_seed >> 33) as f32 / (1u64 << 31) as f32 * 2.0 - 1.0
}
#[inline(always)]
fn read_frac(buf: &[f32], write: usize, delay: f32) -> f32 {
let len = buf.len();
let d0 = delay as usize;
let frac = delay - d0 as f32;
let i0 = (write + len - d0.min(len - 1)) % len;
let i1 = (write + len - (d0 + 1).min(len - 1)) % len;
buf[i0] * (1.0 - frac) + buf[i1] * frac
}
pub fn next_sample(&mut self) -> f32 {
let len_f = self.length.max(2.0).min(MAX_DELAY as f32 - 2.0);
if self.excite {
let len = len_f as usize;
let inject = ((self.excite_pos * len as f32) as usize).min(len - 1);
for k in 0..len {
let n = self.lcg_noise();
self.delay_fwd[(inject + k) % len] = n;
self.delay_bck[(inject + k) % len] = n;
}
self.write_fwd = inject;
self.write_bck = inject;
self.lp_state = 0.0;
self.ap_state = 0.0;
self.excite = false;
}
let half = len_f * 0.5;
let fwd_read = Self::read_frac(&self.delay_fwd, self.write_fwd, half);
let bck_read = Self::read_frac(&self.delay_bck, self.write_bck, half);
let at_bridge = fwd_read + bck_read;
let b = self.brightness;
self.lp_state = (1.0 - b) * at_bridge + b * self.lp_state;
let after_lp = self.lp_state;
let c = self.dispersion;
let ap_in = after_lp;
let ap_out = c * (ap_in - self.ap_state) + self.ap_state;
self.ap_state = ap_in;
let fed_back = ap_out * self.damping;
self.delay_fwd[self.write_fwd] = fed_back;
self.delay_bck[self.write_bck] = -fed_back;
let active_len = len_f as usize;
self.write_fwd = (self.write_fwd + 1) % active_len.max(4);
self.write_bck = (self.write_bck + 1) % active_len.max(4);
fwd_read
}
}
#[cfg(test)]
mod tests {
use super::*;
const SR: f32 = 44100.0;
#[test]
fn test_waveguide_silent_before_excite() {
let mut s = WaveguideString::new(SR);
let mut sum = 0.0f32;
for _ in 0..100 {
sum += s.next_sample().abs();
}
assert_eq!(sum, 0.0, "Should be silent before excitation");
}
#[test]
fn test_waveguide_produces_output_after_excite() {
let mut s = WaveguideString::new(SR);
s.set_freq(440.0);
s.excite = true;
let mut max_abs = 0.0f32;
for _ in 0..4410 {
let out = s.next_sample();
max_abs = max_abs.max(out.abs());
}
assert!(max_abs > 0.0, "Should produce output after excitation");
}
#[test]
fn test_waveguide_output_finite() {
let mut s = WaveguideString::new(SR);
s.set_freq(220.0);
s.excite = true;
for i in 0..22050 {
let out = s.next_sample();
assert!(out.is_finite(), "Output non-finite at sample {}", i);
}
}
#[test]
fn test_waveguide_set_freq_changes_length() {
let mut s = WaveguideString::new(SR);
let len_before = s.length;
s.set_freq(880.0);
assert!(
(s.length - len_before).abs() > 1.0,
"set_freq should change length"
);
}
#[test]
fn test_waveguide_decays_over_time() {
let mut s = WaveguideString::new(SR);
s.set_freq(220.0);
s.excite = true;
let window = 1000usize;
let mut rms_early = 0.0f32;
for _ in 0..window {
let v = s.next_sample();
rms_early += v * v;
}
for _ in 0..(SR as usize / 2) {
s.next_sample();
}
let mut rms_late = 0.0f32;
for _ in 0..window {
let v = s.next_sample();
rms_late += v * v;
}
assert!(
rms_late < rms_early,
"String should decay: early={}, late={}",
rms_early,
rms_late
);
}
#[test]
fn test_waveguide_higher_freq_shorter_period() {
let mut s = WaveguideString::new(SR);
s.set_freq(220.0);
let len_low = s.length;
s.set_freq(880.0);
let len_high = s.length;
assert!(
len_high < len_low,
"Higher frequency should give shorter delay line: 220={}, 880={}",
len_low,
len_high
);
}
#[test]
fn test_waveguide_tension_shifts_pitch() {
let mut s_mid = WaveguideString::new(SR);
s_mid.tension = 0.5;
s_mid.set_freq(440.0);
let len_mid = s_mid.length;
let mut s_low = WaveguideString::new(SR);
s_low.tension = 0.25; s_low.set_freq(440.0);
let len_low = s_low.length;
assert!(
len_low > len_mid,
"Lower tension should increase delay-line length: mid={}, low={}",
len_mid,
len_low
);
}
}