Skip to main content

rill_patchbay/automaton/
envelope.rs

1//! # Огибающие (Envelope) автоматы
2//!
3//! Генераторы огибающих для управления амплитудой, фильтрами и другими
4//! параметрами во времени. Поддерживаются ADSR, AR, ASR и другие типы.
5
6use crate::control::{Automaton, Range, Time};
7
8/// Тип огибающей
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum EnvelopeType {
11    /// ADSR: Attack, Decay, Sustain, Release
12    ADSR,
13    /// AR: Attack, Release (для перкуссии)
14    AR,
15    /// ASR: Attack, Sustain, Release (для органных звуков)
16    ASR,
17    /// AHDSR: Attack, Hold, Decay, Sustain, Release
18    AHDSR,
19}
20
21/// Стадия огибающей
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum EnvelopeStage {
24    Attack,
25    Hold,
26    Decay,
27    Sustain,
28    Release,
29    Off,
30}
31
32impl EnvelopeStage {
33    pub fn name(&self) -> &'static str {
34        match self {
35            EnvelopeStage::Attack => "Attack",
36            EnvelopeStage::Hold => "Hold",
37            EnvelopeStage::Decay => "Decay",
38            EnvelopeStage::Sustain => "Sustain",
39            EnvelopeStage::Release => "Release",
40            EnvelopeStage::Off => "Off",
41        }
42    }
43}
44
45/// Состояние огибающей
46#[derive(Debug, Clone)]
47pub struct EnvelopeState {
48    /// Текущая стадия
49    pub stage: EnvelopeStage,
50    /// Текущий уровень (0.0 - 1.0)
51    pub level: f64,
52    /// Время начала текущей стадии
53    pub stage_start_time: Time,
54    /// Уровень в начале стадии
55    pub stage_start_level: f64,
56    /// Целевой уровень стадии
57    pub stage_target_level: f64,
58    /// Длительность стадии в секундах
59    pub stage_duration: f64,
60    /// Запущена ли огибающая
61    pub gate: bool,
62}
63
64/// Огибающая автомат
65#[derive(Debug, Clone)]
66pub struct EnvelopeAutomaton {
67    /// Имя автомата
68    name: String,
69    /// Тип огибающей
70    env_type: EnvelopeType,
71    /// Время атаки (секунды)
72    attack: f64,
73    /// Время удержания (секунды) - для AHDSR
74    hold: f64,
75    /// Время спада (секунды)
76    decay: f64,
77    /// Уровень удержания (0.0 - 1.0)
78    sustain: f64,
79    /// Время отпускания (секунды)
80    release: f64,
81    /// Диапазон выходных значений
82    range: Range,
83    /// Кривая стадий (1.0 = линейная, >1.0 = экспоненциальная, <1.0 = логарифмическая)
84    curve: f64,
85}
86
87impl EnvelopeAutomaton {
88    /// Создать новую ADSR огибающую
89    pub fn adsr(name: &str, attack: f64, decay: f64, sustain: f64, release: f64) -> Self {
90        Self {
91            name: name.to_string(),
92            env_type: EnvelopeType::ADSR,
93            attack: attack.max(0.001),
94            hold: 0.0,
95            decay: decay.max(0.001),
96            sustain: sustain.clamp(0.0, 1.0),
97            release: release.max(0.001),
98            range: Range::unipolar(),
99            curve: 1.0,
100        }
101    }
102
103    /// Создать новую AR огибающую (для перкуссии)
104    pub fn ar(name: &str, attack: f64, release: f64) -> Self {
105        Self {
106            name: name.to_string(),
107            env_type: EnvelopeType::AR,
108            attack: attack.max(0.001),
109            hold: 0.0,
110            decay: 0.0,
111            sustain: 0.0,
112            release: release.max(0.001),
113            range: Range::unipolar(),
114            curve: 1.0,
115        }
116    }
117
118    /// Создать новую ASR огибающую (для органных звуков)
119    pub fn asr(name: &str, attack: f64, sustain: f64, release: f64) -> Self {
120        Self {
121            name: name.to_string(),
122            env_type: EnvelopeType::ASR,
123            attack: attack.max(0.001),
124            hold: 0.0,
125            decay: 0.0,
126            sustain: sustain.clamp(0.0, 1.0),
127            release: release.max(0.001),
128            range: Range::unipolar(),
129            curve: 1.0,
130        }
131    }
132
133    /// Создать новую AHDSR огибающую
134    pub fn ahdsr(
135        name: &str,
136        attack: f64,
137        hold: f64,
138        decay: f64,
139        sustain: f64,
140        release: f64,
141    ) -> Self {
142        Self {
143            name: name.to_string(),
144            env_type: EnvelopeType::AHDSR,
145            attack: attack.max(0.001),
146            hold: hold.max(0.001),
147            decay: decay.max(0.001),
148            sustain: sustain.clamp(0.0, 1.0),
149            release: release.max(0.001),
150            range: Range::unipolar(),
151            curve: 1.0,
152        }
153    }
154
155    /// Установить кривую стадий
156    pub fn with_curve(mut self, curve: f64) -> Self {
157        self.curve = curve.max(0.1);
158        self
159    }
160
161    /// Установить диапазон
162    pub fn with_range(mut self, range: Range) -> Self {
163        self.range = range;
164        self
165    }
166
167    /// Вычислить значение с учётом кривой
168    fn apply_curve(&self, t: f64) -> f64 {
169        if self.curve == 1.0 {
170            t
171        } else {
172            t.powf(self.curve)
173        }
174    }
175
176    /// Обновить стадию на основе времени
177    fn update_stage(&self, state: &mut EnvelopeState, time: Time) {
178        let elapsed = time - state.stage_start_time;
179
180        match state.stage {
181            EnvelopeStage::Attack => {
182                if elapsed >= self.attack {
183                    // Переход к следующей стадии
184                    match self.env_type {
185                        EnvelopeType::ADSR => {
186                            state.stage = EnvelopeStage::Decay;
187                            state.stage_start_time = time;
188                            state.stage_start_level = 1.0;
189                            state.stage_target_level = self.sustain;
190                            state.stage_duration = self.decay;
191                        }
192                        EnvelopeType::AR => {
193                            state.stage = EnvelopeStage::Release;
194                            state.stage_start_time = time;
195                            state.stage_start_level = 1.0;
196                            state.stage_target_level = 0.0;
197                            state.stage_duration = self.release;
198                        }
199                        EnvelopeType::ASR => {
200                            state.stage = EnvelopeStage::Sustain;
201                            state.stage_start_time = time;
202                            state.stage_start_level = 1.0;
203                            state.stage_target_level = self.sustain;
204                            state.stage_duration = 0.0;
205                        }
206                        EnvelopeType::AHDSR => {
207                            state.stage = EnvelopeStage::Hold;
208                            state.stage_start_time = time;
209                            state.stage_start_level = 1.0;
210                            state.stage_target_level = 1.0;
211                            state.stage_duration = self.hold;
212                        }
213                    }
214                } else {
215                    let t = elapsed / self.attack;
216                    state.level = state.stage_start_level
217                        + (state.stage_target_level - state.stage_start_level)
218                            * self.apply_curve(t);
219                }
220            }
221
222            EnvelopeStage::Hold => {
223                if elapsed >= self.hold {
224                    state.stage = EnvelopeStage::Decay;
225                    state.stage_start_time = time;
226                    state.stage_start_level = 1.0;
227                    state.stage_target_level = self.sustain;
228                    state.stage_duration = self.decay;
229                } else {
230                    state.level = 1.0;
231                }
232            }
233
234            EnvelopeStage::Decay => {
235                if elapsed >= self.decay {
236                    state.stage = EnvelopeStage::Sustain;
237                    state.level = self.sustain;
238                } else {
239                    let t = elapsed / self.decay;
240                    state.level = state.stage_start_level
241                        + (state.stage_target_level - state.stage_start_level)
242                            * self.apply_curve(t);
243                }
244            }
245
246            EnvelopeStage::Sustain => {
247                state.level = self.sustain;
248            }
249
250            EnvelopeStage::Release => {
251                if elapsed >= self.release {
252                    state.stage = EnvelopeStage::Off;
253                    state.level = 0.0;
254                } else {
255                    let t = elapsed / self.release;
256                    state.level = state.stage_start_level
257                        + (state.stage_target_level - state.stage_start_level)
258                            * self.apply_curve(t);
259                }
260            }
261
262            EnvelopeStage::Off => {
263                state.level = 0.0;
264            }
265        }
266    }
267}
268
269/// Действие для огибающей
270#[derive(Debug, Clone, Default)]
271pub enum EnvelopeAction {
272    #[default]
273    /// Удержание (None/Off)
274    None,
275    /// Запуск атаки
276    GateOn,
277    /// Запуск отпускания
278    GateOff,
279}
280
281impl Automaton for EnvelopeAutomaton {
282    type State = EnvelopeState;
283    type Action = EnvelopeAction;
284
285    fn step(
286        &self,
287        time: Time,
288        action: &Self::Action,
289        state: &Self::State,
290    ) -> (Self::State, Option<f64>) {
291        let mut new_state = state.clone();
292
293        // Обработка действия Gate
294        match action {
295            EnvelopeAction::GateOn => {
296                new_state.gate = true;
297                new_state.stage = EnvelopeStage::Attack;
298                new_state.stage_start_time = time;
299                new_state.stage_start_level = new_state.level;
300                new_state.stage_target_level = 1.0;
301            }
302            EnvelopeAction::GateOff => {
303                new_state.gate = false;
304                new_state.stage = EnvelopeStage::Release;
305                new_state.stage_start_time = time;
306                new_state.stage_start_level = new_state.level;
307                new_state.stage_target_level = 0.0;
308            }
309            EnvelopeAction::None => {}
310        }
311
312        self.update_stage(&mut new_state, time);
313
314        let value = self.range.denormalize(new_state.level);
315
316        (new_state, Some(value))
317    }
318
319    fn initial_state(&self) -> Self::State {
320        EnvelopeState {
321            stage: EnvelopeStage::Off,
322            level: 0.0,
323            stage_start_time: 0.0,
324            stage_start_level: 0.0,
325            stage_target_level: 0.0,
326            stage_duration: 0.0,
327            gate: false,
328        }
329    }
330
331    fn name(&self) -> &str {
332        &self.name
333    }
334
335    fn extract_value(&self, state: &Self::State) -> f64 {
336        self.range.denormalize(state.level)
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343
344    #[test]
345    fn test_adsr_envelope() {
346        let env = EnvelopeAutomaton::adsr("ADSR", 0.1, 0.2, 0.7, 0.3);
347        let mut state = env.initial_state();
348
349        assert_eq!(state.stage, EnvelopeStage::Off);
350
351        // Gate on — start attack
352        let (_s, _value) = env.step(0.0, &EnvelopeAction::GateOn, &state);
353        state = _s;
354        assert_eq!(state.stage, EnvelopeStage::Attack);
355
356        // Step at time 0.05 (during attack) — no new gate action
357        let (s, value) = env.step(0.05, &EnvelopeAction::None, &state);
358        state = s;
359        assert!(value.unwrap() > 0.0);
360        assert!(value.unwrap() < 1.0);
361
362        // Gate off — start release
363        let (s, _) = env.step(0.5, &EnvelopeAction::GateOff, &state);
364        assert_eq!(s.stage, EnvelopeStage::Release);
365    }
366}