use super::basic::{BasicOscillator, Waveform};
use crate::generators::{Generator, SyncableGenerator};
use crate::vector::prelude::*;
use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
use rill_core::traits::ProcessResult;
use rill_core::Transcendental;
#[derive(Clone, Copy)]
pub struct LFO<T: Transcendental> {
osc: BasicOscillator<T>,
bipolar: bool,
phase_offset: ScalarVector1<T>,
}
impl<T: Transcendental> LFO<T> {
pub fn new(frequency: f32, waveform: Waveform, bipolar: bool) -> Self {
let one = T::from_f32(1.0);
Self {
osc: BasicOscillator::new(waveform, frequency, one),
bipolar,
phase_offset: ScalarVector1::splat(T::ZERO),
}
}
pub fn with_phase_offset(mut self, offset: T) -> Self {
self.set_phase_offset(offset);
self
}
pub fn set_bipolar(&mut self, bipolar: bool) {
self.bipolar = bipolar;
}
pub fn set_phase_offset(&mut self, offset: T) {
let one = T::from_f32(1.0);
let zero = T::ZERO;
let clamped = if offset > one {
one
} else if offset < zero {
zero
} else {
offset
};
self.phase_offset = ScalarVector1::splat(clamped);
}
pub fn phase_offset(&self) -> T {
self.phase_offset.extract(0)
}
pub fn is_bipolar(&self) -> bool {
self.bipolar
}
pub fn sync(&mut self, reset: bool) {
if reset {
self.osc.set_phase(self.phase_offset.extract(0));
}
}
pub fn modulate(&mut self) -> T {
let raw = self.osc.generate().extract(0);
if self.bipolar {
raw } else {
raw.mul(T::from_f32(0.5)).add(T::from_f32(0.5))
}
}
pub fn reset(&mut self) {
self.osc.reset();
self.osc.set_phase(self.phase_offset.extract(0));
}
}
impl<T: Transcendental> Algorithm<T> for LFO<T> {
fn init(&mut self, sample_rate: f32) {
self.osc.init(sample_rate);
self.osc.set_phase(self.phase_offset.extract(0));
}
fn reset(&mut self) {
self.osc.reset();
self.osc.set_phase(self.phase_offset.extract(0));
}
fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
for out in output.iter_mut() {
*out = self.modulate();
}
Ok(())
}
fn metadata(&self) -> AlgorithmMetadata {
AlgorithmMetadata {
name: "LFO",
category: AlgorithmCategory::Generator,
description: format!(
"{} wave LFO ({}polar)",
match self.osc.frequency() {
_ if self.osc.frequency() < 1.0 => "Very low frequency",
_ if self.osc.frequency() < 10.0 => "Low frequency",
_ => "Signal rate",
},
if self.bipolar { "bi" } else { "uni" }
)
.leak(),
author: "Rill",
version: env!("CARGO_PKG_VERSION"),
}
}
}
impl<T: Transcendental> Generator<T> for LFO<T> {
fn phase(&self) -> T {
self.osc.phase()
}
fn set_phase(&mut self, phase: T) {
self.osc.set_phase(phase);
}
fn frequency(&self) -> f32 {
self.osc.frequency()
}
fn set_frequency(&mut self, freq: f32) {
self.osc.set_frequency(freq);
}
fn amplitude(&self) -> T {
self.osc.amplitude()
}
fn set_amplitude(&mut self, amp: T) {
self.osc.set_amplitude(amp);
}
}
impl<T: Transcendental> SyncableGenerator<T> for LFO<T> {
fn sync(&mut self, reset: bool) {
if reset {
self.osc.set_phase(self.phase_offset.extract(0));
}
}
fn periods(&self) -> u32 {
self.osc.periods()
}
}
#[cfg(test)]
mod tests {
use super::*;
use float_cmp::approx_eq;
#[test]
fn test_lfo_creation() {
let lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
assert_eq!(lfo.frequency(), 5.0);
assert!(lfo.is_bipolar());
assert_eq!(lfo.phase_offset(), 0.0);
}
#[test]
fn test_lfo_bipolar_mode() {
let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
lfo.init(44100.0);
let mut output = [0.0f32; 1];
for _ in 0..100 {
lfo.process(None, &mut output).unwrap();
let val = output[0];
assert!(
(-1.0..=1.0).contains(&val),
"Value {} out of range [-1,1]",
val
);
}
}
#[test]
fn test_lfo_unipolar_mode() {
let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, false);
lfo.init(44100.0);
let mut output = [0.0f32; 1];
for _ in 0..100 {
lfo.process(None, &mut output).unwrap();
let val = output[0];
assert!(
(0.0..=1.0).contains(&val),
"Value {} out of range [0,1]",
val
);
}
}
#[test]
fn test_lfo_phase_offset() {
let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
lfo.set_phase_offset(0.25);
lfo.init(44100.0);
assert!(approx_eq!(f32, lfo.phase(), 0.25, epsilon = 0.01));
}
#[test]
fn test_lfo_sync() {
let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
lfo.set_phase_offset(0.5);
lfo.init(44100.0);
let mut output = [0.0f32; 1];
for _ in 0..10 {
lfo.process(None, &mut output).unwrap();
}
lfo.sync(true);
assert!(approx_eq!(f32, lfo.phase(), 0.5, epsilon = 0.01));
}
#[test]
fn test_lfo_waveforms() {
let waveforms = [
Waveform::Sine,
Waveform::Saw,
Waveform::Square,
Waveform::Triangle,
];
for &wav in &waveforms {
let mut lfo = LFO::<f32>::new(5.0, wav, true);
lfo.init(44100.0);
let mut output = [0.0f32; 1];
lfo.process(None, &mut output).unwrap();
let val = output[0];
assert!(
(-1.0..=1.0).contains(&val),
"Waveform {:?} produced {}",
wav,
val
);
}
}
#[test]
fn test_lfo_generator_trait() {
let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
lfo.init(44100.0);
assert_eq!(lfo.frequency(), 5.0);
lfo.set_frequency(10.0);
assert_eq!(lfo.frequency(), 10.0);
lfo.set_amplitude(0.5);
assert_eq!(lfo.amplitude(), 0.5);
let phase = lfo.phase();
assert!((0.0..=1.0).contains(&phase));
}
#[test]
fn test_lfo_syncable_trait() {
let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
lfo.init(44100.0);
let initial_periods = lfo.periods();
println!("Initial periods: {}", initial_periods);
let samples_per_period = (44100.0 / 5.0) as usize; println!("Samples per period: {}", samples_per_period);
let initial_phase = lfo.phase();
println!("Initial phase: {}", initial_phase.to_f32());
let mut output = [0.0f32; 1];
for i in 0..samples_per_period * 3 {
let before_phase = lfo.phase();
lfo.process(None, &mut output).unwrap();
let after_phase = lfo.phase();
if after_phase < before_phase {
println!(
"Phase reset at sample {}: {} -> {}",
i,
before_phase.to_f32(),
after_phase.to_f32()
);
println!("Periods count: {}", lfo.periods());
}
if i == samples_per_period - 1 {
println!(
"After 1 period (sample {}): phase={}, periods={}",
i,
lfo.phase().to_f32(),
lfo.periods()
);
} else if i == samples_per_period * 2 - 1 {
println!(
"After 2 periods (sample {}): phase={}, periods={}",
i,
lfo.phase().to_f32(),
lfo.periods()
);
}
}
println!("Final phase: {}", lfo.phase().to_f32());
println!("Final periods: {}", lfo.periods());
assert!(
lfo.periods() > initial_periods,
"Periods should increase: before={}, after={}",
initial_periods,
lfo.periods()
);
let mid_phase = lfo.phase();
assert!(mid_phase != initial_phase, "Phase should change");
lfo.sync(true);
assert!(approx_eq!(f32, lfo.phase(), 0.0, epsilon = 0.01));
}
#[test]
fn test_lfo_clone_copy() {
let lfo1 = LFO::<f32>::new(5.0, Waveform::Sine, true);
let lfo2 = lfo1; let lfo3 = Clone::clone(&lfo1);
assert_eq!(lfo1.frequency(), lfo2.frequency());
assert_eq!(lfo1.frequency(), lfo3.frequency());
assert_eq!(lfo1.is_bipolar(), lfo2.is_bipolar());
}
}