use serde::{Deserialize, Serialize};
use crate::delay::DelayLine;
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KarplusStrong {
delay_line: DelayLine,
feedback: f32,
damping_prev: f32,
brightness: f32,
delay_samples: f32,
sample_rate: f32,
frequency: f32,
noise_state: u32,
}
impl KarplusStrong {
pub fn new(frequency: f32, decay: f32, brightness: f32, sample_rate: f32) -> Result<Self> {
if sample_rate <= 0.0 || !sample_rate.is_finite() {
return Err(crate::error::NaadError::InvalidSampleRate { sample_rate });
}
if frequency <= 0.0 || !frequency.is_finite() || frequency >= sample_rate / 2.0 {
return Err(crate::error::NaadError::InvalidFrequency {
frequency,
nyquist: sample_rate / 2.0,
});
}
let delay_samples = sample_rate / frequency;
let max_delay = (delay_samples.ceil() as usize) + 2;
let delay_line = DelayLine::new(max_delay);
let feedback = 0.9 + decay.clamp(0.0, 1.0) * 0.099;
Ok(Self {
delay_line,
feedback,
damping_prev: 0.0,
brightness: brightness.clamp(0.0, 1.0),
delay_samples,
sample_rate,
frequency,
noise_state: 54321,
})
}
pub fn pluck(&mut self) {
self.delay_line.clear();
self.damping_prev = 0.0;
let n = self.delay_samples.ceil() as usize;
for _ in 0..n {
let noise = self.next_noise();
self.delay_line.write(noise);
}
}
pub fn set_frequency(&mut self, frequency: f32) -> Result<()> {
if frequency <= 0.0 || !frequency.is_finite() || frequency >= self.sample_rate / 2.0 {
return Err(crate::error::NaadError::InvalidFrequency {
frequency,
nyquist: self.sample_rate / 2.0,
});
}
self.frequency = frequency;
self.delay_samples = self.sample_rate / frequency;
Ok(())
}
#[inline]
#[must_use]
pub fn next_sample(&mut self) -> f32 {
let delayed = self.delay_line.read(self.delay_samples);
let filtered = self.brightness * delayed + (1.0 - self.brightness) * self.damping_prev;
self.damping_prev = crate::flush_denormal(filtered);
let feedback_sample = filtered * self.feedback;
self.delay_line
.write(crate::flush_denormal(feedback_sample));
delayed
}
#[inline]
pub fn fill_buffer(&mut self, buffer: &mut [f32]) {
for s in buffer.iter_mut() {
*s = self.next_sample();
}
}
#[must_use]
pub fn is_active(&self) -> bool {
self.damping_prev.abs() > 1e-6
}
#[must_use]
pub fn frequency(&self) -> f32 {
self.frequency
}
#[inline]
fn next_noise(&mut self) -> f32 {
crate::dsp_util::xorshift32_signed_f32(&mut self.noise_state)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Waveguide {
forward_delay: DelayLine,
backward_delay: DelayLine,
delay_samples: f32,
damping: f32,
junction_coeff: f32,
sample_rate: f32,
}
impl Waveguide {
pub fn new(frequency: f32, damping: f32, sample_rate: f32) -> Result<Self> {
if sample_rate <= 0.0 || !sample_rate.is_finite() {
return Err(crate::error::NaadError::InvalidSampleRate { sample_rate });
}
if frequency <= 0.0 || !frequency.is_finite() || frequency >= sample_rate / 2.0 {
return Err(crate::error::NaadError::InvalidFrequency {
frequency,
nyquist: sample_rate / 2.0,
});
}
let half_period = sample_rate / frequency / 2.0;
let max_delay = (half_period.ceil() as usize) + 2;
Ok(Self {
forward_delay: DelayLine::new(max_delay),
backward_delay: DelayLine::new(max_delay),
delay_samples: half_period,
damping: damping.clamp(0.0, 1.0),
junction_coeff: 0.99,
sample_rate,
})
}
pub fn excite(&mut self, sample: f32) {
let half = sample * 0.5;
self.forward_delay.write(half);
self.backward_delay.write(half);
}
#[inline]
#[must_use]
pub fn next_sample(&mut self) -> f32 {
let fwd = self.forward_delay.read(self.delay_samples);
let bwd = self.backward_delay.read(self.delay_samples);
let attenuation = 1.0 - self.damping;
let fwd_reflected = -bwd * attenuation * self.junction_coeff;
let bwd_reflected = -fwd * attenuation * self.junction_coeff;
self.forward_delay
.write(crate::flush_denormal(fwd_reflected));
self.backward_delay
.write(crate::flush_denormal(bwd_reflected));
fwd + bwd
}
#[inline]
pub fn fill_buffer(&mut self, buffer: &mut [f32]) {
for s in buffer.iter_mut() {
*s = self.next_sample();
}
}
pub fn set_junction_coeff(&mut self, coeff: f32) {
self.junction_coeff = coeff.clamp(0.0, 1.0);
}
#[must_use]
pub fn is_active(&self) -> bool {
let fwd = self.forward_delay.read(self.delay_samples);
let bwd = self.backward_delay.read(self.delay_samples);
(fwd + bwd).abs() > 1e-6
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ks_produces_pitched_output() {
let mut ks = KarplusStrong::new(440.0, 0.9, 0.5, 44100.0).unwrap();
ks.pluck();
let mut buf = [0.0f32; 2048];
ks.fill_buffer(&mut buf);
assert!(
buf.iter().any(|&s| s.abs() > 0.1),
"KS should produce output after pluck"
);
assert!(buf.iter().all(|s| s.is_finite()));
}
#[test]
fn test_ks_decays() {
let mut ks = KarplusStrong::new(440.0, 0.0, 0.5, 44100.0).unwrap();
ks.pluck();
let mut buf = [0.0f32; 512];
for _ in 0..100 {
ks.fill_buffer(&mut buf);
}
let peak = buf.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
assert!(
peak < 0.1,
"KS with low decay should attenuate, peak={peak}"
);
}
#[test]
fn test_ks_frequency_matches() {
let freq = 200.0;
let sr = 44100.0;
let mut ks = KarplusStrong::new(freq, 0.95, 0.3, sr).unwrap();
ks.pluck();
let expected_period = sr / freq;
let mut skip_buf = vec![0.0f32; 4096];
ks.fill_buffer(&mut skip_buf);
let num_samples = 16384;
let mut samples = vec![0.0f32; num_samples];
ks.fill_buffer(&mut samples);
let search_min = (expected_period * 0.8) as usize;
let search_max = (expected_period * 1.2) as usize;
let window = 4096;
let mut best_lag = search_min;
let mut best_corr = f32::NEG_INFINITY;
for lag in search_min..=search_max.min(window) {
let mut corr = 0.0f32;
for i in 0..(window - lag) {
corr += samples[i] * samples[i + lag];
}
if corr > best_corr {
best_corr = corr;
best_lag = lag;
}
}
let error = (best_lag as f32 - expected_period).abs() / expected_period;
assert!(
error < 0.05,
"frequency should be close to expected: measured_period={best_lag}, expected={expected_period}"
);
}
#[test]
fn test_waveguide_produces_output() {
let mut wg = Waveguide::new(220.0, 0.01, 44100.0).unwrap();
for _ in 0..10 {
wg.excite(1.0);
let _ = wg.next_sample();
}
let mut buf = [0.0f32; 1024];
wg.fill_buffer(&mut buf);
assert!(
buf.iter().any(|&s| s.abs() > 0.001),
"waveguide should produce output after excitation"
);
assert!(buf.iter().all(|s| s.is_finite()));
}
#[test]
fn test_ks_serde_roundtrip() {
let ks = KarplusStrong::new(440.0, 0.9, 0.5, 44100.0).unwrap();
let json = serde_json::to_string(&ks).unwrap();
let back: KarplusStrong = serde_json::from_str(&json).unwrap();
assert!((ks.frequency - back.frequency).abs() < f32::EPSILON);
assert!((ks.feedback - back.feedback).abs() < f32::EPSILON);
}
#[test]
fn test_waveguide_serde_roundtrip() {
let wg = Waveguide::new(220.0, 0.01, 44100.0).unwrap();
let json = serde_json::to_string(&wg).unwrap();
let back: Waveguide = serde_json::from_str(&json).unwrap();
assert!((wg.damping - back.damping).abs() < f32::EPSILON);
assert!((wg.delay_samples - back.delay_samples).abs() < f32::EPSILON);
}
}