audio_processor_analysis/
envelope_follower_processor.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
24//! Envelope follower implementation
25//!
26//! ![](https://raw.githubusercontent.com/yamadapc/augmented-audio/master/crates/augmented/audio/audio-processor-analysis/audio-envelope.png)
27//!
28//! ## Usage
29//! ```
30//! use std::time::Duration;
31//! use audio_garbage_collector::Shared;
32//! use audio_processor_analysis::envelope_follower_processor::{EnvelopeFollowerHandle, EnvelopeFollowerProcessor};
33//! use audio_processor_traits::{AudioContext, AudioProcessorSettings, simple_processor::MonoAudioProcessor};
34//!
35//! let mut envelope_follower = EnvelopeFollowerProcessor::default();
36//! let handle: Shared<EnvelopeFollowerHandle> = envelope_follower.handle().clone();
37//! handle.set_attack(Duration::from_secs_f32(0.4));
38//!
39//! let mut context = AudioContext::from(AudioProcessorSettings::default());
40//! envelope_follower.m_prepare(&mut context);
41//! envelope_follower.m_process(&mut context, 0.0);
42//! ```
43
44use audio_garbage_collector::{make_shared, Shared};
45use audio_processor_traits::atomic_float::{AtomicFloatRepresentable, AtomicValue};
46use audio_processor_traits::simple_processor::MonoAudioProcessor;
47use audio_processor_traits::{AudioContext, AudioProcessorSettings, Float};
48use std::time::Duration;
49
50fn calculate_multiplier<F: Float>(sample_rate: F, duration_ms: F) -> F {
51    let attack_secs = duration_ms * F::from(0.001).unwrap();
52    let attack_samples = sample_rate * attack_secs;
53    (F::from(-1.0).unwrap() / attack_samples).exp2()
54}
55
56/// Envelope follower processor (f32)
57pub type EnvelopeFollowerProcessor = EnvelopeFollowerProcessorImpl<f32>;
58
59/// Envelope follower handle (f32)
60pub type EnvelopeFollowerHandle = EnvelopeFollowerHandleImpl<f32>;
61
62/// Handle for [`EnvelopeFollowerProcessorImpl`] use this to interact with the processor parameters from
63/// any thread.
64pub struct EnvelopeFollowerHandleImpl<ST: AtomicFloatRepresentable> {
65    envelope_state: ST::AtomicType,
66    attack_multiplier: ST::AtomicType,
67    release_multiplier: ST::AtomicType,
68    attack_duration_ms: ST::AtomicType,
69    release_duration_ms: ST::AtomicType,
70    sample_rate: ST::AtomicType,
71    _marker: std::marker::PhantomData<ST>,
72}
73
74impl<ST: Float + AtomicFloatRepresentable> EnvelopeFollowerHandleImpl<ST> {
75    /// Get the current envelope value
76    pub fn state(&self) -> ST {
77        ST::from(self.envelope_state.get()).unwrap()
78    }
79
80    /// Set the current envelope value
81    pub fn set_state(&self, state: ST) {
82        self.envelope_state.set(state);
83    }
84
85    /// Set the attack as a `Duration`
86    pub fn set_attack(&self, duration: Duration) {
87        let duration_ms = ST::from(duration.as_millis()).unwrap();
88        self.attack_duration_ms.set(duration_ms);
89        self.attack_multiplier
90            .set(calculate_multiplier(self.sample_rate.get(), duration_ms));
91    }
92
93    /// Set the release as a `Duration`
94    pub fn set_release(&self, duration: Duration) {
95        let duration_ms = ST::from(duration.as_millis()).unwrap();
96        self.release_duration_ms.set(duration_ms);
97        self.release_multiplier
98            .set(calculate_multiplier(self.sample_rate.get(), duration_ms));
99    }
100}
101
102/// An implementation of an envelope follower.
103///
104/// Implements [`audio_processor_traits::simple_processor::MonoAudioProcessor`]. Can either use it for per-sample
105/// processing or wrap this with [`audio_processor_traits::simple_processor::BufferProcessor`].
106///
107/// # Example
108/// ```rust
109/// use audio_processor_analysis::envelope_follower_processor::EnvelopeFollowerProcessorImpl;
110/// use audio_processor_traits::{AudioContext, AudioProcessorSettings, simple_processor::MonoAudioProcessor};
111///
112/// let mut  envelope_follower = EnvelopeFollowerProcessorImpl::default();
113/// let _handle = envelope_follower.handle(); // can send to another thread
114///
115/// // Envelope follower implements `MonoAudioProcessor
116/// let mut context = AudioContext::from(AudioProcessorSettings::default());
117/// envelope_follower.m_prepare(&mut context);
118/// envelope_follower.m_process(&mut context, 1.0);
119/// ```
120pub struct EnvelopeFollowerProcessorImpl<ST: AtomicFloatRepresentable> {
121    handle: Shared<EnvelopeFollowerHandleImpl<ST>>,
122}
123
124impl<ST> Default for EnvelopeFollowerProcessorImpl<ST>
125where
126    ST: AtomicFloatRepresentable + Float + Send + 'static,
127    ST::AtomicType: Send + 'static,
128{
129    fn default() -> Self {
130        Self::new(Duration::from_millis(10), Duration::from_millis(10))
131    }
132}
133
134impl<ST> EnvelopeFollowerProcessorImpl<ST>
135where
136    ST: AtomicFloatRepresentable + Float + Send + 'static,
137    ST::AtomicType: Send + 'static,
138{
139    /// Create a new `EnvelopeFollowerProcessorImpl` with this attack and release times.
140    pub fn new(attack_duration: Duration, release_duration: Duration) -> Self {
141        let sample_rate = AudioProcessorSettings::default().sample_rate as f64;
142        EnvelopeFollowerProcessorImpl {
143            handle: make_shared(EnvelopeFollowerHandleImpl {
144                envelope_state: ST::AtomicType::from(ST::zero()),
145                attack_multiplier: ST::AtomicType::from(
146                    ST::from(calculate_multiplier(
147                        sample_rate,
148                        attack_duration.as_millis() as f64,
149                    ))
150                    .unwrap(),
151                ),
152                release_multiplier: ST::AtomicType::from(
153                    ST::from(calculate_multiplier(
154                        sample_rate,
155                        release_duration.as_millis() as f64,
156                    ))
157                    .unwrap(),
158                ),
159                attack_duration_ms: (ST::AtomicType::from(
160                    ST::from(attack_duration.as_millis() as f64).unwrap(),
161                )),
162                release_duration_ms: ST::AtomicType::from(
163                    ST::from(release_duration.as_millis() as f64).unwrap(),
164                ),
165                sample_rate: ST::AtomicType::from(ST::from(sample_rate).unwrap()),
166                _marker: Default::default(),
167            }),
168        }
169    }
170
171    /// Get a reference to the `basedrop::Shared` state handle of this processor
172    pub fn handle(&self) -> &Shared<EnvelopeFollowerHandleImpl<ST>> {
173        &self.handle
174    }
175}
176
177impl<ST: AtomicFloatRepresentable + Copy + Float> MonoAudioProcessor
178    for EnvelopeFollowerProcessorImpl<ST>
179{
180    type SampleType = ST;
181
182    fn m_prepare(&mut self, context: &mut AudioContext) {
183        let sample_rate = ST::from(context.settings.sample_rate as f64).unwrap();
184        self.handle.sample_rate.set(sample_rate);
185        self.handle.attack_multiplier.set(calculate_multiplier(
186            sample_rate,
187            self.handle.attack_duration_ms.get(),
188        ));
189        self.handle.release_multiplier.set(calculate_multiplier(
190            sample_rate,
191            self.handle.release_duration_ms.get(),
192        ));
193    }
194
195    fn m_process(
196        &mut self,
197        _context: &mut AudioContext,
198        sample: Self::SampleType,
199    ) -> Self::SampleType {
200        let value = sample.abs();
201
202        let handle = &self.handle;
203        let envelope = ST::from(handle.envelope_state.get()).unwrap();
204        let attack = ST::from(handle.attack_multiplier.get()).unwrap();
205        let release = ST::from(handle.release_multiplier.get()).unwrap();
206
207        let one = ST::from(1.0).unwrap();
208        if value > envelope {
209            handle
210                .envelope_state
211                .set((one - attack) * value + attack * envelope);
212        } else {
213            handle
214                .envelope_state
215                .set((one - release) * value + release * envelope);
216        }
217
218        sample
219    }
220}
221
222#[cfg(test)]
223mod test {
224    use audio_processor_file::AudioFileProcessor;
225    use audio_processor_testing_helpers::charts::draw_vec_chart;
226    use audio_processor_testing_helpers::relative_path;
227    use audio_processor_traits::{AudioBuffer, AudioProcessor, AudioProcessorSettings};
228
229    use super::*;
230
231    #[test]
232    fn test_draw_envelope() {
233        let output_path = relative_path!("src/envelope_follower_processor");
234        let input_file_path = relative_path!("../../../../input-files/C3-loop.mp3");
235
236        let settings = AudioProcessorSettings::default();
237        let mut context = AudioContext::from(settings);
238        let mut input = AudioFileProcessor::from_path(
239            audio_garbage_collector::handle(),
240            settings,
241            &input_file_path,
242        )
243        .unwrap();
244        input.prepare(&mut context);
245
246        let mut envelope_follower = EnvelopeFollowerProcessor::default();
247        envelope_follower.m_prepare(&mut context);
248
249        let mut buffer = AudioBuffer::empty();
250        buffer.resize(1, settings.block_size());
251        let num_chunks = (input.num_samples() / 8) / settings.block_size();
252
253        let mut envelope_readings = vec![];
254        for _chunk in 0..num_chunks {
255            for sample in buffer.slice_mut() {
256                *sample = 0.0;
257            }
258
259            input.process(&mut context, &mut buffer);
260            for sample_num in 0..buffer.num_samples() {
261                let sample = *buffer.get(0, sample_num);
262                envelope_follower.m_process(&mut context, sample);
263                envelope_readings.push(envelope_follower.handle.envelope_state.get());
264            }
265        }
266
267        draw_vec_chart(&output_path, "Envelope", envelope_readings);
268    }
269}