Skip to main content

rill_core_dsp/generators/
lfo.rs

1//! Низкочастотные генераторы для модуляции
2//!
3//! LFO (Low Frequency Oscillator) используются для модуляции параметров
4//! звука: вибрато (частота), тремоло (амплитуда), фильтр-свип (частота среза)
5//! и другие эффекты.
6
7use 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/// LFO генератор (Low Frequency Oscillator)
15///
16/// Генерирует низкочастотные сигналы для модуляции параметров.
17/// Частотный диапазон: 0.01 Hz - 100 Hz.
18///
19/// # Режимы работы
20///
21/// - **Биполярный**: выходной сигнал в диапазоне [-1, 1]
22/// - **Униполярный**: выходной сигнал в диапазоне [0, 1]
23///
24/// # Пример
25/// ```
26/// use rill_core::time::ClockTick;
27/// use rill_core::traits::ActionContext;
28/// use rill_core_dsp::generators::*;
29/// use rill_core_dsp::Algorithm;
30///
31/// let tick = ClockTick::default();
32/// let ctx = ActionContext::new(&tick);
33///
34/// // Создаём LFO для модуляции частоты фильтра
35/// let mut lfo = LFO::<f32>::new(
36///     5.0,              // 5 Hz
37///     Waveform::Sine,
38///     true              // биполярный режим (-1..1)
39/// );
40/// lfo.init(44100.0);
41///
42/// // Генерируем модуляционный сигнал
43/// let mut output = [0.0_f32];
44/// lfo.process(None, &mut output, &ctx).unwrap();
45/// let modulation = output[0];
46/// ```
47#[derive(Clone, Copy)]
48pub struct LFO<T: Transcendental> {
49    /// Внутренний осциллятор
50    osc: BasicOscillator<T>,
51    /// Биполярный режим (-1..1) или униполярный (0..1)
52    bipolar: bool,
53    /// Смещение фазы (для синхронизации)
54    phase_offset: ScalarVector1<T>,
55}
56
57impl<T: Transcendental> LFO<T> {
58    /// Создать новый LFO
59    ///
60    /// # Arguments
61    /// * `frequency` - частота в Hz (0.01 - 100)
62    /// * `waveform` - форма волны
63    /// * `bipolar` - true для биполярного режима (-1..1), false для униполярного (0..1)
64    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    /// Создать LFO с фазовым смещением
74    pub fn with_phase_offset(mut self, offset: T) -> Self {
75        self.set_phase_offset(offset);
76        self
77    }
78
79    /// Установить биполярный режим
80    ///
81    /// # Arguments
82    /// * `bipolar` - true: выход в [-1, 1], false: выход в [0, 1]
83    pub fn set_bipolar(&mut self, bipolar: bool) {
84        self.bipolar = bipolar;
85    }
86
87    /// Установить смещение фазы (0..1)
88    ///
89    /// Позволяет сдвинуть фазу LFO относительно опорной точки.
90    /// Полезно для создания стерео эффектов или синхронизации нескольких LFO.
91    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    /// Получить текущее смещение фазы
105    pub fn phase_offset(&self) -> T {
106        self.phase_offset.extract(0)
107    }
108
109    /// Проверить, работает ли LFO в биполярном режиме
110    pub fn is_bipolar(&self) -> bool {
111        self.bipolar
112    }
113
114    /// Синхронизировать с внешним clock
115    ///
116    /// # Arguments
117    /// * `reset` - если true, сбросить фазу в значение phase_offset
118    pub fn sync(&mut self, reset: bool) {
119        if reset {
120            self.osc.set_phase(self.phase_offset.extract(0));
121        }
122    }
123
124    /// Получить значение для модуляции (текущий семпл)
125    pub fn modulate(&mut self) -> T {
126        let raw = self.osc.generate().extract(0);
127
128        if self.bipolar {
129            raw // уже -1..1
130        } else {
131            // Конвертируем из -1..1 в 0..1
132            raw.mul(T::from_f32(0.5)).add(T::from_f32(0.5))
133        }
134    }
135
136    /// Сбросить LFO в начальное состояние
137    pub fn reset(&mut self) {
138        self.osc.reset();
139        self.osc.set_phase(self.phase_offset.extract(0));
140    }
141}
142
143// ==================== Реализация трейта Algorithm ====================
144
145impl<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        // Получаем имя волны из поля waveform самого LFO
171        // Но у нас нет прямого доступа к waveform, поэтому используем описание из BasicOscillator
172        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
191// ==================== Реализация трейта Generator ====================
192
193impl<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
219// ==================== Реализация трейта SyncableGenerator ====================
220
221impl<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// ==================== Тесты ====================
234
235#[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        // В биполярном режиме значения должны быть в [-1, 1]
256        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        // В униполярном режиме значения должны быть в [0, 1]
276        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        // Проверяем, что фаза установлена правильно
293        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        // Продвигаем фазу
303        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        // Синхронизируем со сбросом
311        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        // Тестируем методы из трейта Generator
348        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        // Вычисляем количество семплов за период
369        let samples_per_period = (44100.0 / 5.0) as usize; // 8820 семплов
370        println!("Samples per period: {}", samples_per_period);
371
372        // Записываем начальную фазу
373        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        // Продвигаем фазу на несколько периодов
380        for i in 0..samples_per_period * 3 {
381            // 3 полных периода
382            let before_phase = lfo.phase();
383            lfo.process(None, &mut output, &ctx).unwrap();
384            let after_phase = lfo.phase();
385
386            // Проверяем, не произошёл ли сброс фазы
387            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            // Для отладки выведем информацию на ключевых точках
398            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        // Проверяем, что фаза продолжает меняться
426        let mid_phase = lfo.phase();
427        assert!(mid_phase != initial_phase, "Phase should change");
428
429        // Синхронизируем со сбросом
430        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; // Копирование
438        let lfo3 = lfo1.clone(); // Явное клонирование
439
440        assert_eq!(lfo1.frequency(), lfo2.frequency());
441        assert_eq!(lfo1.frequency(), lfo3.frequency());
442        assert_eq!(lfo1.is_bipolar(), lfo2.is_bipolar());
443    }
444}