embedded_audio/
envelope.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
3pub struct AdsrSpec {
4 pub attack_ms: u16,
5 pub decay_ms: u16,
6 pub sustain_q8: u8,
7 pub release_ms: u16,
8}
9
10impl AdsrSpec {
11 pub const fn click() -> Self {
12 Self {
13 attack_ms: 2,
14 decay_ms: 40,
15 sustain_q8: 0,
16 release_ms: 10,
17 }
18 }
19
20 pub const fn pad() -> Self {
21 Self {
22 attack_ms: 5,
23 decay_ms: 80,
24 sustain_q8: 200,
25 release_ms: 120,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31enum AdsrPhase {
32 Attack,
33 Decay,
34 Sustain,
35 Release,
36 Idle,
37}
38
39#[derive(Debug, Clone, Copy)]
41pub struct Adsr {
42 spec: AdsrSpec,
43 phase: AdsrPhase,
44 level_q8: u8,
45 samples_in_phase: u32,
46 phase_length_samples: u32,
47 sample_rate_hz: u32,
48}
49
50impl Adsr {
51 pub const fn sample_rate_hz(self) -> u32 {
52 self.sample_rate_hz
53 }
54
55 pub const fn new(spec: AdsrSpec, sample_rate_hz: u32) -> Self {
56 Self {
57 spec,
58 phase: AdsrPhase::Idle,
59 level_q8: 0,
60 samples_in_phase: 0,
61 phase_length_samples: 0,
62 sample_rate_hz,
63 }
64 }
65
66 pub fn trigger(&mut self) {
67 self.phase = AdsrPhase::Attack;
68 self.level_q8 = 0;
69 self.samples_in_phase = 0;
70 self.phase_length_samples = ms_to_samples(self.spec.attack_ms, self.sample_rate_hz);
71 if self.phase_length_samples == 0 {
72 self.begin_decay();
73 }
74 }
75
76 pub fn release(&mut self) {
77 if matches!(self.phase, AdsrPhase::Idle) {
78 return;
79 }
80 self.phase = AdsrPhase::Release;
81 self.samples_in_phase = 0;
82 self.phase_length_samples = ms_to_samples(self.spec.release_ms, self.sample_rate_hz);
83 if self.phase_length_samples == 0 {
84 self.phase = AdsrPhase::Idle;
85 self.level_q8 = 0;
86 }
87 }
88
89 pub fn is_active(&self) -> bool {
90 !matches!(self.phase, AdsrPhase::Idle)
91 }
92
93 pub fn level_q8(&self) -> u8 {
95 self.level_q8
96 }
97
98 pub fn tick(&mut self) {
99 match self.phase {
100 AdsrPhase::Idle => {}
101 AdsrPhase::Attack => {
102 self.advance_toward(255);
103 if self.samples_in_phase >= self.phase_length_samples {
104 self.begin_decay();
105 }
106 }
107 AdsrPhase::Decay => {
108 self.advance_toward(self.spec.sustain_q8);
109 if self.samples_in_phase >= self.phase_length_samples {
110 self.phase = AdsrPhase::Sustain;
111 self.level_q8 = self.spec.sustain_q8;
112 self.samples_in_phase = 0;
113 }
114 }
115 AdsrPhase::Sustain => {}
116 AdsrPhase::Release => {
117 self.advance_toward(0);
118 if self.samples_in_phase >= self.phase_length_samples {
119 self.phase = AdsrPhase::Idle;
120 self.level_q8 = 0;
121 }
122 }
123 }
124 }
125
126 fn begin_decay(&mut self) {
127 self.phase = AdsrPhase::Decay;
128 self.samples_in_phase = 0;
129 self.phase_length_samples = ms_to_samples(self.spec.decay_ms, self.sample_rate_hz);
130 if self.phase_length_samples == 0 {
131 self.phase = AdsrPhase::Sustain;
132 self.level_q8 = self.spec.sustain_q8;
133 }
134 }
135
136 fn advance_toward(&mut self, target: u8) {
137 self.samples_in_phase += 1;
138 if self.phase_length_samples == 0 {
139 self.level_q8 = target;
140 return;
141 }
142 let start = self.level_q8 as i32;
143 let end = target as i32;
144 let t = (self.samples_in_phase as i32).min(self.phase_length_samples as i32);
145 let v = start + ((end - start) * t) / self.phase_length_samples as i32;
146 self.level_q8 = v.clamp(0, 255) as u8;
147 }
148}
149
150fn ms_to_samples(ms: u16, sample_rate_hz: u32) -> u32 {
151 if ms == 0 || sample_rate_hz == 0 {
152 return 0;
153 }
154 (ms as u32 * sample_rate_hz) / 1000
155}