augmented_adsr_envelope/
lib.rs

1// Augmented Audio: Audio libraries and applications
2// Copyright (c) 2022 Pedro Tacla Yamada
3//
4// The MIT License (MIT)
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22// THE SOFTWARE.
23//! [![crates.io](https://img.shields.io/crates/v/adsr-envelope.svg)](https://crates.io/crates/adsr-envelope)
24//! [![docs.rs](https://docs.rs/adsr-envelope/badge.svg)](https://docs.rs/adsr-envelope/)
25//! - - -
26//! Implementation of an ADSR envelope.
27//!
28//! # Basic usage
29//!
30//! ```rust
31//! use std::time::Duration;
32//!
33//! use augmented_adsr_envelope::Envelope;
34//!
35//! // Create an exponential envelope.
36//! // The envelope configuration uses atomics, so it doesn't need
37//! // to be an immutable reference.
38//! let envelope = Envelope::exp();
39//!
40//! // Set settings
41//! envelope.set_sample_rate(1000.0);
42//! envelope.set_attack(Duration::from_millis(200));
43//!
44//! // Trigger the envelope
45//! envelope.note_on();
46//! for i in 0..10000 {
47//!   // Tick the envelope by 1 sample
48//!   envelope.tick();
49//!   // Get the current volume
50//!   let _volume = envelope.volume();
51//! }
52//! // Trigger the release stage
53//! envelope.note_off();
54//! ```
55//!
56//! # Plots
57//! `Envelope::default();`
58//! ------------------
59//! | Attack  | 0.3  |
60//! | Decay   | 0.3  |
61//! | Sustain | 0.8  |
62//! | Release | 0.3  |
63//! ------------------
64//! ![](https://raw.githubusercontent.com/yamadapc/augmented-audio/master/crates/augmented/audio/adsr-envelope/src/__plots__/default-envelope.png)
65//!
66//! `Envelope::exp();`
67//! ------------------
68//! | Attack  | 0.3  |
69//! | Decay   | 0.3  |
70//! | Sustain | 0.8  |
71//! | Release | 0.3  |
72//! ------------------
73//! ![](https://raw.githubusercontent.com/yamadapc/augmented-audio/master/crates/augmented/audio/adsr-envelope/src/__plots__/exp-envelope.png)
74
75use 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
170/// An ADSR envelope implementation
171pub 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    /// Create a linear envelope with default configuration
185    pub fn new() -> Self {
186        Envelope {
187            stage: EnvelopeStage::Idle.into(),
188            state: EnvelopeState::default(),
189            config: EnvelopeConfig::default(),
190        }
191    }
192
193    /// Create an exponential envelope with default configuration
194    pub fn exp() -> Self {
195        Envelope {
196            stage: EnvelopeStage::Idle.into(),
197            state: EnvelopeState::default(),
198            config: EnvelopeConfig::exp(),
199        }
200    }
201
202    /// Set the envelope sample rate, required before playback
203    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    /// Set the envelope sample rate, required before playback
211    pub fn set_attack(&self, duration: Duration) {
212        self.config
213            .attack
214            .set_duration(self.config.sample_rate.get(), duration);
215    }
216
217    /// Set the envelope decay time
218    pub fn set_decay(&self, duration: Duration) {
219        self.config
220            .decay
221            .set_duration(self.config.sample_rate.get(), duration);
222    }
223
224    /// Set the envelope sustain time
225    pub fn set_sustain(&self, sustain: f32) {
226        self.config.sustain.set(sustain);
227    }
228
229    /// Set the envelope release time
230    pub fn set_release(&self, duration: Duration) {
231        self.config
232            .release
233            .set_duration(self.config.sample_rate.get(), duration);
234    }
235
236    /// Get the current volume multiplier
237    pub fn volume(&self) -> f32 {
238        self.update_stage(self.state.current_samples.get(), true);
239        self.state.current_volume.get()
240    }
241
242    /// Update the envelope, pushing its state forwards by 1 sample
243    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        // println!("update_stage(current_samples={})", current_samples);
251        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                // Purpose is to handle 0 value envelopes
285                if recurse {
286                    self.update_stage(current_samples, true);
287                }
288            }
289        }
290    }
291
292    /// Trigger the envelope by setting its stage to the Attack phase. Does not change the current
293    /// volume, only the stage.
294    pub fn note_on(&self) {
295        self.set_stage(EnvelopeStage::Attack);
296    }
297
298    /// Set the envelope stage to release.
299    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}