rill_core_dsp/generators/
lfo.rs1use super::basic::{BasicOscillator, Waveform};
8use crate::generators::{Generator, SyncableGenerator};
9use crate::vector::prelude::*;
10use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
11use rill_core::traits::ProcessResult;
12use rill_core::Transcendental;
13
14#[derive(Clone, Copy)]
48pub struct LFO<T: Transcendental> {
49 osc: BasicOscillator<T>,
51 bipolar: bool,
53 phase_offset: ScalarVector1<T>,
55}
56
57impl<T: Transcendental> LFO<T> {
58 pub fn new(frequency: f32, waveform: Waveform, bipolar: bool) -> Self {
65 let one = T::from_f32(1.0);
66 Self {
67 osc: BasicOscillator::new(waveform, frequency, one),
68 bipolar,
69 phase_offset: ScalarVector1::splat(T::ZERO),
70 }
71 }
72
73 pub fn with_phase_offset(mut self, offset: T) -> Self {
75 self.set_phase_offset(offset);
76 self
77 }
78
79 pub fn set_bipolar(&mut self, bipolar: bool) {
84 self.bipolar = bipolar;
85 }
86
87 pub fn set_phase_offset(&mut self, offset: T) {
92 let one = T::from_f32(1.0);
93 let zero = T::ZERO;
94 let clamped = if offset > one {
95 one
96 } else if offset < zero {
97 zero
98 } else {
99 offset
100 };
101 self.phase_offset = ScalarVector1::splat(clamped);
102 }
103
104 pub fn phase_offset(&self) -> T {
106 self.phase_offset.extract(0)
107 }
108
109 pub fn is_bipolar(&self) -> bool {
111 self.bipolar
112 }
113
114 pub fn sync(&mut self, reset: bool) {
119 if reset {
120 self.osc.set_phase(self.phase_offset.extract(0));
121 }
122 }
123
124 pub fn modulate(&mut self) -> T {
126 let raw = self.osc.generate().extract(0);
127
128 if self.bipolar {
129 raw } else {
131 raw.mul(T::from_f32(0.5)).add(T::from_f32(0.5))
133 }
134 }
135
136 pub fn reset(&mut self) {
138 self.osc.reset();
139 self.osc.set_phase(self.phase_offset.extract(0));
140 }
141}
142
143impl<T: Transcendental> Algorithm<T> for LFO<T> {
146 fn init(&mut self, sample_rate: f32) {
147 self.osc.init(sample_rate);
148 self.osc.set_phase(self.phase_offset.extract(0));
149 }
150
151 fn reset(&mut self) {
152 self.osc.reset();
153 self.osc.set_phase(self.phase_offset.extract(0));
154 }
155
156 fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
157 for out in output.iter_mut() {
158 *out = self.modulate();
159 }
160 Ok(())
161 }
162
163 fn metadata(&self) -> AlgorithmMetadata {
164 AlgorithmMetadata {
167 name: "LFO",
168 category: AlgorithmCategory::Generator,
169 description: format!(
170 "{} wave LFO ({}polar)",
171 match self.osc.frequency() {
172 _ if self.osc.frequency() < 1.0 => "Very low frequency",
173 _ if self.osc.frequency() < 10.0 => "Low frequency",
174 _ => "Signal rate",
175 },
176 if self.bipolar { "bi" } else { "uni" }
177 )
178 .leak(),
179 author: "Rill",
180 version: env!("CARGO_PKG_VERSION"),
181 }
182 }
183}
184
185impl<T: Transcendental> Generator<T> for LFO<T> {
188 fn phase(&self) -> T {
189 self.osc.phase()
190 }
191
192 fn set_phase(&mut self, phase: T) {
193 self.osc.set_phase(phase);
194 }
195
196 fn frequency(&self) -> f32 {
197 self.osc.frequency()
198 }
199
200 fn set_frequency(&mut self, freq: f32) {
201 self.osc.set_frequency(freq);
202 }
203
204 fn amplitude(&self) -> T {
205 self.osc.amplitude()
206 }
207
208 fn set_amplitude(&mut self, amp: T) {
209 self.osc.set_amplitude(amp);
210 }
211}
212
213impl<T: Transcendental> SyncableGenerator<T> for LFO<T> {
216 fn sync(&mut self, reset: bool) {
217 if reset {
218 self.osc.set_phase(self.phase_offset.extract(0));
219 }
220 }
221
222 fn periods(&self) -> u32 {
223 self.osc.periods()
224 }
225}
226
227#[cfg(test)]
230mod tests {
231 use super::*;
232 use float_cmp::approx_eq;
233
234 #[test]
235 fn test_lfo_creation() {
236 let lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
237 assert_eq!(lfo.frequency(), 5.0);
238 assert!(lfo.is_bipolar());
239 assert_eq!(lfo.phase_offset(), 0.0);
240 }
241
242 #[test]
243 fn test_lfo_bipolar_mode() {
244 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
245 lfo.init(44100.0);
246
247 let mut output = [0.0f32; 1];
249 for _ in 0..100 {
250 lfo.process(None, &mut output).unwrap();
251 let val = output[0];
252 assert!(
253 (-1.0..=1.0).contains(&val),
254 "Value {} out of range [-1,1]",
255 val
256 );
257 }
258 }
259
260 #[test]
261 fn test_lfo_unipolar_mode() {
262 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, false);
263 lfo.init(44100.0);
264
265 let mut output = [0.0f32; 1];
267 for _ in 0..100 {
268 lfo.process(None, &mut output).unwrap();
269 let val = output[0];
270 assert!(
271 (0.0..=1.0).contains(&val),
272 "Value {} out of range [0,1]",
273 val
274 );
275 }
276 }
277
278 #[test]
279 fn test_lfo_phase_offset() {
280 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
281 lfo.set_phase_offset(0.25);
282 lfo.init(44100.0);
283
284 assert!(approx_eq!(f32, lfo.phase(), 0.25, epsilon = 0.01));
286 }
287
288 #[test]
289 fn test_lfo_sync() {
290 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
291 lfo.set_phase_offset(0.5);
292 lfo.init(44100.0);
293
294 let mut output = [0.0f32; 1];
296 for _ in 0..10 {
297 lfo.process(None, &mut output).unwrap();
298 }
299
300 lfo.sync(true);
302 assert!(approx_eq!(f32, lfo.phase(), 0.5, epsilon = 0.01));
303 }
304
305 #[test]
306 fn test_lfo_waveforms() {
307 let waveforms = [
308 Waveform::Sine,
309 Waveform::Saw,
310 Waveform::Square,
311 Waveform::Triangle,
312 ];
313
314 for &wav in &waveforms {
315 let mut lfo = LFO::<f32>::new(5.0, wav, true);
316 lfo.init(44100.0);
317
318 let mut output = [0.0f32; 1];
319 lfo.process(None, &mut output).unwrap();
320 let val = output[0];
321 assert!(
322 (-1.0..=1.0).contains(&val),
323 "Waveform {:?} produced {}",
324 wav,
325 val
326 );
327 }
328 }
329
330 #[test]
331 fn test_lfo_generator_trait() {
332 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
333 lfo.init(44100.0);
334
335 assert_eq!(lfo.frequency(), 5.0);
337
338 lfo.set_frequency(10.0);
339 assert_eq!(lfo.frequency(), 10.0);
340
341 lfo.set_amplitude(0.5);
342 assert_eq!(lfo.amplitude(), 0.5);
343
344 let phase = lfo.phase();
345 assert!((0.0..=1.0).contains(&phase));
346 }
347
348 #[test]
349 fn test_lfo_syncable_trait() {
350 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
351 lfo.init(44100.0);
352
353 let initial_periods = lfo.periods();
354 println!("Initial periods: {}", initial_periods);
355
356 let samples_per_period = (44100.0 / 5.0) as usize; println!("Samples per period: {}", samples_per_period);
359
360 let initial_phase = lfo.phase();
362 println!("Initial phase: {}", initial_phase.to_f32());
363 let mut output = [0.0f32; 1];
364
365 for i in 0..samples_per_period * 3 {
367 let before_phase = lfo.phase();
369 lfo.process(None, &mut output).unwrap();
370 let after_phase = lfo.phase();
371
372 if after_phase < before_phase {
374 println!(
375 "Phase reset at sample {}: {} -> {}",
376 i,
377 before_phase.to_f32(),
378 after_phase.to_f32()
379 );
380 println!("Periods count: {}", lfo.periods());
381 }
382
383 if i == samples_per_period - 1 {
385 println!(
386 "After 1 period (sample {}): phase={}, periods={}",
387 i,
388 lfo.phase().to_f32(),
389 lfo.periods()
390 );
391 } else if i == samples_per_period * 2 - 1 {
392 println!(
393 "After 2 periods (sample {}): phase={}, periods={}",
394 i,
395 lfo.phase().to_f32(),
396 lfo.periods()
397 );
398 }
399 }
400
401 println!("Final phase: {}", lfo.phase().to_f32());
402 println!("Final periods: {}", lfo.periods());
403
404 assert!(
405 lfo.periods() > initial_periods,
406 "Periods should increase: before={}, after={}",
407 initial_periods,
408 lfo.periods()
409 );
410
411 let mid_phase = lfo.phase();
413 assert!(mid_phase != initial_phase, "Phase should change");
414
415 lfo.sync(true);
417 assert!(approx_eq!(f32, lfo.phase(), 0.0, epsilon = 0.01));
418 }
419
420 #[test]
421 fn test_lfo_clone_copy() {
422 let lfo1 = LFO::<f32>::new(5.0, Waveform::Sine, true);
423 let lfo2 = lfo1; let lfo3 = Clone::clone(&lfo1); assert_eq!(lfo1.frequency(), lfo2.frequency());
427 assert_eq!(lfo1.frequency(), lfo3.frequency());
428 assert_eq!(lfo1.is_bipolar(), lfo2.is_bipolar());
429 }
430}