1use std::time::Duration;
76
77use num_derive::{FromPrimitive, ToPrimitive};
78
79use augmented_atomics::{AtomicEnum, AtomicF32};
80
81#[derive(Debug, FromPrimitive, ToPrimitive)]
82enum EnvelopeStage {
83 Idle,
84 Attack,
85 Decay,
86 Sustain,
87 Release,
88}
89
90struct StageConfig {
91 samples: AtomicF32,
92 duration_secs: AtomicF32,
93}
94
95impl Default for StageConfig {
96 fn default() -> Self {
97 StageConfig::new(0.0, Duration::from_secs_f32(0.0))
98 }
99}
100
101impl StageConfig {
102 fn new(samples: f32, duration: Duration) -> Self {
103 StageConfig {
104 samples: samples.into(),
105 duration_secs: duration.as_secs_f32().into(),
106 }
107 }
108
109 fn set_sample_rate(&self, sample_rate: f32) {
110 self.samples
111 .set(samples_for_duration(sample_rate, self.duration_secs.get()));
112 }
113
114 fn set_duration(&self, sample_rate: f32, duration: Duration) {
115 self.duration_secs.set(duration.as_secs_f32());
116 self.samples
117 .set(samples_for_duration(sample_rate, self.duration_secs.get()));
118 }
119}
120
121struct EnvelopeConfig {
122 attack: StageConfig,
123 attack_level: AtomicF32,
124 decay: StageConfig,
125 sustain: AtomicF32,
126 release: StageConfig,
127 sample_rate: AtomicF32,
128 is_exp: bool,
129}
130
131impl Default for EnvelopeConfig {
132 fn default() -> Self {
133 EnvelopeConfig {
134 attack: StageConfig::new(0.0, Duration::from_secs_f32(0.2)),
135 attack_level: 1.0.into(),
136 decay: StageConfig::new(0.0, Duration::from_secs_f32(0.3)),
137 sustain: 0.8.into(),
138 release: StageConfig::new(0.0, Duration::from_secs_f32(0.1)),
139 sample_rate: 0.0.into(),
140 is_exp: false,
141 }
142 }
143}
144
145impl EnvelopeConfig {
146 fn exp() -> Self {
147 Self {
148 is_exp: true,
149 ..Self::default()
150 }
151 }
152}
153
154struct EnvelopeState {
155 current_samples: AtomicF32,
156 stage_start_volume: AtomicF32,
157 current_volume: AtomicF32,
158}
159
160impl Default for EnvelopeState {
161 fn default() -> Self {
162 EnvelopeState {
163 current_samples: 0.0.into(),
164 stage_start_volume: 0.0.into(),
165 current_volume: 0.0.into(),
166 }
167 }
168}
169
170pub struct Envelope {
172 stage: AtomicEnum<EnvelopeStage>,
173 state: EnvelopeState,
174 config: EnvelopeConfig,
175}
176
177impl Default for Envelope {
178 fn default() -> Self {
179 Envelope::new()
180 }
181}
182
183impl Envelope {
184 pub fn new() -> Self {
186 Envelope {
187 stage: EnvelopeStage::Idle.into(),
188 state: EnvelopeState::default(),
189 config: EnvelopeConfig::default(),
190 }
191 }
192
193 pub fn exp() -> Self {
195 Envelope {
196 stage: EnvelopeStage::Idle.into(),
197 state: EnvelopeState::default(),
198 config: EnvelopeConfig::exp(),
199 }
200 }
201
202 pub fn set_sample_rate(&self, sample_rate: f32) {
204 self.config.sample_rate.set(sample_rate);
205 self.config.attack.set_sample_rate(sample_rate);
206 self.config.decay.set_sample_rate(sample_rate);
207 self.config.release.set_sample_rate(sample_rate);
208 }
209
210 pub fn set_attack(&self, duration: Duration) {
212 self.config
213 .attack
214 .set_duration(self.config.sample_rate.get(), duration);
215 }
216
217 pub fn set_decay(&self, duration: Duration) {
219 self.config
220 .decay
221 .set_duration(self.config.sample_rate.get(), duration);
222 }
223
224 pub fn set_sustain(&self, sustain: f32) {
226 self.config.sustain.set(sustain);
227 }
228
229 pub fn set_release(&self, duration: Duration) {
231 self.config
232 .release
233 .set_duration(self.config.sample_rate.get(), duration);
234 }
235
236 pub fn volume(&self) -> f32 {
238 self.update_stage(self.state.current_samples.get(), true);
239 self.state.current_volume.get()
240 }
241
242 pub fn tick(&self) {
244 let current_samples = self.state.current_samples.get() + 1.0;
245 self.state.current_samples.set(current_samples);
246 self.update_stage(current_samples, false);
247 }
248
249 fn update_stage(&self, current_samples: f32, recurse: bool) {
250 let maybe_stage_config =
252 match self.stage.get() {
253 EnvelopeStage::Idle => None,
254 EnvelopeStage::Attack => {
255 self.state.current_volume.set(self.calculate_volume(
256 self.config.attack_level.get(),
257 self.config.attack.samples.get(),
258 ));
259 Some(&self.config.attack)
260 }
261 EnvelopeStage::Decay => {
262 self.state.current_volume.set(self.calculate_volume(
263 self.config.sustain.get(),
264 self.config.decay.samples.get(),
265 ));
266 Some(&self.config.decay)
267 }
268 EnvelopeStage::Sustain => {
269 self.state.current_volume.set(self.config.sustain.get());
270 None
271 }
272 EnvelopeStage::Release => {
273 self.state
274 .current_volume
275 .set(self.calculate_volume(0.0, self.config.release.samples.get()));
276 Some(&self.config.release)
277 }
278 };
279
280 if let Some(stage_config) = maybe_stage_config {
281 if current_samples >= stage_config.samples.get() {
282 self.next_stage();
283
284 if recurse {
286 self.update_stage(current_samples, true);
287 }
288 }
289 }
290 }
291
292 pub fn note_on(&self) {
295 self.set_stage(EnvelopeStage::Attack);
296 }
297
298 pub fn note_off(&self) {
300 self.set_stage(EnvelopeStage::Release);
301 }
302
303 fn next_stage(&self) {
304 match self.stage.get() {
305 EnvelopeStage::Attack => {
306 self.state
307 .current_volume
308 .set(self.config.attack_level.get());
309 self.set_stage(EnvelopeStage::Decay);
310 }
311 EnvelopeStage::Decay => {
312 self.set_stage(EnvelopeStage::Sustain);
313 }
314 EnvelopeStage::Sustain => {
315 self.set_stage(EnvelopeStage::Release);
316 }
317 EnvelopeStage::Release => {
318 self.set_stage(EnvelopeStage::Idle);
319 }
320 EnvelopeStage::Idle => {}
321 }
322 }
323
324 fn set_stage(&self, stage: EnvelopeStage) {
325 self.state
326 .stage_start_volume
327 .set(self.state.current_volume.get());
328 self.state.current_samples.set(0.0);
329 self.stage.set(stage);
330 }
331
332 fn calculate_volume(&self, target: f32, duration_samples: f32) -> f32 {
333 let start = self.state.stage_start_volume.get();
334 let current_samples = self.state.current_samples.get();
335
336 if self.config.is_exp {
337 let current_volume = self.state.current_volume.get();
338 let a = std::f32::consts::E.powf(-1.0 / (duration_samples.max(f32::EPSILON) * 0.3));
339 return a * current_volume + (1.0 - a) * target;
340 }
341
342 let perc = current_samples / duration_samples.max(f32::EPSILON);
343 let diff = target - start;
344 start + perc * diff
345 }
346}
347
348fn samples_for_duration(sample_rate: f32, duration_secs: f32) -> f32 {
349 sample_rate * duration_secs
350}
351
352#[cfg(test)]
353mod test {
354 use std::path::Path;
355
356 use plotters::prelude::*;
357
358 use super::*;
359
360 #[test]
361 fn test_0_attack_envelope_with_decay() {
362 let envelope = Envelope::default();
363 envelope.set_sample_rate(44100.0);
364
365 envelope.set_attack(Duration::from_secs_f32(0.0));
366 envelope.set_decay(Duration::from_secs_f32(0.2));
367
368 let mut envelope_buffer = Vec::new();
369 envelope.note_on();
370 for i in 0..(samples_for_duration(44100.0, 2.0) as i32) {
371 envelope_buffer.push((i, envelope.volume()));
372 envelope.tick();
373 }
374 envelope.note_off();
375 let start = envelope_buffer.len() as i32;
376 for i in 0..(samples_for_duration(44100.0, 1.0) as i32) {
377 envelope_buffer.push((start + i, envelope.volume()));
378 envelope.tick();
379 }
380
381 generate_plot(envelope_buffer, "zero-attack-envelope-with-decay")
382 }
383
384 #[test]
385 fn test_0_attack_envelope() {
386 let envelope = Envelope::default();
387 envelope.set_sample_rate(44100.0);
388
389 envelope.set_attack(Duration::from_secs_f32(0.0));
390 envelope.set_decay(Duration::from_secs_f32(0.0));
391
392 let mut envelope_buffer = Vec::new();
393 envelope.note_on();
394 for i in 0..(samples_for_duration(44100.0, 2.0) as i32) {
395 envelope_buffer.push((i, envelope.volume()));
396 envelope.tick();
397 }
398 envelope.note_off();
399 let start = envelope_buffer.len() as i32;
400 for i in 0..(samples_for_duration(44100.0, 1.0) as i32) {
401 envelope_buffer.push((start + i, envelope.volume()));
402 envelope.tick();
403 }
404
405 generate_plot(envelope_buffer, "zero-attack-envelope")
406 }
407
408 #[test]
409 fn test_adsr_default_envelope() {
410 let envelope = Envelope::default();
411 envelope.set_sample_rate(44100.0);
412 envelope.set_attack(Duration::from_secs_f32(0.3));
413 envelope.set_release(Duration::from_secs_f32(0.3));
414 envelope.set_decay(Duration::from_secs_f32(0.3));
415
416 let mut envelope_buffer = Vec::new();
417 envelope.note_on();
418 for i in 0..(samples_for_duration(44100.0, 2.0) as i32) {
419 envelope_buffer.push((i, envelope.volume()));
420 envelope.tick();
421 }
422 envelope.note_off();
423 let start = envelope_buffer.len() as i32;
424 for i in 0..(samples_for_duration(44100.0, 1.0) as i32) {
425 envelope_buffer.push((start + i, envelope.volume()));
426 envelope.tick();
427 }
428
429 generate_plot(envelope_buffer, "default-envelope")
430 }
431
432 #[test]
433 fn test_adsr_exp_envelope() {
434 let envelope = Envelope::exp();
435 envelope.set_sample_rate(44100.0);
436 envelope.set_attack(Duration::from_secs_f32(0.3));
437 envelope.set_release(Duration::from_secs_f32(0.3));
438 envelope.set_decay(Duration::from_secs_f32(0.3));
439
440 let mut envelope_buffer = Vec::new();
441 envelope.note_on();
442 for i in 0..(samples_for_duration(44100.0, 2.0) as i32) {
443 envelope_buffer.push((i, envelope.volume()));
444 envelope.tick();
445 }
446 envelope.note_off();
447 let start = envelope_buffer.len() as i32;
448 for i in 0..(samples_for_duration(44100.0, 1.0) as i32) {
449 envelope_buffer.push((start + i, envelope.volume()));
450 envelope.tick();
451 }
452
453 generate_plot(envelope_buffer, "exp-envelope")
454 }
455
456 fn generate_plot(output: Vec<(i32, f32)>, plot_name: &str) {
457 let root_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
458 let filename = root_dir.join(format!("src/__plots__/{}.png", plot_name));
459 let plot_filename = Path::new(&filename);
460
461 let backend = BitMapBackend::new(plot_filename, (1000, 1000));
462 let drawing_area = backend.into_drawing_area();
463 drawing_area.fill(&WHITE).unwrap();
464
465 let mut chart = ChartBuilder::on(&drawing_area)
466 .caption(plot_name, ("sans-serif", 20))
467 .set_label_area_size(LabelAreaPosition::Left, 40)
468 .set_label_area_size(LabelAreaPosition::Bottom, 40)
469 .build_cartesian_2d(0.0..output.len() as f64, 0.0..1.2)
470 .unwrap();
471 chart.configure_mesh().draw().unwrap();
472
473 chart
474 .draw_series(LineSeries::new(
475 output.iter().map(|(x, y)| (*x as f64, *y as f64)),
476 RED,
477 ))
478 .unwrap();
479 drawing_area.present().unwrap();
480 }
481}