#![forbid(unsafe_code)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
use std::f64::consts::PI;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum LfoWaveform {
#[default]
Sine,
Triangle,
Sawtooth,
Square,
Random,
}
#[derive(Clone, Debug)]
pub struct Lfo {
phase: f64,
phase_increment: f64,
waveform: LfoWaveform,
sample_rate: f64,
rate_hz: f64,
random_state: u64,
last_random: f64,
}
impl Lfo {
#[must_use]
pub fn new(rate_hz: f64, sample_rate: f64, waveform: LfoWaveform) -> Self {
let phase_increment = rate_hz / sample_rate;
Self {
phase: 0.0,
phase_increment,
waveform,
sample_rate,
rate_hz,
random_state: 0x123456789ABCDEF0,
last_random: 0.0,
}
}
pub fn set_rate(&mut self, rate_hz: f64) {
self.rate_hz = rate_hz;
self.phase_increment = rate_hz / self.sample_rate;
}
#[must_use]
pub fn rate(&self) -> f64 {
self.rate_hz
}
pub fn set_waveform(&mut self, waveform: LfoWaveform) {
self.waveform = waveform;
}
#[must_use]
pub fn waveform(&self) -> LfoWaveform {
self.waveform
}
pub fn set_phase(&mut self, phase: f64) {
self.phase = phase.fract();
}
#[must_use]
pub fn phase(&self) -> f64 {
self.phase
}
pub fn reset(&mut self) {
self.phase = 0.0;
self.last_random = 0.0;
}
pub fn next(&mut self) -> f64 {
let value = self.generate_waveform();
self.phase += self.phase_increment;
if self.phase >= 1.0 {
self.phase -= 1.0;
}
value
}
pub fn next_unipolar(&mut self) -> f64 {
(self.next() + 1.0) * 0.5
}
fn generate_waveform(&mut self) -> f64 {
match self.waveform {
LfoWaveform::Sine => self.sine(),
LfoWaveform::Triangle => self.triangle(),
LfoWaveform::Sawtooth => self.sawtooth(),
LfoWaveform::Square => self.square(),
LfoWaveform::Random => self.random(),
}
}
fn sine(&self) -> f64 {
(self.phase * 2.0 * PI).sin()
}
fn triangle(&self) -> f64 {
if self.phase < 0.5 {
4.0 * self.phase - 1.0
} else {
3.0 - 4.0 * self.phase
}
}
fn sawtooth(&self) -> f64 {
2.0 * self.phase - 1.0
}
fn square(&self) -> f64 {
if self.phase < 0.5 {
1.0
} else {
-1.0
}
}
fn random(&mut self) -> f64 {
if self.phase < self.phase_increment {
self.random_state = self.random_state.wrapping_mul(6364136223846793005);
self.random_state = self.random_state.wrapping_add(1442695040888963407);
let normalized = (self.random_state >> 32) as f64 / (u32::MAX as f64);
self.last_random = normalized * 2.0 - 1.0;
}
self.last_random
}
#[must_use]
pub fn peek(&self) -> f64 {
match self.waveform {
LfoWaveform::Sine => self.sine(),
LfoWaveform::Triangle => self.triangle(),
LfoWaveform::Sawtooth => self.sawtooth(),
LfoWaveform::Square => self.square(),
LfoWaveform::Random => self.last_random,
}
}
}
#[derive(Clone, Debug)]
pub struct StereoLfo {
left: Lfo,
right: Lfo,
phase_offset: f64,
}
impl StereoLfo {
#[must_use]
pub fn new(rate_hz: f64, sample_rate: f64, waveform: LfoWaveform, phase_offset: f64) -> Self {
let left = Lfo::new(rate_hz, sample_rate, waveform);
let mut right = Lfo::new(rate_hz, sample_rate, waveform);
right.set_phase(phase_offset);
Self {
left,
right,
phase_offset,
}
}
pub fn set_rate(&mut self, rate_hz: f64) {
self.left.set_rate(rate_hz);
self.right.set_rate(rate_hz);
}
pub fn set_waveform(&mut self, waveform: LfoWaveform) {
self.left.set_waveform(waveform);
self.right.set_waveform(waveform);
}
pub fn set_phase_offset(&mut self, offset: f64) {
self.phase_offset = offset;
let left_phase = self.left.phase();
self.right.set_phase((left_phase + offset).fract());
}
#[must_use]
pub fn phase_offset(&self) -> f64 {
self.phase_offset
}
pub fn reset(&mut self) {
self.left.reset();
self.right.reset();
self.right.set_phase(self.phase_offset);
}
pub fn next(&mut self) -> (f64, f64) {
(self.left.next(), self.right.next())
}
pub fn next_unipolar(&mut self) -> (f64, f64) {
(self.left.next_unipolar(), self.right.next_unipolar())
}
#[must_use]
pub fn left(&self) -> &Lfo {
&self.left
}
#[must_use]
pub fn right(&self) -> &Lfo {
&self.right
}
pub fn left_mut(&mut self) -> &mut Lfo {
&mut self.left
}
pub fn right_mut(&mut self) -> &mut Lfo {
&mut self.right
}
}
#[derive(Clone, Debug)]
pub struct ParameterSmoother {
current: f64,
target: f64,
coefficient: f64,
}
impl ParameterSmoother {
#[must_use]
pub fn new(initial_value: f64, smoothing_time_ms: f64, sample_rate: f64) -> Self {
let coefficient = Self::calculate_coefficient(smoothing_time_ms, sample_rate);
Self {
current: initial_value,
target: initial_value,
coefficient,
}
}
fn calculate_coefficient(time_ms: f64, sample_rate: f64) -> f64 {
let time_samples = time_ms * 0.001 * sample_rate;
(-1.0 / time_samples).exp()
}
pub fn set_target(&mut self, target: f64) {
self.target = target;
}
#[must_use]
pub fn current(&self) -> f64 {
self.current
}
#[must_use]
pub fn target(&self) -> f64 {
self.target
}
pub fn next(&mut self) -> f64 {
self.current = self.current * self.coefficient + self.target * (1.0 - self.coefficient);
self.current
}
pub fn reset(&mut self, value: f64) {
self.current = value;
self.target = value;
}
#[must_use]
pub fn is_stable(&self) -> bool {
(self.current - self.target).abs() < 1e-6
}
pub fn set_smoothing_time(&mut self, time_ms: f64, sample_rate: f64) {
self.coefficient = Self::calculate_coefficient(time_ms, sample_rate);
}
}