use aether_core::{node::DspNode, param::ParamBlock, state::StateBlob, BUFFER_SIZE, MAX_INPUTS};
use std::f32::consts::TAU;
#[derive(Clone, Copy)]
struct LfoState {
phase: f32,
held_value: f32,
smooth_target: f32,
smooth_current: f32,
}
pub struct Lfo {
phase: f32,
held_value: f32,
smooth_target: f32,
smooth_current: f32,
prev_phase: f32,
}
impl Lfo {
pub fn new() -> Self {
Self {
phase: 0.0,
held_value: 0.0,
smooth_target: 0.0,
smooth_current: 0.0,
prev_phase: 0.0,
}
}
#[inline(always)]
fn next_sample(&mut self, rate: f32, depth: f32, waveform: u32, sr: f32) -> f32 {
let phase_inc = rate / sr;
let crossed_zero = self.phase < self.prev_phase;
let raw = match waveform {
0 => (self.phase * TAU).sin(),
1 => {
if self.phase < 0.5 {
4.0 * self.phase - 1.0
} else {
3.0 - 4.0 * self.phase
}
}
2 => if self.phase < 0.5 { 1.0 } else { -1.0 },
3 => {
if crossed_zero {
self.held_value = pseudo_random(self.phase) * 2.0 - 1.0;
}
self.held_value
}
_ => {
if crossed_zero {
self.smooth_target = pseudo_random(self.phase) * 2.0 - 1.0;
}
let smooth_rate = rate / sr * 0.1;
self.smooth_current += (self.smooth_target - self.smooth_current) * smooth_rate;
self.smooth_current
}
};
self.prev_phase = self.phase;
self.phase = (self.phase + phase_inc).fract();
raw * depth
}
}
#[inline(always)]
fn pseudo_random(seed: f32) -> f32 {
let x = (seed * 127.1 + 311.7).sin() * 43_758.547;
x - x.floor()
}
impl Default for Lfo {
fn default() -> Self {
Self::new()
}
}
impl DspNode for Lfo {
fn process(
&mut self,
_inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
output: &mut [f32; BUFFER_SIZE],
params: &mut ParamBlock,
sample_rate: f32,
) {
for sample in output.iter_mut() {
let rate = params.get(0).current.clamp(0.01, 20.0);
let depth = params.get(1).current.clamp(0.0, 1.0);
let waveform = params.get(2).current as u32;
*sample = self.next_sample(rate, depth, waveform, sample_rate);
params.tick_all();
}
}
fn capture_state(&self) -> StateBlob {
StateBlob::from_value(&LfoState {
phase: self.phase,
held_value: self.held_value,
smooth_target: self.smooth_target,
smooth_current: self.smooth_current,
})
}
fn restore_state(&mut self, state: StateBlob) {
let s: LfoState = state.to_value();
self.phase = s.phase;
self.held_value = s.held_value;
self.smooth_target = s.smooth_target;
self.smooth_current = s.smooth_current;
}
fn type_name(&self) -> &'static str {
"Lfo"
}
}