use rand::{rngs::SmallRng, Rng, SeedableRng};
fn sine_approx(x: f32) -> f32 {
use std::f32::consts::PI;
debug_assert!((-PI..=PI).contains(&x));
const B: f32 = 4.0 / PI;
const C: f32 = -4.0 / (PI * PI);
const P: f32 = 0.225;
let y = B * x + C * x * x.abs();
P * (y * y.abs() - y) + y
}
#[derive(
Debug,
Default,
Copy,
Clone,
PartialEq,
strum::Display,
strum::EnumString,
strum::VariantNames,
strum::VariantArray,
)]
pub enum LfoWaveform {
#[default]
Sine,
Triangle,
#[strum(serialize = "Ramp Up")]
RampUp,
#[strum(serialize = "Ramp Down")]
RampDown,
Square,
Random,
#[strum(serialize = "Smooth Random")]
SmoothRandom,
}
#[derive(Debug, Clone)]
pub struct Lfo {
phase: f32,
phase_inc: f32,
waveform: LfoWaveform,
sample_hold_value: f32,
jitter_current: f32,
jitter_target: f32,
rng: SmallRng,
}
impl Default for Lfo {
fn default() -> Self {
Self::new(44100, 1.0, LfoWaveform::Sine)
}
}
impl Lfo {
pub fn new(sample_rate: u32, rate: f64, waveform: LfoWaveform) -> Self {
let phase = 0.0;
let phase_inc = (rate / sample_rate as f64) as f32;
let mut rng = SmallRng::from_os_rng();
let sample_hold_value = rng.random::<f32>() * 2.0 - 1.0;
let jitter_current = rng.random::<f32>() * 2.0 - 1.0;
let jitter_target = rng.random::<f32>() * 2.0 - 1.0;
Self {
phase,
phase_inc,
waveform,
sample_hold_value,
jitter_current,
jitter_target,
rng,
}
}
pub fn reset(&mut self) {
self.phase = 0.0;
if matches!(
self.waveform,
LfoWaveform::Random | LfoWaveform::SmoothRandom
) {
self.sample_hold_value = self.rng.random::<f32>() * 2.0 - 1.0;
self.jitter_current = self.jitter_target;
self.jitter_target = self.rng.random::<f32>() * 2.0 - 1.0;
}
}
pub fn set_rate(&mut self, sample_rate: u32, rate: f64) {
self.phase_inc = (rate / sample_rate as f64) as f32;
}
pub fn set_phase(&mut self, phase: f32) {
self.phase = phase.rem_euclid(1.0);
}
pub fn set_phase_degrees(&mut self, phase: f32) {
self.set_phase(phase / std::f32::consts::TAU);
}
pub fn set_waveform(&mut self, waveform: LfoWaveform) {
self.waveform = waveform;
}
pub fn run(&mut self) -> f32 {
let value = match self.waveform {
LfoWaveform::Sine => {
let p = if self.phase < 0.5 {
self.phase * std::f32::consts::TAU
} else {
(self.phase - 1.0) * std::f32::consts::TAU
};
sine_approx(p)
}
LfoWaveform::Triangle => {
if self.phase < 0.25 {
self.phase * 4.0
} else if self.phase < 0.75 {
2.0 - self.phase * 4.0
} else {
self.phase * 4.0 - 4.0
}
}
LfoWaveform::RampUp => self.phase * 2.0 - 1.0,
LfoWaveform::RampDown => 1.0 - self.phase * 2.0,
LfoWaveform::Square => {
if self.phase < 0.5 {
1.0
} else {
-1.0
}
}
LfoWaveform::Random => self.sample_hold_value,
LfoWaveform::SmoothRandom => {
let p = std::f32::consts::FRAC_PI_2 - self.phase * std::f32::consts::PI;
let t = (1.0 - sine_approx(p)) * 0.5;
self.jitter_current + t * (self.jitter_target - self.jitter_current)
}
};
if matches!(
self.waveform,
LfoWaveform::Random | LfoWaveform::SmoothRandom
) {
self.advance_phase_random();
} else {
self.advance_phase();
}
value
}
pub fn process(&mut self, output: &mut [f32]) {
match self.waveform {
LfoWaveform::Sine => {
for sample in output {
let p = if self.phase < 0.5 {
self.phase * std::f32::consts::TAU
} else {
(self.phase - 1.0) * std::f32::consts::TAU
};
*sample = sine_approx(p);
self.advance_phase();
}
}
LfoWaveform::Triangle => {
for sample in output {
*sample = if self.phase < 0.25 {
self.phase * 4.0
} else if self.phase < 0.75 {
2.0 - self.phase * 4.0
} else {
self.phase * 4.0 - 4.0
};
self.advance_phase();
}
}
LfoWaveform::RampUp => {
for sample in output {
*sample = self.phase * 2.0 - 1.0;
self.advance_phase();
}
}
LfoWaveform::RampDown => {
for sample in output {
*sample = 1.0 - self.phase * 2.0;
self.advance_phase();
}
}
LfoWaveform::Square => {
for sample in output {
*sample = if self.phase < 0.5 { 1.0 } else { -1.0 };
self.advance_phase();
}
}
LfoWaveform::Random => {
for sample in output {
*sample = self.sample_hold_value;
self.advance_phase_random();
}
}
LfoWaveform::SmoothRandom => {
for sample in output {
let p = std::f32::consts::FRAC_PI_2 - self.phase * std::f32::consts::PI;
let t = (1.0 - sine_approx(p)) * 0.5;
*sample = self.jitter_current + t * (self.jitter_target - self.jitter_current);
self.advance_phase_random();
}
}
}
}
#[inline]
fn advance_phase(&mut self) {
self.phase += self.phase_inc;
if self.phase >= 1.0 {
self.phase -= 1.0;
}
}
#[inline]
fn advance_phase_random(&mut self) {
self.phase += self.phase_inc;
if self.phase >= 1.0 {
self.phase -= 1.0;
self.sample_hold_value = self.rng.random::<f32>() * 2.0 - 1.0;
self.jitter_current = self.jitter_target;
self.jitter_target = self.rng.random::<f32>() * 2.0 - 1.0;
}
}
}