Skip to main content

aether_nodes/
envelope.rs

1//! ADSR envelope generator.
2//!
3//! Param layout:
4//!   0 = attack  (seconds)
5//!   1 = decay   (seconds)
6//!   2 = sustain (0..1 level)
7//!   3 = release (seconds)
8//!   4 = gate    (0=off, 1=on)
9
10use aether_core::{node::DspNode, param::ParamBlock, state::StateBlob, BUFFER_SIZE, MAX_INPUTS};
11
12#[derive(Clone, Copy, PartialEq)]
13enum Stage {
14    Idle,
15    Attack,
16    Decay,
17    Sustain,
18    Release,
19}
20
21#[derive(Clone, Copy)]
22struct EnvState {
23    level: f32,
24    stage: u8,
25}
26
27pub struct AdsrEnvelope {
28    level: f32,
29    stage: Stage,
30    prev_gate: f32,
31}
32
33impl AdsrEnvelope {
34    pub fn new() -> Self {
35        Self {
36            level: 0.0,
37            stage: Stage::Idle,
38            prev_gate: 0.0,
39        }
40    }
41
42    #[inline(always)]
43    fn tick_sample(
44        &mut self,
45        gate: f32,
46        attack: f32,
47        decay: f32,
48        sustain: f32,
49        release: f32,
50        sr: f32,
51    ) -> f32 {
52        // Gate edge detection.
53        if gate > 0.5 && self.prev_gate <= 0.5 {
54            self.stage = Stage::Attack;
55        } else if gate <= 0.5 && self.prev_gate > 0.5 {
56            self.stage = Stage::Release;
57        }
58        self.prev_gate = gate;
59
60        let attack_rate = if attack > 0.0 {
61            1.0 / (attack * sr)
62        } else {
63            1.0
64        };
65        let decay_rate = if decay > 0.0 { 1.0 / (decay * sr) } else { 1.0 };
66        let release_rate = if release > 0.0 {
67            1.0 / (release * sr)
68        } else {
69            1.0
70        };
71
72        match self.stage {
73            Stage::Idle => {}
74            Stage::Attack => {
75                self.level += attack_rate;
76                if self.level >= 1.0 {
77                    self.level = 1.0;
78                    self.stage = Stage::Decay;
79                }
80            }
81            Stage::Decay => {
82                self.level -= decay_rate;
83                if self.level <= sustain {
84                    self.level = sustain;
85                    self.stage = Stage::Sustain;
86                }
87            }
88            Stage::Sustain => {
89                self.level = sustain;
90            }
91            Stage::Release => {
92                self.level -= release_rate;
93                if self.level <= 0.0 {
94                    self.level = 0.0;
95                    self.stage = Stage::Idle;
96                }
97            }
98        }
99        self.level
100    }
101}
102
103impl Default for AdsrEnvelope {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109impl DspNode for AdsrEnvelope {
110    fn process(
111        &mut self,
112        inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
113        output: &mut [f32; BUFFER_SIZE],
114        params: &mut ParamBlock,
115        sample_rate: f32,
116    ) {
117        // Input 0 can be an audio signal to modulate; if absent, output raw envelope.
118        let _silence = [0.0f32; BUFFER_SIZE];
119        let audio_in = inputs[0];
120
121        for (i, out) in output.iter_mut().enumerate() {
122            let attack = params.get(0).current.max(0.0);
123            let decay = params.get(1).current.max(0.0);
124            let sustain = params.get(2).current.clamp(0.0, 1.0);
125            let release = params.get(3).current.max(0.0);
126            let gate = params.get(4).current;
127
128            let env = self.tick_sample(gate, attack, decay, sustain, release, sample_rate);
129            *out = match audio_in {
130                Some(buf) => buf[i] * env,
131                None => env,
132            };
133            params.tick_all();
134        }
135    }
136
137    fn capture_state(&self) -> StateBlob {
138        StateBlob::from_value(&EnvState {
139            level: self.level,
140            stage: self.stage as u8,
141        })
142    }
143
144    fn restore_state(&mut self, state: StateBlob) {
145        let s: EnvState = state.to_value();
146        self.level = s.level;
147        self.stage = match s.stage {
148            0 => Stage::Idle,
149            1 => Stage::Attack,
150            2 => Stage::Decay,
151            3 => Stage::Sustain,
152            _ => Stage::Release,
153        };
154    }
155
156    fn type_name(&self) -> &'static str {
157        "AdsrEnvelope"
158    }
159}