audio_processor_analysis/
running_rms_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
24use std::sync::atomic::{AtomicUsize, Ordering};
25use std::time::Duration;
26
27use audio_garbage_collector::{Handle, Shared, SharedCell};
28use audio_processor_traits::atomic_float::{AtomicFloatRepresentable, AtomicValue};
29use audio_processor_traits::{AudioBuffer, AudioContext, AudioProcessor, Float};
30
31pub type RunningRMSProcessorHandle = RunningRMSProcessorHandleImpl<f32>;
32
33/// A shared "processor handle" to `RunningRMSProcessor`
34pub struct RunningRMSProcessorHandleImpl<ST: AtomicFloatRepresentable> {
35    window: SharedCell<AudioBuffer<ST::AtomicType>>,
36    running_sums: SharedCell<Vec<ST::AtomicType>>,
37    cursor: AtomicUsize,
38    duration_micros: AtomicUsize,
39}
40
41impl<ST> RunningRMSProcessorHandleImpl<ST>
42where
43    ST: AtomicFloatRepresentable + Float,
44    ST::AtomicType: Send + Sync + Clone + 'static,
45{
46    /// Create a new handle with empty buffers
47    fn new(gc_handle: &Handle) -> Self {
48        RunningRMSProcessorHandleImpl {
49            window: SharedCell::new(Shared::new(gc_handle, AudioBuffer::empty())),
50            running_sums: SharedCell::new(Shared::new(gc_handle, Vec::new())),
51            cursor: AtomicUsize::new(0),
52            duration_micros: AtomicUsize::new(0),
53        }
54    }
55
56    fn cursor(&self) -> usize {
57        self.cursor.load(Ordering::Relaxed)
58    }
59
60    /// Create a new RMS window with size & replace the old one with it.
61    #[numeric_literals::replace_float_literals(ST::from(literal).unwrap())]
62    fn resize(&self, gc_handle: &Handle, num_channels: usize, num_samples: usize) {
63        self.cursor.store(0, Ordering::Relaxed);
64
65        let mut window = AudioBuffer::empty();
66        window.resize_with(num_channels, num_samples, || ST::AtomicType::from(0.0));
67        self.window.replace(Shared::new(gc_handle, window));
68
69        let mut running_sums = Vec::new();
70        running_sums.resize(num_channels, ST::AtomicType::from(0.0));
71        self.running_sums
72            .replace(Shared::new(gc_handle, running_sums));
73    }
74
75    /// Calculate the RMS of the current window based on its running sum and size
76    #[numeric_literals::replace_float_literals(ST::from(literal).unwrap())]
77    pub fn calculate_rms(&self, channel: usize) -> ST {
78        let running_sums = self.running_sums.get();
79        if channel >= running_sums.len() {
80            return 0.0;
81        }
82
83        let sum = running_sums[channel].get().max(0.0);
84        let num_samples = ST::from(self.window.get().num_samples()).unwrap();
85        (sum / num_samples).sqrt()
86    }
87}
88
89pub type RunningRMSProcessor = RunningRMSProcessorImpl<f32>;
90
91/// An `AudioProcessor` which slides a window & calculates a running Squared sum of the input.
92///
93/// It exposes a `RunningRMSProcessorHandle` which may be called from any thread to get the current
94/// RMS in real-time.
95///
96/// When the internal window's buffer needs to be resized, it's replaced via an atomic pointer swap.
97pub struct RunningRMSProcessorImpl<ST: AtomicFloatRepresentable + Float> {
98    handle: Shared<RunningRMSProcessorHandleImpl<ST>>,
99    duration_samples: usize,
100    duration: Duration,
101    gc_handle: Handle,
102}
103
104impl<ST> RunningRMSProcessorImpl<ST>
105where
106    ST: AtomicFloatRepresentable + Float + 'static,
107    ST::AtomicType: Send + Sync + Clone + 'static,
108{
109    /// Create a `RunningRMSProcessor` which will calculate RMS based on a certain `duration` of
110    /// samples.
111    pub fn new_with_duration(gc_handle: &Handle, duration: Duration) -> Self {
112        let handle = Shared::new(gc_handle, RunningRMSProcessorHandleImpl::new(gc_handle));
113        handle
114            .duration_micros
115            .store(duration.as_micros() as usize, Ordering::Relaxed);
116
117        RunningRMSProcessorImpl {
118            handle,
119            duration_samples: 0,
120            duration,
121            gc_handle: gc_handle.clone(),
122        }
123    }
124
125    pub fn from_handle(handle: Shared<RunningRMSProcessorHandleImpl<ST>>) -> Self {
126        let duration = Duration::from_micros(handle.duration_micros.load(Ordering::Relaxed) as u64);
127        Self {
128            gc_handle: audio_garbage_collector::handle().clone(),
129            handle,
130            duration_samples: 0,
131            duration,
132        }
133    }
134
135    pub fn handle(&self) -> &Shared<RunningRMSProcessorHandleImpl<ST>> {
136        &self.handle
137    }
138}
139
140impl<ST> AudioProcessor for RunningRMSProcessorImpl<ST>
141where
142    ST: AtomicFloatRepresentable + Float + 'static,
143    ST::AtomicType: Send + Sync + Clone + 'static,
144{
145    type SampleType = ST;
146
147    fn prepare(&mut self, context: &mut AudioContext) {
148        let settings = context.settings;
149        self.duration_samples = (settings.sample_rate() * self.duration.as_secs_f32()) as usize;
150        self.handle.resize(
151            &self.gc_handle,
152            settings.output_channels(),
153            self.duration_samples,
154        );
155    }
156
157    fn process(&mut self, _context: &mut AudioContext, buffer: &mut AudioBuffer<Self::SampleType>) {
158        if self.duration_samples == 0 {
159            return;
160        }
161
162        for sample_index in 0..buffer.num_samples() {
163            let running_sums = self.handle.running_sums.get();
164            let window = self.handle.window.get();
165            let mut cursor = self.handle.cursor();
166
167            for channel_index in 0..buffer.num_channels() {
168                let value_slot = window.get(channel_index, cursor);
169                let previous_value = value_slot.get();
170
171                let sample = *buffer.get(channel_index, sample_index);
172                let new_value = sample * sample; // using square rather than abs is around 1% faster
173                value_slot.set(new_value);
174
175                let running_sum_slot = &running_sums[channel_index];
176                let running_sum = running_sum_slot.get() + new_value - previous_value;
177                running_sum_slot.set(running_sum);
178            }
179
180            cursor += 1;
181            if cursor >= self.duration_samples {
182                cursor = 0;
183            }
184            self.handle.cursor.store(cursor, Ordering::Relaxed);
185        }
186    }
187}
188
189#[cfg(test)]
190mod test {
191    use audio_garbage_collector::GarbageCollector;
192    use audio_processor_testing_helpers::assert_f_eq;
193
194    use super::*;
195
196    #[test]
197    fn test_create_handle() {
198        let gc = GarbageCollector::default();
199        let handle = RunningRMSProcessorHandle::new(gc.handle());
200
201        assert_f_eq!(handle.calculate_rms(0), 0.0);
202    }
203
204    #[test]
205    fn test_resize() {
206        let gc = GarbageCollector::default();
207        let handle = RunningRMSProcessorHandle::new(gc.handle());
208
209        handle.resize(gc.handle(), 2, 1000);
210        assert_eq!(handle.window.get().num_channels(), 2);
211        assert_eq!(handle.window.get().num_samples(), 1000);
212        assert_f_eq!(handle.calculate_rms(0), 0.0);
213        assert_f_eq!(handle.calculate_rms(1), 0.0);
214        assert_eq!(handle.cursor(), 0)
215    }
216
217    #[test]
218    fn test_create_running_rms_processor() {
219        let gc = GarbageCollector::default();
220        let mut processor =
221            RunningRMSProcessor::new_with_duration(gc.handle(), Duration::from_millis(10));
222
223        let mut context = AudioContext::default();
224        context.settings.sample_rate = 44100.0;
225        processor.prepare(&mut context);
226
227        assert_eq!(processor.duration_samples, 441);
228        assert_eq!(processor.duration, Duration::from_millis(10));
229        assert_eq!(
230            processor.handle.duration_micros.load(Ordering::Relaxed),
231            10_000
232        );
233    }
234
235    #[test]
236    fn test_create_running_rms_processor_from_handle() {
237        let gc = GarbageCollector::default();
238        let handle = RunningRMSProcessorHandle::new(gc.handle());
239        handle.resize(gc.handle(), 2, 1000);
240        let handle = Shared::new(gc.handle(), handle);
241        let processor = RunningRMSProcessor::from_handle(handle.clone());
242
243        assert_eq!(processor.duration_samples, 0);
244        assert_eq!(processor.duration, Duration::from_micros(0));
245        assert_eq!(processor.handle.duration_micros.load(Ordering::Relaxed), 0);
246        assert_eq!(
247            &*processor.handle().clone() as *const RunningRMSProcessorHandle,
248            &*handle as *const RunningRMSProcessorHandle
249        )
250    }
251
252    #[test]
253    fn test_audio_process_running() {
254        let gc = GarbageCollector::default();
255        let mut processor =
256            RunningRMSProcessor::new_with_duration(gc.handle(), Duration::from_millis(10));
257        let mut test_buffer = AudioBuffer::empty();
258
259        test_buffer.resize_with(2, 1000, || 1.0);
260        let mut context = AudioContext::default();
261        processor.prepare(&mut context);
262        processor.process(&mut context, &mut test_buffer);
263        let rms = processor.handle.calculate_rms(0);
264        assert!(rms > 0.0);
265    }
266}