Skip to main content

rill_patchbay/automaton/
lfo.rs

1//! # LFO (Low Frequency Oscillator) автоматы
2//!
3//! Генераторы периодических сигналов для модуляции параметров.
4//! Поддерживаются различные формы волны и режимы синхронизации.
5
6use crate::control::{Automaton, Range, Time};
7use std::f64::consts::PI;
8
9/// Форма волны LFO
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum LfoWaveform {
12    /// Синусоида (гладкая)
13    Sine,
14    /// Треугольная волна
15    Triangle,
16    /// Пилообразная волна (нарастающая)
17    Saw,
18    /// Пилообразная волна (спадающая)
19    ReverseSaw,
20    /// Прямоугольная волна
21    Square,
22    /// Прямоугольная с переменной скважностью
23    Pulse(f64), // 0.0 - 1.0
24    /// Случайное значение, удерживаемое в течение периода
25    SampleAndHold,
26    /// Плавное случайное блуждание
27    RandomWalk,
28}
29
30impl LfoWaveform {
31    /// Получить название формы волны
32    pub fn name(&self) -> &'static str {
33        match self {
34            LfoWaveform::Sine => "Sine",
35            LfoWaveform::Triangle => "Triangle",
36            LfoWaveform::Saw => "Saw",
37            LfoWaveform::ReverseSaw => "Reverse Saw",
38            LfoWaveform::Square => "Square",
39            LfoWaveform::Pulse(_) => "Pulse",
40            LfoWaveform::SampleAndHold => "S&H",
41            LfoWaveform::RandomWalk => "Random Walk",
42        }
43    }
44
45    /// Вычислить значение для заданной фазы
46    pub fn evaluate(&self, phase: f64, pulse_width: Option<f64>) -> f64 {
47        match self {
48            LfoWaveform::Sine => (phase * 2.0 * PI).sin(),
49
50            LfoWaveform::Triangle => {
51                if phase < 0.25 {
52                    4.0 * phase
53                } else if phase < 0.75 {
54                    2.0 - 4.0 * phase
55                } else {
56                    4.0 * phase - 4.0
57                }
58            }
59
60            LfoWaveform::Saw => 2.0 * phase - 1.0,
61
62            LfoWaveform::ReverseSaw => 1.0 - 2.0 * phase,
63
64            LfoWaveform::Square => {
65                if phase < 0.5 {
66                    1.0
67                } else {
68                    -1.0
69                }
70            }
71
72            LfoWaveform::Pulse(width) => {
73                let w = pulse_width.unwrap_or(*width);
74                if phase < w {
75                    1.0
76                } else {
77                    -1.0
78                }
79            }
80
81            LfoWaveform::SampleAndHold => {
82                // Значение обновляется на каждом периоде
83                // Здесь просто возвращаем фазу как заглушку
84                // Реальное значение хранится в состоянии автомата
85                phase
86            }
87
88            LfoWaveform::RandomWalk => {
89                // Непрерывное случайное блуждание
90                // Здесь просто возвращаем фазу как заглушку
91                phase
92            }
93        }
94    }
95}
96
97/// Состояние LFO
98#[derive(Debug, Clone)]
99pub struct LfoState {
100    /// Текущая фаза (0.0 - 1.0)
101    pub phase: f64,
102    /// Текущее значение (для S&H и RandomWalk)
103    pub value: f64,
104    /// Счётчик семплов для S&H
105    pub hold_counter: usize,
106    /// Случайное зерно
107    pub rng_state: u64,
108    /// Время последнего шага
109    pub last_time: f64,
110}
111
112/// LFO автомат
113#[derive(Debug, Clone)]
114pub struct LfoAutomaton {
115    /// Имя автомата
116    name: String,
117    /// Частота (Hz)
118    frequency: f64,
119    /// Амплитуда
120    amplitude: f64,
121    /// Смещение
122    offset: f64,
123    /// Форма волны
124    waveform: LfoWaveform,
125    /// Диапазон выходных значений
126    range: Range,
127    /// Ширина импульса (для Pulse)
128    pulse_width: f64,
129    /// Скорость случайного блуждания
130    walk_rate: f64,
131}
132
133impl LfoAutomaton {
134    /// Создать новый LFO
135    pub fn new(
136        name: &str,
137        frequency: f64,
138        amplitude: f64,
139        offset: f64,
140        waveform: LfoWaveform,
141    ) -> Self {
142        Self {
143            name: name.to_string(),
144            frequency: frequency.max(0.001),
145            amplitude,
146            offset,
147            waveform,
148            range: Range::bipolar(),
149            pulse_width: 0.5,
150            walk_rate: 0.1,
151        }
152    }
153
154    pub fn with_range(mut self, range: Range) -> Self {
155        self.range = range;
156        self
157    }
158
159    pub fn with_pulse_width(mut self, width: f64) -> Self {
160        self.pulse_width = width.clamp(0.01, 0.99);
161        self
162    }
163
164    pub fn with_walk_rate(mut self, rate: f64) -> Self {
165        self.walk_rate = rate.max(0.0);
166        self
167    }
168
169    fn random(&self, state: &mut u64) -> f64 {
170        let mut x = *state;
171        x ^= x << 13;
172        x ^= x >> 7;
173        x ^= x << 17;
174        *state = x;
175        (x as f64 / u64::MAX as f64) * 2.0 - 1.0
176    }
177
178    fn update_random_walk(&self, state: &mut LfoState, dt: f64) {
179        let step = (self.random(&mut state.rng_state) - 0.5) * self.walk_rate * dt * 100.0;
180        state.value = (state.value + step).clamp(-1.0, 1.0);
181    }
182}
183
184/// Действие для LFO
185#[derive(Debug, Clone, Default)]
186pub enum LfoAction {
187    #[default]
188    None,
189    /// Сбросить фазу
190    Reset,
191}
192
193impl Automaton for LfoAutomaton {
194    type State = LfoState;
195    type Action = LfoAction;
196
197    fn step(
198        &self,
199        time: Time,
200        action: &Self::Action,
201        state: &Self::State,
202    ) -> (Self::State, Option<f64>) {
203        let mut new_state = state.clone();
204
205        // Обработка действий
206        if let LfoAction::Reset = action {
207            new_state.phase = 0.0;
208            new_state.last_time = time;
209        }
210
211        let dt = time - new_state.last_time;
212
213        new_state.phase += self.frequency * dt;
214        if new_state.phase >= 1.0 {
215            new_state.phase -= 1.0;
216            if let LfoWaveform::SampleAndHold = self.waveform {
217                new_state.value = self.random(&mut new_state.rng_state);
218            }
219        }
220        new_state.last_time = time;
221
222        if let LfoWaveform::RandomWalk = self.waveform {
223            self.update_random_walk(&mut new_state, dt);
224        }
225
226        let raw_value = match self.waveform {
227            LfoWaveform::SampleAndHold => new_state.value,
228            LfoWaveform::RandomWalk => new_state.value,
229            _ => self
230                .waveform
231                .evaluate(new_state.phase, Some(self.pulse_width)),
232        };
233
234        let value = raw_value * self.amplitude + self.offset;
235        let clamped = self.range.clamp(value);
236
237        (new_state, Some(clamped))
238    }
239
240    fn initial_state(&self) -> Self::State {
241        LfoState {
242            phase: 0.0,
243            value: 0.0,
244            hold_counter: 0,
245            rng_state: 123456789,
246            last_time: 0.0,
247        }
248    }
249
250    fn name(&self) -> &str {
251        &self.name
252    }
253
254    fn extract_value(&self, state: &Self::State) -> f64 {
255        let raw = match self.waveform {
256            LfoWaveform::SampleAndHold => state.value,
257            LfoWaveform::RandomWalk => state.value,
258            _ => self.waveform.evaluate(state.phase, Some(self.pulse_width)),
259        };
260        self.range.clamp(raw * self.amplitude + self.offset)
261    }
262}
263
264// =============================================================================
265// Тесты
266// =============================================================================
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use float_cmp::approx_eq;
272
273    #[test]
274    fn test_sine_lfo() {
275        let lfo = LfoAutomaton::new("Sine", 1.0, 1.0, 0.0, LfoWaveform::Sine);
276        let state = lfo.initial_state();
277
278        let (new_state, value) = lfo.step(0.0, &LfoAction::None, &state);
279        assert!(approx_eq!(f64, value.unwrap(), 0.0, epsilon = 0.01));
280
281        let (_, value) = lfo.step(0.25, &LfoAction::None, &new_state);
282        assert!(approx_eq!(f64, value.unwrap(), 1.0, epsilon = 0.01));
283    }
284
285    #[test]
286    fn test_reset_action() {
287        let lfo = LfoAutomaton::new("Test", 1.0, 1.0, 0.0, LfoWaveform::Sine);
288        let mut state = lfo.initial_state();
289        state.phase = 0.5;
290
291        let (new_state, _) = lfo.step(1.0, &LfoAction::Reset, &state);
292        assert!(approx_eq!(f64, new_state.phase, 0.0, epsilon = 0.01));
293    }
294}