Skip to content

Commit c6f305a

Browse files
authored
Merge pull request #56 from 223230/feature-rms
RMS buffer
2 parents c806574 + b95f18e commit c6f305a

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

src/utils/buffers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod histogram_buffer;
22
pub mod minima_buffer;
33
pub mod peak_buffer;
44
pub mod ring_buffer;
5+
mod rms_buffer;
56
pub mod waveform_buffer;
67

78
use std::ops::{Index, IndexMut};
@@ -10,6 +11,7 @@ pub use histogram_buffer::HistogramBuffer;
1011
pub use minima_buffer::MinimaBuffer;
1112
pub use peak_buffer::PeakBuffer;
1213
pub use ring_buffer::RingBuffer;
14+
pub use rms_buffer::RMSBuffer;
1315
pub use waveform_buffer::WaveformBuffer;
1416

1517
pub trait VisualizerBuffer<T>: Index<usize> + IndexMut<usize> {

src/utils/buffers/ring_buffer.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ impl<T: Default + Copy> RingBuffer<T> {
9797
self.data[(self.size + self.head - 1) % self.size]
9898
}
9999

100+
pub fn tail(self: &Self) -> T {
101+
self.data[(self.size + self.head) % self.size]
102+
}
103+
100104
/// Clears the entire buffer, filling it with default values (usually 0)
101105
pub fn clear(self: &mut Self) {
102106
self.data.iter_mut().for_each(|x| *x = T::default());
@@ -287,5 +291,6 @@ mod tests {
287291
rb.enqueue(6);
288292
rb.enqueue(7);
289293
assert_eq!(rb.peek(), 7);
294+
assert_eq!(rb.tail(), 4);
290295
}
291296
}

src/utils/buffers/rms_buffer.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use nih_plug::buffer::Buffer;
2+
use std::ops::{Index, IndexMut};
3+
4+
use super::{RingBuffer, VisualizerBuffer};
5+
6+
/// Stores RMS amplitudes over time.
7+
///
8+
/// This buffer keeps track of the windowed root mean squared amplitudes of a
9+
/// signal.
10+
///
11+
/// It needs to be provided a sample rate after initialization - do this inside your
12+
/// [`initialize()`](nih_plug::plugin::Plugin::initialize)` function!
13+
#[derive(Clone, Default)]
14+
pub struct RMSBuffer {
15+
buffer: RingBuffer<f32>,
16+
/// The duration of RMS values that the buffer captures, in s (example: 10.0)
17+
duration: f32,
18+
/// The time window in which the RMS is calculated, in ms (example: 300.0)
19+
rms_duration: f32,
20+
21+
/// The sample rate (example: 44100.0)
22+
sample_rate: f32,
23+
/// The current time
24+
t: f32,
25+
/// The squared sum accumulator - When a sample gets enqueued, its squared value
26+
/// is added into this. When it gets removed, its squared value is removed from
27+
/// here.
28+
sum_acc: f32,
29+
/// The time it takes (in samples) for an RMS value to get enqueued
30+
sample_delta: f32,
31+
/// The buffer of squared sums - This is needed so that the squared samples can
32+
/// be removed from the `sum_acc`
33+
squared_buffer: RingBuffer<f32>,
34+
}
35+
36+
impl RMSBuffer {
37+
/// Creates a new RMSBuffer
38+
///
39+
/// * `size` - The length of the buffer in samples
40+
/// * `duration` - The duration (in seconds) of the RMS data inside the buffer, in seconds
41+
/// * `rms_duration` - The duration of each RMS window, in milliseconds
42+
pub fn new(size: usize, duration: f32, rms_duration: f32) -> Self {
43+
Self {
44+
buffer: RingBuffer::<f32>::new(size),
45+
duration,
46+
rms_duration,
47+
48+
// These values will be needed internally.
49+
sample_delta: 0.0,
50+
t: 0.0,
51+
sum_acc: 0.0,
52+
sample_rate: 0.0,
53+
squared_buffer: RingBuffer::<f32>::new(0),
54+
}
55+
}
56+
57+
pub fn set_sample_rate(&mut self, sample_rate: f32) {
58+
self.sample_rate = sample_rate;
59+
self.update();
60+
}
61+
62+
fn update(&mut self) {
63+
self.sample_delta =
64+
((self.sample_rate as f64 * self.duration as f64) / self.buffer.len() as f64) as f32;
65+
66+
let rms_size = (self.sample_rate as f64 * (self.rms_duration as f64 / 1000.0)) as usize;
67+
self.squared_buffer.resize(rms_size);
68+
69+
self.clear();
70+
}
71+
}
72+
73+
impl Index<usize> for RMSBuffer {
74+
type Output = f32;
75+
76+
fn index(&self, index: usize) -> &Self::Output {
77+
&self.buffer[index]
78+
}
79+
}
80+
81+
impl IndexMut<usize> for RMSBuffer {
82+
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
83+
&mut self.buffer[index]
84+
}
85+
}
86+
87+
impl VisualizerBuffer<f32> for RMSBuffer {
88+
fn enqueue(self: &mut Self, value: f32) {
89+
let squared_value = value * value;
90+
91+
self.sum_acc -= self.squared_buffer.tail();
92+
self.squared_buffer.enqueue(squared_value);
93+
self.sum_acc += squared_value;
94+
95+
self.t -= 1.0;
96+
97+
if self.t <= 0.0 {
98+
let rms = (self.sum_acc / self.squared_buffer.len() as f32).sqrt();
99+
if rms.is_nan() {
100+
self.buffer.enqueue(0.0);
101+
} else {
102+
self.buffer.enqueue(rms);
103+
}
104+
self.t += self.sample_delta
105+
}
106+
}
107+
108+
fn enqueue_buffer(self: &mut Self, buffer: &mut Buffer, channel: Option<usize>) {
109+
match channel {
110+
Some(channel) => {
111+
for sample in buffer.as_slice()[channel].into_iter() {
112+
self.enqueue(*sample);
113+
}
114+
}
115+
None => {
116+
for sample in buffer.iter_samples() {
117+
self.enqueue(
118+
(1. / (&sample).len() as f32) * sample.into_iter().map(|x| *x).sum::<f32>(),
119+
);
120+
}
121+
}
122+
}
123+
}
124+
125+
fn clear(self: &mut Self) {
126+
self.sum_acc = 0.0;
127+
self.t = self.sample_delta;
128+
self.buffer.clear();
129+
self.squared_buffer.clear();
130+
}
131+
132+
fn grow(self: &mut Self, size: usize) {
133+
self.clear();
134+
self.buffer.grow(size);
135+
}
136+
137+
fn shrink(self: &mut Self, size: usize) {
138+
self.clear();
139+
self.buffer.shrink(size);
140+
}
141+
142+
fn len(self: &Self) -> usize {
143+
self.buffer.len()
144+
}
145+
}

0 commit comments

Comments
 (0)