From 1bc57b97da8afb29de98b1666fa7dc2695747bf9 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 10 Nov 2017 19:33:44 +1100 Subject: [PATCH 1/3] Update Rms type to use new ring buffer type rather than VecDeque This should improve the efficiency of Rms in cases. See RustAudio/sample#75 and RustAudio/sample#77 for related discussion around the new `Fixed` ring buffer type. --- Cargo.toml | 2 +- examples/test.rs | 8 ++-- src/lib.rs | 18 +++----- src/mode.rs | 5 +- src/rms.rs | 116 ++++++++++++++++++----------------------------- 5 files changed, 58 insertions(+), 91 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b3e63e..65ed753 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/MindBuffer/envelope_detector.git" homepage = "https://github.com/MindBuffer/envelope_detector" [dependencies] -sample = "0.6.0" +sample = "0.8.0" [dev-dependencies] portaudio = "0.6.4" diff --git a/examples/test.rs b/examples/test.rs index 5e5fb68..4f6c995 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -19,19 +19,19 @@ fn run() -> Result<(), pa::Error> { const ATTACK_MS: f64 = 1.0; const RELEASE_MS: f64 = 1.0; - let window = time::Ms(WINDOW_SIZE_MS).samples(SAMPLE_HZ) as usize; + let window_size = time::Ms(WINDOW_SIZE_MS).samples(SAMPLE_HZ) as usize; + let window = vec![[0.0; CHANNELS]; window_size]; + let ring_buffer = sample::ring_buffer::Fixed::from(window); let attack = time::Ms(ATTACK_MS).samples(SAMPLE_HZ) as f32; let release = time::Ms(RELEASE_MS).samples(SAMPLE_HZ) as f32; - let mut envelope_detector = EnvelopeDetector::rms(window, attack, release); + let mut envelope_detector = EnvelopeDetector::rms(ring_buffer, attack, release); // Callback used to construct the duplex sound stream. let callback = move |pa::InputStreamCallbackArgs { buffer, .. }| { - let window_frames = time::Ms(WINDOW_SIZE_MS).samples(SAMPLE_HZ) as usize; let attack = time::Ms(ATTACK_MS).samples(SAMPLE_HZ) as f32; let release = time::Ms(RELEASE_MS).samples(SAMPLE_HZ) as f32; - envelope_detector.set_window_frames(window_frames); envelope_detector.set_attack_frames(attack); envelope_detector.set_release_frames(release); diff --git a/src/lib.rs b/src/lib.rs index 538293a..3ec0b4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ extern crate sample; pub use mode::Mode; pub use peak::Peak; pub use rms::Rms; -pub use sample::{Frame, Sample}; +pub use sample::{ring_buffer, Frame, Sample}; pub mod mode; pub mod peak; @@ -40,7 +40,7 @@ pub struct EnvelopeDetector } /// An `EnvelopeDetector` that tracks the signal envelope using RMS. -pub type RmsEnvelopeDetector = EnvelopeDetector>; +pub type RmsEnvelopeDetector = EnvelopeDetector>; /// An `EnvelopeDetector` that tracks the full wave `Peak` envelope of a signal. pub type PeakEnvelopeDetector = EnvelopeDetector>; @@ -50,21 +50,15 @@ fn calc_gain(n_frames: f32) -> f32 { } -impl EnvelopeDetector> +impl EnvelopeDetector> where F: Frame, + S: ring_buffer::Slice + ring_buffer::SliceMut, { - /// Construct a new **Rms** **EnvelopeDetector**. - pub fn rms(rms_window_frames: usize, attack_frames: f32, release_frames: f32) -> Self { - let rms = Rms::new(rms_window_frames); + pub fn rms(buffer: ring_buffer::Fixed, attack_frames: f32, release_frames: f32) -> Self { + let rms = Rms::new(buffer); Self::new(rms, attack_frames, release_frames) } - - /// Set the duration of the **Rms** window in frames. - pub fn set_window_frames(&mut self, n_window_frames: usize) { - self.mode.set_window_frames(n_window_frames); - } - } impl EnvelopeDetector> diff --git a/src/mode.rs b/src/mode.rs index 7a6c580..ff655fb 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -5,7 +5,7 @@ use peak::{self, Peak}; use rms::Rms; -use sample::{Frame, Sample}; +use sample::{ring_buffer, Frame, Sample}; /// The mode used to detect the envelope of a signal. @@ -25,8 +25,9 @@ impl Mode for Peak } } -impl Mode for Rms +impl Mode for Rms where F: Frame, + S: ring_buffer::Slice + ring_buffer::SliceMut, { fn next_frame(&mut self, frame: F) -> F { self.next(frame).map(|s| s.to_sample::()) diff --git a/src/rms.rs b/src/rms.rs index a7e3674..becfb6f 100644 --- a/src/rms.rs +++ b/src/rms.rs @@ -2,31 +2,33 @@ //! //! The primary type of interest in this module is the [**Rms**](./struct.Rms). -use sample::{FloatSample, Frame, Sample}; +use sample::{ring_buffer, FloatSample, Frame, Sample}; use std; -/// Iteratively extracts the RMS (root mean square) envelope from a window over a signal of -/// sample `Frame`s. +/// Iteratively extracts the RMS (root mean square) envelope from a window over a signal of sample +/// `Frame`s. #[derive(Clone)] -pub struct Rms +pub struct Rms where F: Frame, + S: ring_buffer::Slice, { /// The type of `Frame`s for which the RMS will be calculated. frame: std::marker::PhantomData, /// The ringbuffer of frame sample squares (i.e. `sample * sample`) used to calculate the RMS - /// per sample. + /// per frame. /// - /// When a new sample is received, the **Rms** pops the front sample_square and adds the new + /// When a new frame is received, the **Rms** pops the front sample_square and adds the new /// sample_square to the back. - window: std::collections::VecDeque, + window: ring_buffer::Fixed, /// The sum total of all sample_squares currently within the **Rms**'s `window` ring buffer. sum: F::Float, } -impl std::fmt::Debug for Rms +impl std::fmt::Debug for Rms where F: Frame, F::Float: std::fmt::Debug, + S: std::fmt::Debug + ring_buffer::Slice, { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!(f, "Rms {{ frame: {:?}, window: {:?}, sum: {:?} }}", @@ -35,58 +37,32 @@ impl std::fmt::Debug for Rms } -impl Rms +impl Rms where F: Frame, + S: ring_buffer::Slice, { - - /// Construct a new **Rms**. - pub fn new(n_window_frames: usize) -> Self { + /// Construct a new **Rms** that uses the given ring buffer as its window. + /// + /// The window size of the **Rms** is equal to the length of the given ring buffer. + pub fn new(ring_buffer: ring_buffer::Fixed) -> Self { Rms { frame: std::marker::PhantomData, - window: (0..n_window_frames).map(|_| Frame::equilibrium()).collect(), + window: ring_buffer, sum: Frame::equilibrium(), } } /// Zeroes the sum and the buffer of the `window`. - pub fn reset(&mut self) { - for sample_square in &mut self.window { + pub fn reset(&mut self) + where + S: ring_buffer::SliceMut, + { + for sample_square in self.window.iter_mut() { *sample_square = Frame::equilibrium(); } self.sum = Frame::equilibrium(); } - /// Set the size of the `window` as a number of frames. - /// - /// If the current window length is longer than the given length, the difference will be popped - /// from the front of the `window` while adjusting the `sum` accordingly. - /// - /// If the current window length is shorter than the given length, the difference will be - /// pushed to the front of the `window` using frames at signal equilibrium. - /// - /// If the length already is already correct, no re-sizing occurs. - pub fn set_window_frames(&mut self, n_window_frames: usize) { - let len = self.window.len(); - if len == n_window_frames { - return; - - // If our window is too long, truncate it from the front (removing the olest frames). - } else if len > n_window_frames { - let diff = len - n_window_frames; - for _ in 0..diff { - self.pop_front(); - } - - // If our window is too short, we'll zero-pad the front of the ringbuffer (this way, the - // padded zeroes will be the first to be removed). - } else if len < n_window_frames { - let diff = n_window_frames - len; - for _ in 0..diff { - self.window.push_front(Frame::equilibrium()); - } - } - } - /// The length of the window as a number of frames. #[inline] pub fn window_frames(&self) -> usize { @@ -102,35 +78,25 @@ impl Rms /// /// Returns `Frame::equilibrium` if the `window` is empty. #[inline] - pub fn next(&mut self, new_frame: F) -> F::Float { - // If our **Window** has no length, there's nothing to calculate. - if self.window.len() == 0 { - return Frame::equilibrium(); - } - self.pop_front(); - self.push_back(new_frame.to_float_frame()); + pub fn next(&mut self, new_frame: F) -> F::Float + where + S: ring_buffer::SliceMut, + { + // Determine the square of the new frame. + let new_frame_square = new_frame.to_float_frame().map(|s| s * s); + // Push back the new frame_square. + let removed_frame_square = self.window.push(new_frame_square); + // Add the new frame square and subtract the removed frame square. + self.sum = self.sum + .add_amp(new_frame_square) + .zip_map(removed_frame_square, |s, r| { + let diff = s - r; + // Don't let floating point rounding errors put us below 0.0. + if diff < Sample::equilibrium() { Sample::equilibrium() } else { diff } + }); self.calc_rms() } - /// Remove the front frame and subtract it from the `sum` frame. - fn pop_front(&mut self) { - let removed_sample_square = self.window.pop_front().unwrap(); - self.sum = self.sum.zip_map(removed_sample_square, |s, r| { - let diff = s - r; - // Don't let floating point rounding errors put us below 0.0. - if diff < Sample::equilibrium() { Sample::equilibrium() } else { diff } - }); - } - - /// Determines the square of the given frame, pushes it back onto our buffer and adds it to - /// the `sum`. - fn push_back(&mut self, new_frame: F::Float) { - // Push back the new frame_square and add it to the `sum`. - let new_frame_square = new_frame.zip_map(new_frame, |a, b| a * b); - self.window.push_back(new_frame_square); - self.sum = self.sum.add_amp(new_frame_square); - } - /// Calculate the RMS for the **Window** in its current state and yield the result as the /// `Frame`s associated `Float` type. fn calc_rms(&self) -> F::Float { @@ -138,4 +104,10 @@ impl Rms self.sum.map(|s| (s / num_frames_f).sample_sqrt()) } + /// Consumes the **Rms** and returns its inner ring buffer of squared frames along with a frame + /// representing the sum of all frame squares contained within the ring buffer. + pub fn into_parts(self) -> (ring_buffer::Fixed, S::Element) { + let Rms { window, sum, .. } = self; + (window, sum) + } } From dc8f97f8f3092eee13a32099e67297c0f2f76fec Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 10 Nov 2017 19:43:30 +1100 Subject: [PATCH 2/3] Re-export sample crate --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3ec0b4a..317dc05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ #![deny(missing_copy_implementations)] #![deny(missing_docs)] -extern crate sample; +pub extern crate sample; pub use mode::Mode; pub use peak::Peak; From 52788765087444a18ac0b2f9a386c03b9e7f3abc Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 10 Nov 2017 20:58:04 +1100 Subject: [PATCH 3/3] Bump to version 0.3 to publish breaking change --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 65ed753..e6a8251 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "envelope_detector" -version = "0.2.0" +version = "0.3.0" authors = [ "mitchmindtree ", "JoshuaBatty"