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}