From 3b48b889689fadbe16f2c5cbb5a62ca8b413c1d7 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Wed, 29 May 2024 13:30:51 +0200 Subject: [PATCH 1/3] Add `tail()` method to ring buffer For accessing the last element in the buffer --- src/utils/buffers/ring_buffer.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/buffers/ring_buffer.rs b/src/utils/buffers/ring_buffer.rs index 1b67a4b..f8ef1c6 100644 --- a/src/utils/buffers/ring_buffer.rs +++ b/src/utils/buffers/ring_buffer.rs @@ -97,6 +97,10 @@ impl RingBuffer { 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()); @@ -287,5 +291,6 @@ mod tests { rb.enqueue(6); rb.enqueue(7); assert_eq!(rb.peek(), 7); + assert_eq!(rb.tail(), 4); } } From 462afa5abf4f0fa6791a9b1bbfb9e0bc1b88b935 Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Wed, 29 May 2024 13:31:11 +0200 Subject: [PATCH 2/3] Add RMS buffer --- src/utils/buffers/mod.rs | 2 + src/utils/buffers/rms_buffer.rs | 139 ++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/utils/buffers/rms_buffer.rs diff --git a/src/utils/buffers/mod.rs b/src/utils/buffers/mod.rs index 45f8437..dd419f3 100644 --- a/src/utils/buffers/mod.rs +++ b/src/utils/buffers/mod.rs @@ -1,6 +1,7 @@ pub mod minima_buffer; pub mod peak_buffer; pub mod ring_buffer; +mod rms_buffer; pub mod waveform_buffer; use std::ops::{Index, IndexMut}; @@ -8,6 +9,7 @@ use std::ops::{Index, IndexMut}; 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: Index + IndexMut { diff --git a/src/utils/buffers/rms_buffer.rs b/src/utils/buffers/rms_buffer.rs new file mode 100644 index 0000000..bea3ae7 --- /dev/null +++ b/src/utils/buffers/rms_buffer.rs @@ -0,0 +1,139 @@ +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 amplitude 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, + /// 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, +} + +impl RMSBuffer { + pub fn new(size: usize, rms_duration: f32, duration: f32) -> Self { + Self { + buffer: RingBuffer::::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::::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 for RMSBuffer { + type Output = f32; + + fn index(&self, index: usize) -> &Self::Output { + &self.buffer[index] + } +} + +impl IndexMut for RMSBuffer { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.buffer[index] + } +} + +impl VisualizerBuffer 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) { + 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::(), + ); + } + } + } + } + + 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() + } +} From b95f18ead96b35de5967320ccce6cc2bb87fc20b Mon Sep 17 00:00:00 2001 From: luna <223230@proton.me> Date: Wed, 29 May 2024 14:14:06 +0200 Subject: [PATCH 3/3] Re-order RMS parameters --- src/utils/buffers/rms_buffer.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils/buffers/rms_buffer.rs b/src/utils/buffers/rms_buffer.rs index bea3ae7..62b2bc0 100644 --- a/src/utils/buffers/rms_buffer.rs +++ b/src/utils/buffers/rms_buffer.rs @@ -5,7 +5,8 @@ use super::{RingBuffer, VisualizerBuffer}; /// Stores RMS amplitudes over time. /// -/// This buffer keeps track of the windowed root mean squared amplitude of a signal. +/// 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! @@ -33,7 +34,12 @@ pub struct RMSBuffer { } impl RMSBuffer { - pub fn new(size: usize, rms_duration: f32, duration: f32) -> Self { + /// 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::::new(size), duration,