rill_core_dsp/generators/
lfo.rs1use super::basic::{BasicOscillator, Waveform};
8use crate::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
9use crate::generators::{Generator, SyncableGenerator};
10use crate::vector::prelude::*;
11use rill_core::traits::{ActionContext, 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(
157 &mut self,
158 input: Option<&[T]>,
159 output: &mut [T],
160 _ctx: &ActionContext,
161 ) -> ProcessResult<()> {
162 let input = input.unwrap_or(&[]);
163 for out in output.iter_mut() {
164 *out = self.modulate();
165 }
166 Ok(())
167 }
168
169 fn metadata(&self) -> AlgorithmMetadata {
170 AlgorithmMetadata {
173 name: "LFO",
174 category: AlgorithmCategory::Generator,
175 description: format!(
176 "{} wave LFO ({}polar)",
177 match self.osc.frequency() {
178 _ if self.osc.frequency() < 1.0 => "Very low frequency",
179 _ if self.osc.frequency() < 10.0 => "Low frequency",
180 _ => "Audio rate",
181 },
182 if self.bipolar { "bi" } else { "uni" }
183 )
184 .leak(),
185 author: "Rill",
186 version: env!("CARGO_PKG_VERSION"),
187 }
188 }
189}
190
191impl<T: Transcendental> Generator<T> for LFO<T> {
194 fn phase(&self) -> T {
195 self.osc.phase()
196 }
197
198 fn set_phase(&mut self, phase: T) {
199 self.osc.set_phase(phase);
200 }
201
202 fn frequency(&self) -> f32 {
203 self.osc.frequency()
204 }
205
206 fn set_frequency(&mut self, freq: f32) {
207 self.osc.set_frequency(freq);
208 }
209
210 fn amplitude(&self) -> T {
211 self.osc.amplitude()
212 }
213
214 fn set_amplitude(&mut self, amp: T) {
215 self.osc.set_amplitude(amp);
216 }
217}
218
219impl<T: Transcendental> SyncableGenerator<T> for LFO<T> {
222 fn sync(&mut self, reset: bool) {
223 if reset {
224 self.osc.set_phase(self.phase_offset.extract(0));
225 }
226 }
227
228 fn periods(&self) -> u32 {
229 self.osc.periods()
230 }
231}
232
233#[cfg(test)]
236mod tests {
237 use super::*;
238 use float_cmp::approx_eq;
239 use rill_core::time::ClockTick;
240 use rill_core::traits::ActionContext;
241
242 #[test]
243 fn test_lfo_creation() {
244 let lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
245 assert_eq!(lfo.frequency(), 5.0);
246 assert!(lfo.is_bipolar());
247 assert_eq!(lfo.phase_offset(), 0.0);
248 }
249
250 #[test]
251 fn test_lfo_bipolar_mode() {
252 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
253 lfo.init(44100.0);
254
255 let mut output = [0.0f32; 1];
257 let tick = ClockTick::default();
258 let ctx = ActionContext::new(&tick);
259 for _ in 0..100 {
260 lfo.process(None, &mut output, &ctx).unwrap();
261 let val = output[0];
262 assert!(
263 val >= -1.0 && val <= 1.0,
264 "Value {} out of range [-1,1]",
265 val
266 );
267 }
268 }
269
270 #[test]
271 fn test_lfo_unipolar_mode() {
272 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, false);
273 lfo.init(44100.0);
274
275 let mut output = [0.0f32; 1];
277 let tick = ClockTick::default();
278 let ctx = ActionContext::new(&tick);
279 for _ in 0..100 {
280 lfo.process(None, &mut output, &ctx).unwrap();
281 let val = output[0];
282 assert!(val >= 0.0 && val <= 1.0, "Value {} out of range [0,1]", val);
283 }
284 }
285
286 #[test]
287 fn test_lfo_phase_offset() {
288 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
289 lfo.set_phase_offset(0.25);
290 lfo.init(44100.0);
291
292 assert!(approx_eq!(f32, lfo.phase(), 0.25, epsilon = 0.01));
294 }
295
296 #[test]
297 fn test_lfo_sync() {
298 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
299 lfo.set_phase_offset(0.5);
300 lfo.init(44100.0);
301
302 let mut output = [0.0f32; 1];
304 let tick = ClockTick::default();
305 let ctx = ActionContext::new(&tick);
306 for _ in 0..10 {
307 lfo.process(None, &mut output, &ctx).unwrap();
308 }
309
310 lfo.sync(true);
312 assert!(approx_eq!(f32, lfo.phase(), 0.5, epsilon = 0.01));
313 }
314
315 #[test]
316 fn test_lfo_waveforms() {
317 let waveforms = [
318 Waveform::Sine,
319 Waveform::Saw,
320 Waveform::Square,
321 Waveform::Triangle,
322 ];
323
324 for &wav in &waveforms {
325 let mut lfo = LFO::<f32>::new(5.0, wav, true);
326 lfo.init(44100.0);
327
328 let mut output = [0.0f32; 1];
329 let tick = ClockTick::default();
330 let ctx = ActionContext::new(&tick);
331 lfo.process(None, &mut output, &ctx).unwrap();
332 let val = output[0];
333 assert!(
334 val >= -1.0 && val <= 1.0,
335 "Waveform {:?} produced {}",
336 wav,
337 val
338 );
339 }
340 }
341
342 #[test]
343 fn test_lfo_generator_trait() {
344 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
345 lfo.init(44100.0);
346
347 assert_eq!(lfo.frequency(), 5.0);
349
350 lfo.set_frequency(10.0);
351 assert_eq!(lfo.frequency(), 10.0);
352
353 lfo.set_amplitude(0.5);
354 assert_eq!(lfo.amplitude(), 0.5);
355
356 let phase = lfo.phase();
357 assert!(phase >= 0.0 && phase <= 1.0);
358 }
359
360 #[test]
361 fn test_lfo_syncable_trait() {
362 let mut lfo = LFO::<f32>::new(5.0, Waveform::Sine, true);
363 lfo.init(44100.0);
364
365 let initial_periods = lfo.periods();
366 println!("Initial periods: {}", initial_periods);
367
368 let samples_per_period = (44100.0 / 5.0) as usize; println!("Samples per period: {}", samples_per_period);
371
372 let initial_phase = lfo.phase();
374 println!("Initial phase: {}", initial_phase.to_f32());
375 let mut output = [0.0f32; 1];
376 let tick = ClockTick::default();
377 let ctx = ActionContext::new(&tick);
378
379 for i in 0..samples_per_period * 3 {
381 let before_phase = lfo.phase();
383 lfo.process(None, &mut output, &ctx).unwrap();
384 let after_phase = lfo.phase();
385
386 if after_phase < before_phase {
388 println!(
389 "Phase reset at sample {}: {} -> {}",
390 i,
391 before_phase.to_f32(),
392 after_phase.to_f32()
393 );
394 println!("Periods count: {}", lfo.periods());
395 }
396
397 if i == samples_per_period - 1 {
399 println!(
400 "After 1 period (sample {}): phase={}, periods={}",
401 i,
402 lfo.phase().to_f32(),
403 lfo.periods()
404 );
405 } else if i == samples_per_period * 2 - 1 {
406 println!(
407 "After 2 periods (sample {}): phase={}, periods={}",
408 i,
409 lfo.phase().to_f32(),
410 lfo.periods()
411 );
412 }
413 }
414
415 println!("Final phase: {}", lfo.phase().to_f32());
416 println!("Final periods: {}", lfo.periods());
417
418 assert!(
419 lfo.periods() > initial_periods,
420 "Periods should increase: before={}, after={}",
421 initial_periods,
422 lfo.periods()
423 );
424
425 let mid_phase = lfo.phase();
427 assert!(mid_phase != initial_phase, "Phase should change");
428
429 lfo.sync(true);
431 assert!(approx_eq!(f32, lfo.phase(), 0.0, epsilon = 0.01));
432 }
433
434 #[test]
435 fn test_lfo_clone_copy() {
436 let lfo1 = LFO::<f32>::new(5.0, Waveform::Sine, true);
437 let lfo2 = lfo1; let lfo3 = lfo1.clone(); assert_eq!(lfo1.frequency(), lfo2.frequency());
441 assert_eq!(lfo1.frequency(), lfo3.frequency());
442 assert_eq!(lfo1.is_bipolar(), lfo2.is_bipolar());
443 }
444}