dasp_rms/lib.rs
1//! Root mean square calculation over a signal.
2//!
3//! The primary type of interest in this module is the [**Rms**](./struct.Rms).
4
5#![cfg_attr(not(feature = "std"), no_std)]
6
7use core::fmt;
8use core::marker::PhantomData;
9use dasp_frame::Frame;
10use dasp_ring_buffer as ring_buffer;
11use dasp_sample::{FloatSample, Sample};
12
13/// Iteratively extracts the RMS (root mean square) envelope from a window over a signal of sample
14/// `Frame`s.
15#[derive(Clone)]
16pub struct Rms<F, S>
17where
18 F: Frame,
19 S: ring_buffer::Slice<Element = F::Float>,
20{
21 /// The type of `Frame`s for which the RMS will be calculated.
22 frame: PhantomData<F>,
23 /// The ringbuffer of frame sample squares (i.e. `sample * sample`) used to calculate the RMS
24 /// per frame.
25 ///
26 /// When a new frame is received, the **Rms** pops the front sample_square and adds the new
27 /// sample_square to the back.
28 window: ring_buffer::Fixed<S>,
29 /// The sum total of all sample_squares currently within the **Rms**'s `window` ring buffer.
30 square_sum: F::Float,
31}
32
33impl<F, S> Rms<F, S>
34where
35 F: Frame,
36 S: ring_buffer::Slice<Element = F::Float>,
37{
38 /// Construct a new **Rms** that uses the given ring buffer as its window.
39 ///
40 /// The window size of the **Rms** is equal to the length of the given ring buffer.
41 ///
42 /// ```
43 /// use dasp_ring_buffer as ring_buffer;
44 /// use dasp_rms::Rms;
45 ///
46 /// fn main() {
47 /// let window = ring_buffer::Fixed::from([[0.0]; 4]);
48 /// let mut rms = Rms::new(window);
49 /// rms.next([0.5]);
50 /// }
51 /// ```
52 pub fn new(ring_buffer: ring_buffer::Fixed<S>) -> Self {
53 Rms {
54 frame: PhantomData,
55 window: ring_buffer,
56 square_sum: Frame::EQUILIBRIUM,
57 }
58 }
59
60 /// Zeroes the square_sum and the buffer of the `window`.
61 ///
62 /// ```
63 /// use dasp_ring_buffer as ring_buffer;
64 /// use dasp_rms::Rms;
65 ///
66 /// fn main() {
67 /// let window = ring_buffer::Fixed::from([[0.0]; 4]);
68 /// let mut rms = Rms::new(window);
69 /// rms.next([0.6]);
70 /// rms.next([0.9]);
71 /// rms.reset();
72 /// assert_eq!(rms.current(), [0.0]);
73 /// }
74 /// ```
75 pub fn reset(&mut self)
76 where
77 S: ring_buffer::SliceMut,
78 {
79 for sample_square in self.window.iter_mut() {
80 *sample_square = Frame::EQUILIBRIUM;
81 }
82 self.square_sum = Frame::EQUILIBRIUM;
83 }
84
85 /// The length of the window as a number of frames.
86 ///
87 /// ```
88 /// use dasp_ring_buffer as ring_buffer;
89 /// use dasp_rms::Rms;
90 ///
91 /// fn main() {
92 /// let window = ring_buffer::Fixed::from([[0.0]; 4]);
93 /// let mut rms = Rms::new(window);
94 /// assert_eq!(rms.window_frames(), 4);
95 /// rms.next([0.5]);
96 /// assert_eq!(rms.window_frames(), 4);
97 /// }
98 /// ```
99 #[inline]
100 pub fn window_frames(&self) -> usize {
101 self.window.len()
102 }
103
104 /// The next RMS given the new frame in the sequence.
105 ///
106 /// The **Rms** pops its front frame and adds the new frame to the back.
107 ///
108 /// The yielded RMS is the RMS of all frame squares in the `window` after the new frame is
109 /// added.
110 ///
111 /// This method uses `Rms::next_squared` internally and then calculates the square root.
112 ///
113 /// ```
114 /// use dasp_ring_buffer as ring_buffer;
115 /// use dasp_rms::Rms;
116 ///
117 /// fn main() {
118 /// let window = ring_buffer::Fixed::from([[0.0]; 4]);
119 /// let mut rms = Rms::new(window);
120 /// assert_eq!(rms.next([1.0]), [0.5]);
121 /// assert_eq!(rms.next([-1.0]), [0.7071067811865476]);
122 /// assert_eq!(rms.next([1.0]), [0.8660254037844386]);
123 /// assert_eq!(rms.next([-1.0]), [1.0]);
124 /// }
125 /// ```
126 #[inline]
127 pub fn next(&mut self, new_frame: F) -> F::Float
128 where
129 S: ring_buffer::SliceMut,
130 {
131 self.next_squared(new_frame).map(|s| s.sample_sqrt())
132 }
133
134 /// The same as **Rms::next**, but does not calculate the final square root required to
135 /// determine the RMS.
136 #[inline]
137 pub fn next_squared(&mut self, new_frame: F) -> F::Float
138 where
139 S: ring_buffer::SliceMut,
140 {
141 // Determine the square of the new frame.
142 let new_frame_square = new_frame.to_float_frame().map(|s| s * s);
143 // Push back the new frame_square.
144 let removed_frame_square = self.window.push(new_frame_square);
145 // Add the new frame square and subtract the removed frame square.
146 self.square_sum =
147 self.square_sum
148 .add_amp(new_frame_square)
149 .zip_map(removed_frame_square, |s, r| {
150 let diff = s - r;
151 // Don't let floating point rounding errors put us below 0.0.
152 if diff < Sample::EQUILIBRIUM {
153 Sample::EQUILIBRIUM
154 } else {
155 diff
156 }
157 });
158 self.calc_rms_squared()
159 }
160
161 /// Consumes the **Rms** and returns its inner ring buffer of squared frames along with a frame
162 /// representing the sum of all frame squares contained within the ring buffer.
163 pub fn into_parts(self) -> (ring_buffer::Fixed<S>, S::Element) {
164 let Rms {
165 window, square_sum, ..
166 } = self;
167 (window, square_sum)
168 }
169
170 /// Calculates the RMS of all frames currently stored within the inner window.
171 ///
172 /// ```
173 /// use dasp_ring_buffer as ring_buffer;
174 /// use dasp_rms::Rms;
175 ///
176 /// fn main() {
177 /// let window = ring_buffer::Fixed::from([[0.0]; 4]);
178 /// let mut rms = Rms::new(window);
179 /// assert_eq!(rms.current(), [0.0]);
180 /// rms.next([1.0]);
181 /// rms.next([1.0]);
182 /// rms.next([1.0]);
183 /// rms.next([1.0]);
184 /// assert_eq!(rms.current(), [1.0]);
185 /// }
186 /// ```
187 pub fn current(&self) -> F::Float {
188 self.calc_rms_squared().map(|s| s.sample_sqrt())
189 }
190
191 fn calc_rms_squared(&self) -> F::Float {
192 let num_frames_f = Sample::from_sample(self.window.len() as f32);
193 self.square_sum.map(|s| s / num_frames_f)
194 }
195}
196
197impl<F, S> fmt::Debug for Rms<F, S>
198where
199 F: Frame,
200 F::Float: fmt::Debug,
201 S: fmt::Debug + ring_buffer::Slice<Element = F::Float>,
202{
203 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
204 f.debug_struct("Rms")
205 .field("frame", &self.frame)
206 .field("window", &self.window)
207 .field("square_sum", &self.square_sum)
208 .finish()
209 }
210}