Skip to content

Commit

Permalink
Merge pull request #56 from 223230/feature-rms
Browse files Browse the repository at this point in the history
RMS buffer
  • Loading branch information
exa04 authored Jun 6, 2024
2 parents c806574 + b95f18e commit c6f305a
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/utils/buffers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod histogram_buffer;
pub mod minima_buffer;
pub mod peak_buffer;
pub mod ring_buffer;
mod rms_buffer;
pub mod waveform_buffer;

use std::ops::{Index, IndexMut};
Expand All @@ -10,6 +11,7 @@ pub use histogram_buffer::HistogramBuffer;
pub use minima_buffer::MinimaBuffer;
pub use peak_buffer::PeakBuffer;
pub use ring_buffer::RingBuffer;
pub use rms_buffer::RMSBuffer;
pub use waveform_buffer::WaveformBuffer;

pub trait VisualizerBuffer<T>: Index<usize> + IndexMut<usize> {
Expand Down
5 changes: 5 additions & 0 deletions src/utils/buffers/ring_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ impl<T: Default + Copy> RingBuffer<T> {
self.data[(self.size + self.head - 1) % self.size]
}

pub fn tail(self: &Self) -> T {
self.data[(self.size + self.head) % self.size]
}

/// Clears the entire buffer, filling it with default values (usually 0)
pub fn clear(self: &mut Self) {
self.data.iter_mut().for_each(|x| *x = T::default());
Expand Down Expand Up @@ -287,5 +291,6 @@ mod tests {
rb.enqueue(6);
rb.enqueue(7);
assert_eq!(rb.peek(), 7);
assert_eq!(rb.tail(), 4);
}
}
145 changes: 145 additions & 0 deletions src/utils/buffers/rms_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use nih_plug::buffer::Buffer;
use std::ops::{Index, IndexMut};

use super::{RingBuffer, VisualizerBuffer};

/// Stores RMS amplitudes over time.
///
/// This buffer keeps track of the windowed root mean squared amplitudes of a
/// signal.
///
/// It needs to be provided a sample rate after initialization - do this inside your
/// [`initialize()`](nih_plug::plugin::Plugin::initialize)` function!
#[derive(Clone, Default)]
pub struct RMSBuffer {
buffer: RingBuffer<f32>,
/// The duration of RMS values that the buffer captures, in s (example: 10.0)
duration: f32,
/// The time window in which the RMS is calculated, in ms (example: 300.0)
rms_duration: f32,

/// The sample rate (example: 44100.0)
sample_rate: f32,
/// The current time
t: f32,
/// The squared sum accumulator - When a sample gets enqueued, its squared value
/// is added into this. When it gets removed, its squared value is removed from
/// here.
sum_acc: f32,
/// The time it takes (in samples) for an RMS value to get enqueued
sample_delta: f32,
/// The buffer of squared sums - This is needed so that the squared samples can
/// be removed from the `sum_acc`
squared_buffer: RingBuffer<f32>,
}

impl RMSBuffer {
/// Creates a new RMSBuffer
///
/// * `size` - The length of the buffer in samples
/// * `duration` - The duration (in seconds) of the RMS data inside the buffer, in seconds
/// * `rms_duration` - The duration of each RMS window, in milliseconds
pub fn new(size: usize, duration: f32, rms_duration: f32) -> Self {
Self {
buffer: RingBuffer::<f32>::new(size),
duration,
rms_duration,

// These values will be needed internally.
sample_delta: 0.0,
t: 0.0,
sum_acc: 0.0,
sample_rate: 0.0,
squared_buffer: RingBuffer::<f32>::new(0),
}
}

pub fn set_sample_rate(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.update();
}

fn update(&mut self) {
self.sample_delta =
((self.sample_rate as f64 * self.duration as f64) / self.buffer.len() as f64) as f32;

let rms_size = (self.sample_rate as f64 * (self.rms_duration as f64 / 1000.0)) as usize;
self.squared_buffer.resize(rms_size);

self.clear();
}
}

impl Index<usize> for RMSBuffer {
type Output = f32;

fn index(&self, index: usize) -> &Self::Output {
&self.buffer[index]
}
}

impl IndexMut<usize> for RMSBuffer {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.buffer[index]
}
}

impl VisualizerBuffer<f32> for RMSBuffer {
fn enqueue(self: &mut Self, value: f32) {
let squared_value = value * value;

self.sum_acc -= self.squared_buffer.tail();
self.squared_buffer.enqueue(squared_value);
self.sum_acc += squared_value;

self.t -= 1.0;

if self.t <= 0.0 {
let rms = (self.sum_acc / self.squared_buffer.len() as f32).sqrt();
if rms.is_nan() {
self.buffer.enqueue(0.0);
} else {
self.buffer.enqueue(rms);
}
self.t += self.sample_delta
}
}

fn enqueue_buffer(self: &mut Self, buffer: &mut Buffer, channel: Option<usize>) {
match channel {
Some(channel) => {
for sample in buffer.as_slice()[channel].into_iter() {
self.enqueue(*sample);
}
}
None => {
for sample in buffer.iter_samples() {
self.enqueue(
(1. / (&sample).len() as f32) * sample.into_iter().map(|x| *x).sum::<f32>(),
);
}
}
}
}

fn clear(self: &mut Self) {
self.sum_acc = 0.0;
self.t = self.sample_delta;
self.buffer.clear();
self.squared_buffer.clear();
}

fn grow(self: &mut Self, size: usize) {
self.clear();
self.buffer.grow(size);
}

fn shrink(self: &mut Self, size: usize) {
self.clear();
self.buffer.shrink(size);
}

fn len(self: &Self) -> usize {
self.buffer.len()
}
}

0 comments on commit c6f305a

Please sign in to comment.