Skip to main content

pfx_sound/
envelope.rs

1const MIN_EDGE_SECONDS: f32 = 0.002;
2const CURVE_RATE: f32 = 5.0;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum Shape {
6    Linear,
7    Exponential,
8}
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Envelope {
12    pub attack_ms: f32,
13    pub decay_ms: f32,
14    pub sustain: f32,
15    pub release_ms: f32,
16    pub shape: Shape,
17}
18
19impl Default for Envelope {
20    fn default() -> Self {
21        Self {
22            attack_ms: 0.0,
23            decay_ms: 0.0,
24            sustain: 1.0,
25            release_ms: 0.0,
26            shape: Shape::Linear,
27        }
28    }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32enum Stage {
33    Attack,
34    Decay,
35    Sustain,
36    Release,
37    Done,
38}
39
40#[derive(Clone, Copy, Debug)]
41pub(crate) struct EnvState {
42    shape: Shape,
43    sustain: f32,
44    attack: u64,
45    decay: u64,
46    release: u64,
47    stage: Stage,
48    pos: u64,
49    from: f32,
50    level: f32,
51}
52
53impl EnvState {
54    pub(crate) fn new(envelope: Envelope, rate: u32, playing: bool) -> Self {
55        let floor = (f64::from(MIN_EDGE_SECONDS) * f64::from(rate))
56            .round()
57            .max(1.0) as u64;
58        let frames = |ms: f32| -> u64 {
59            if ms.is_nan() || ms <= 0.0 {
60                0
61            } else {
62                (f64::from(ms) * f64::from(rate) / 1000.0).round() as u64
63            }
64        };
65        let sustain = if envelope.sustain.is_nan() {
66            1.0
67        } else {
68            envelope.sustain.clamp(0.0, 1.0)
69        };
70        let mut state = Self {
71            shape: envelope.shape,
72            sustain,
73            attack: frames(envelope.attack_ms).max(floor),
74            decay: frames(envelope.decay_ms),
75            release: frames(envelope.release_ms).max(floor),
76            stage: Stage::Attack,
77            pos: 0,
78            from: 0.0,
79            level: 0.0,
80        };
81        if playing {
82            state.begin_decay();
83            state.level = 1.0;
84        }
85        state
86    }
87
88    fn begin_decay(&mut self) {
89        self.pos = 0;
90        self.from = 1.0;
91        self.stage = if self.decay == 0 {
92            Stage::Sustain
93        } else {
94            Stage::Decay
95        };
96    }
97
98    pub(crate) fn next(&mut self) -> f32 {
99        let level = match self.stage {
100            Stage::Attack => {
101                let level = self.ease(self.pos, self.attack);
102                self.pos += 1;
103                if self.pos >= self.attack {
104                    self.begin_decay();
105                }
106                level
107            }
108            Stage::Decay => {
109                let level =
110                    self.from + (self.sustain - self.from) * self.ease(self.pos, self.decay);
111                self.pos += 1;
112                if self.pos >= self.decay {
113                    self.stage = Stage::Sustain;
114                }
115                level
116            }
117            Stage::Sustain => self.sustain,
118            Stage::Release => {
119                let level = self.from * (1.0 - self.ease(self.pos, self.release));
120                self.pos += 1;
121                if self.pos >= self.release {
122                    self.stage = Stage::Done;
123                }
124                level
125            }
126            Stage::Done => 0.0,
127        };
128        self.level = level;
129        level
130    }
131
132    fn ease(&self, pos: u64, len: u64) -> f32 {
133        let p = (pos as f64 / len.max(1) as f64) as f32;
134        match self.shape {
135            Shape::Linear => p,
136            Shape::Exponential => {
137                (1.0 - pfx_core::sim::expf(-CURVE_RATE * p))
138                    / (1.0 - pfx_core::sim::expf(-CURVE_RATE))
139            }
140        }
141    }
142
143    pub(crate) fn release(&mut self, at_least: u64) {
144        if matches!(self.stage, Stage::Release | Stage::Done) {
145            return;
146        }
147        self.release = self.release.max(at_least);
148        self.from = self.level;
149        self.pos = 0;
150        self.stage = Stage::Release;
151    }
152
153    pub(crate) fn level(&self) -> f32 {
154        self.level
155    }
156
157    pub(crate) fn done(&self) -> bool {
158        self.stage == Stage::Done
159    }
160
161    pub(crate) fn releasing(&self) -> bool {
162        matches!(self.stage, Stage::Release | Stage::Done)
163    }
164}