Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "envelope_detector"
version = "0.2.0"
version = "0.3.0"
authors = [
"mitchmindtree <mitchell.nordine@gmail.com>",
"JoshuaBatty"
Expand All @@ -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"
Expand Down
8 changes: 4 additions & 4 deletions examples/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
20 changes: 7 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
#![deny(missing_copy_implementations)]
#![deny(missing_docs)]

extern crate sample;
pub 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;
Expand All @@ -40,7 +40,7 @@ pub struct EnvelopeDetector<F, M>
}

/// An `EnvelopeDetector` that tracks the signal envelope using RMS.
pub type RmsEnvelopeDetector<F> = EnvelopeDetector<F, Rms<F>>;
pub type RmsEnvelopeDetector<F, S> = EnvelopeDetector<F, Rms<F, S>>;
/// An `EnvelopeDetector` that tracks the full wave `Peak` envelope of a signal.
pub type PeakEnvelopeDetector<F> = EnvelopeDetector<F, Peak<peak::FullWave>>;

Expand All @@ -50,21 +50,15 @@ fn calc_gain(n_frames: f32) -> f32 {
}


impl<F> EnvelopeDetector<F, Rms<F>>
impl<F, S> EnvelopeDetector<F, Rms<F, S>>
where F: Frame,
S: ring_buffer::Slice<Element=F::Float> + 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<S>, 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<F> EnvelopeDetector<F, Peak<peak::FullWave>>
Expand Down
5 changes: 3 additions & 2 deletions src/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -25,8 +25,9 @@ impl<F, R> Mode<F> for Peak<R>
}
}

impl<F> Mode<F> for Rms<F>
impl<F, S> Mode<F> for Rms<F, S>
where F: Frame,
S: ring_buffer::Slice<Element=F::Float> + ring_buffer::SliceMut,
{
fn next_frame(&mut self, frame: F) -> F {
self.next(frame).map(|s| s.to_sample::<F::Sample>())
Expand Down
116 changes: 44 additions & 72 deletions src/rms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F>
pub struct Rms<F, S>
where F: Frame,
S: ring_buffer::Slice<Element=F::Float>,
{
/// The type of `Frame`s for which the RMS will be calculated.
frame: std::marker::PhantomData<F>,
/// 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<F::Float>,
window: ring_buffer::Fixed<S>,
/// The sum total of all sample_squares currently within the **Rms**'s `window` ring buffer.
sum: F::Float,
}

impl<F> std::fmt::Debug for Rms<F>
impl<F, S> std::fmt::Debug for Rms<F, S>
where F: Frame,
F::Float: std::fmt::Debug,
S: std::fmt::Debug + ring_buffer::Slice<Element=F::Float>,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "Rms {{ frame: {:?}, window: {:?}, sum: {:?} }}",
Expand All @@ -35,58 +37,32 @@ impl<F> std::fmt::Debug for Rms<F>
}


impl<F> Rms<F>
impl<F, S> Rms<F, S>
where F: Frame,
S: ring_buffer::Slice<Element=F::Float>,
{

/// 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<S>) -> 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 {
Expand All @@ -102,40 +78,36 @@ impl<F> Rms<F>
///
/// 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 {
let num_frames_f = Sample::from_sample(self.window.len() as f32);
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>, S::Element) {
let Rms { window, sum, .. } = self;
(window, sum)
}
}