use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use audio_garbage_collector::{Handle, Shared, SharedCell};
use audio_processor_traits::atomic_float::{AtomicFloatRepresentable, AtomicValue};
use audio_processor_traits::{AudioBuffer, AudioContext, AudioProcessor, Float};
pub type RunningRMSProcessorHandle = RunningRMSProcessorHandleImpl<f32>;
pub struct RunningRMSProcessorHandleImpl<ST: AtomicFloatRepresentable> {
window: SharedCell<AudioBuffer<ST::AtomicType>>,
running_sums: SharedCell<Vec<ST::AtomicType>>,
cursor: AtomicUsize,
duration_micros: AtomicUsize,
}
impl<ST> RunningRMSProcessorHandleImpl<ST>
where
ST: AtomicFloatRepresentable + Float,
ST::AtomicType: Send + Sync + Clone + 'static,
{
fn new(gc_handle: &Handle) -> Self {
RunningRMSProcessorHandleImpl {
window: SharedCell::new(Shared::new(gc_handle, AudioBuffer::empty())),
running_sums: SharedCell::new(Shared::new(gc_handle, Vec::new())),
cursor: AtomicUsize::new(0),
duration_micros: AtomicUsize::new(0),
}
}
fn cursor(&self) -> usize {
self.cursor.load(Ordering::Relaxed)
}
#[numeric_literals::replace_float_literals(ST::from(literal).unwrap())]
fn resize(&self, gc_handle: &Handle, num_channels: usize, num_samples: usize) {
self.cursor.store(0, Ordering::Relaxed);
let mut window = AudioBuffer::empty();
window.resize_with(num_channels, num_samples, || ST::AtomicType::from(0.0));
self.window.replace(Shared::new(gc_handle, window));
let mut running_sums = Vec::new();
running_sums.resize(num_channels, ST::AtomicType::from(0.0));
self.running_sums
.replace(Shared::new(gc_handle, running_sums));
}
#[numeric_literals::replace_float_literals(ST::from(literal).unwrap())]
pub fn calculate_rms(&self, channel: usize) -> ST {
let running_sums = self.running_sums.get();
if channel >= running_sums.len() {
return 0.0;
}
let sum = running_sums[channel].get().max(0.0);
let num_samples = ST::from(self.window.get().num_samples()).unwrap();
(sum / num_samples).sqrt()
}
}
pub type RunningRMSProcessor = RunningRMSProcessorImpl<f32>;
pub struct RunningRMSProcessorImpl<ST: AtomicFloatRepresentable + Float> {
handle: Shared<RunningRMSProcessorHandleImpl<ST>>,
duration_samples: usize,
duration: Duration,
gc_handle: Handle,
}
impl<ST> RunningRMSProcessorImpl<ST>
where
ST: AtomicFloatRepresentable + Float + 'static,
ST::AtomicType: Send + Sync + Clone + 'static,
{
pub fn new_with_duration(gc_handle: &Handle, duration: Duration) -> Self {
let handle = Shared::new(gc_handle, RunningRMSProcessorHandleImpl::new(gc_handle));
handle
.duration_micros
.store(duration.as_micros() as usize, Ordering::Relaxed);
RunningRMSProcessorImpl {
handle,
duration_samples: 0,
duration,
gc_handle: gc_handle.clone(),
}
}
pub fn from_handle(handle: Shared<RunningRMSProcessorHandleImpl<ST>>) -> Self {
let duration = Duration::from_micros(handle.duration_micros.load(Ordering::Relaxed) as u64);
Self {
gc_handle: audio_garbage_collector::handle().clone(),
handle,
duration_samples: 0,
duration,
}
}
pub fn handle(&self) -> &Shared<RunningRMSProcessorHandleImpl<ST>> {
&self.handle
}
}
impl<ST> AudioProcessor for RunningRMSProcessorImpl<ST>
where
ST: AtomicFloatRepresentable + Float + 'static,
ST::AtomicType: Send + Sync + Clone + 'static,
{
type SampleType = ST;
fn prepare(&mut self, context: &mut AudioContext) {
let settings = context.settings;
self.duration_samples = (settings.sample_rate() * self.duration.as_secs_f32()) as usize;
self.handle.resize(
&self.gc_handle,
settings.output_channels(),
self.duration_samples,
);
}
fn process(&mut self, _context: &mut AudioContext, buffer: &mut AudioBuffer<Self::SampleType>) {
if self.duration_samples == 0 {
return;
}
for sample_index in 0..buffer.num_samples() {
let running_sums = self.handle.running_sums.get();
let window = self.handle.window.get();
let mut cursor = self.handle.cursor();
for channel_index in 0..buffer.num_channels() {
let value_slot = window.get(channel_index, cursor);
let previous_value = value_slot.get();
let sample = *buffer.get(channel_index, sample_index);
let new_value = sample * sample; value_slot.set(new_value);
let running_sum_slot = &running_sums[channel_index];
let running_sum = running_sum_slot.get() + new_value - previous_value;
running_sum_slot.set(running_sum);
}
cursor += 1;
if cursor >= self.duration_samples {
cursor = 0;
}
self.handle.cursor.store(cursor, Ordering::Relaxed);
}
}
}
#[cfg(test)]
mod test {
use audio_garbage_collector::GarbageCollector;
use audio_processor_testing_helpers::assert_f_eq;
use super::*;
#[test]
fn test_create_handle() {
let gc = GarbageCollector::default();
let handle = RunningRMSProcessorHandle::new(gc.handle());
assert_f_eq!(handle.calculate_rms(0), 0.0);
}
#[test]
fn test_resize() {
let gc = GarbageCollector::default();
let handle = RunningRMSProcessorHandle::new(gc.handle());
handle.resize(gc.handle(), 2, 1000);
assert_eq!(handle.window.get().num_channels(), 2);
assert_eq!(handle.window.get().num_samples(), 1000);
assert_f_eq!(handle.calculate_rms(0), 0.0);
assert_f_eq!(handle.calculate_rms(1), 0.0);
assert_eq!(handle.cursor(), 0)
}
#[test]
fn test_create_running_rms_processor() {
let gc = GarbageCollector::default();
let mut processor =
RunningRMSProcessor::new_with_duration(gc.handle(), Duration::from_millis(10));
let mut context = AudioContext::default();
context.settings.sample_rate = 44100.0;
processor.prepare(&mut context);
assert_eq!(processor.duration_samples, 441);
assert_eq!(processor.duration, Duration::from_millis(10));
assert_eq!(
processor.handle.duration_micros.load(Ordering::Relaxed),
10_000
);
}
#[test]
fn test_create_running_rms_processor_from_handle() {
let gc = GarbageCollector::default();
let handle = RunningRMSProcessorHandle::new(gc.handle());
handle.resize(gc.handle(), 2, 1000);
let handle = Shared::new(gc.handle(), handle);
let processor = RunningRMSProcessor::from_handle(handle.clone());
assert_eq!(processor.duration_samples, 0);
assert_eq!(processor.duration, Duration::from_micros(0));
assert_eq!(processor.handle.duration_micros.load(Ordering::Relaxed), 0);
assert_eq!(
&*processor.handle().clone() as *const RunningRMSProcessorHandle,
&*handle as *const RunningRMSProcessorHandle
)
}
#[test]
fn test_audio_process_running() {
let gc = GarbageCollector::default();
let mut processor =
RunningRMSProcessor::new_with_duration(gc.handle(), Duration::from_millis(10));
let mut test_buffer = AudioBuffer::empty();
test_buffer.resize_with(2, 1000, || 1.0);
let mut context = AudioContext::default();
processor.prepare(&mut context);
processor.process(&mut context, &mut test_buffer);
let rms = processor.handle.calculate_rms(0);
assert!(rms > 0.0);
}
}