From 866343459bc5eed9568b7fdaab0fa225d4f9a2ec Mon Sep 17 00:00:00 2001 From: Pieter Penninckx Date: Sat, 21 Sep 2019 15:51:58 +0200 Subject: [PATCH 01/29] Use cargo features. --- Cargo.toml | 11 ++++++++++- examples/play_wav.rs | 7 +++++++ examples/resample.rs | 7 +++++++ examples/synth.rs | 7 +++++++ src/conv.rs | 30 +++++++++++++++++++++++++++++- src/lib.rs | 32 ++++++++++++++++++++++++++++++-- tests/interpolate.rs | 9 ++++++++- tests/ring_buffer.rs | 6 ++++++ tests/signal.rs | 4 ++++ tests/slice.rs | 6 ++++++ tests/window.rs | 7 ++++++- 11 files changed, 120 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2533471..dd535466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,14 @@ hound = "2.0" portaudio = "0.7" [features] -default = ["std"] +default = ["std", "slice", "envelope", "frame", "peak", "ring_buffer", "rms", "signal", "window", "interpolate"] std = [] +slice = ["frame"] +envelope = ["frame", "peak", "ring_buffer", "rms", "interpolate", "signal"] +frame = [] +peak = ["frame"] +ring_buffer = [] +rms = ["frame", "ring_buffer"] +signal = ["frame", "envelope", "ring_buffer", "interpolate", "rms"] +window = ["frame", "signal"] +interpolate = ["ring_buffer", "frame", "signal"] diff --git a/examples/play_wav.rs b/examples/play_wav.rs index af72aa82..dccd000a 100644 --- a/examples/play_wav.rs +++ b/examples/play_wav.rs @@ -3,8 +3,10 @@ extern crate hound; extern crate portaudio as pa; extern crate sample; +#[cfg(feature = "signal")] use sample::{signal, Signal, ToFrameSliceMut}; +#[cfg(feature = "signal")] fn main() { // Find and load the wav. let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); @@ -50,3 +52,8 @@ fn main() { stream.stop().unwrap(); stream.close().unwrap(); } + +#[cfg(not(feature = "signal"))] +fn main() { + panic!("This example only works when compiled with the 'signal' feature."); +} diff --git a/examples/resample.rs b/examples/resample.rs index 889dd824..500ec723 100644 --- a/examples/resample.rs +++ b/examples/resample.rs @@ -6,8 +6,10 @@ extern crate hound; extern crate sample; use hound::{WavReader, WavWriter}; +#[cfg(all(feature = "interpolate", feature = "ring_buffer", feature = "signal"))] use sample::{interpolate, ring_buffer, signal, Sample, Signal}; +#[cfg(all(feature = "interpolate", feature = "ring_buffer", feature = "signal"))] fn main() { // Find and load the wav. let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); @@ -33,3 +35,8 @@ fn main() { writer.write_sample(frame[0].to_sample::()).unwrap(); } } + +#[cfg(not(all(feature = "interpolate", feature = "ring_buffer", feature = "signal")))] +fn main() { + panic!("This example only works when compiled with the features 'interpolate', 'ring_buffer' and 'signal'."); +} diff --git a/examples/synth.rs b/examples/synth.rs index bf70cb8b..27dd27d7 100644 --- a/examples/synth.rs +++ b/examples/synth.rs @@ -1,6 +1,7 @@ extern crate portaudio as pa; extern crate sample; +#[cfg(feature = "signal")] use sample::{signal, Frame, Sample, Signal, ToFrameSliceMut}; const FRAMES_PER_BUFFER: u32 = 512; @@ -11,6 +12,7 @@ fn main() { run().unwrap(); } +#[cfg(feature = "signal")] fn run() -> Result<(), pa::Error> { // Create a signal chain to play back 1 second of each oscillator at A4. @@ -57,3 +59,8 @@ fn run() -> Result<(), pa::Error> { Ok(()) } + +#[cfg(not(feature = "signal"))] +fn run() -> Result<(), pa::Error> { + panic!("This example only works when compiled with the 'signal' feature."); +} diff --git a/src/conv.rs b/src/conv.rs index cacfa50d..aa98acf3 100644 --- a/src/conv.rs +++ b/src/conv.rs @@ -13,7 +13,9 @@ //! Note that floating point conversions use the range -1.0 <= v < 1.0: //! `(1.0 as f64).to_sample::()` will overflow! -use {Box, Frame, Sample}; +use {Box, Sample}; +#[cfg(feature = "frame")] +use Frame; use core; use types::{I24, U24, I48, U48}; @@ -737,6 +739,7 @@ where fn from_boxed_sample_slice(slice: Box<[S]>) -> Option; } +#[cfg(feature = "frame")] /// For converting from a slice of `Frame`s to a slice of `Sample`s. pub trait FromFrameSlice<'a, F> where @@ -745,6 +748,7 @@ where fn from_frame_slice(slice: &'a [F]) -> Self; } +#[cfg(feature = "frame")] /// For converting from a slice of `Frame`s to a slice of `Sample`s. pub trait FromFrameSliceMut<'a, F> where @@ -753,6 +757,7 @@ where fn from_frame_slice_mut(slice: &'a mut [F]) -> Self; } +#[cfg(feature = "frame")] /// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. pub trait FromBoxedFrameSlice where @@ -785,6 +790,7 @@ where fn to_boxed_sample_slice(self) -> Box<[S]>; } +#[cfg(feature = "frame")] /// For converting from a slice of `Sample`s to a slice of `Frame`s. pub trait ToFrameSlice<'a, F> where @@ -793,6 +799,7 @@ where fn to_frame_slice(self) -> Option<&'a [F]>; } +#[cfg(feature = "frame")] /// For converting from a mutable slice of `Sample`s to a mutable slice of `Frame`s. pub trait ToFrameSliceMut<'a, F> where @@ -801,6 +808,7 @@ where fn to_frame_slice_mut(self) -> Option<&'a mut [F]>; } +#[cfg(feature = "frame")] /// For converting from a boxed slice of `Sample`s to a boxed slice of `Frame`s. pub trait ToBoxedFrameSlice where @@ -843,6 +851,7 @@ where } } +#[cfg(feature = "frame")] impl<'a, F> FromFrameSlice<'a, F> for &'a [F] where F: Frame, @@ -853,6 +862,7 @@ where } } +#[cfg(feature = "frame")] impl<'a, F> FromFrameSliceMut<'a, F> for &'a mut [F] where F: Frame, @@ -863,6 +873,7 @@ where } } +#[cfg(feature = "frame")] impl FromBoxedFrameSlice for Box<[F]> where F: Frame, @@ -903,6 +914,7 @@ where } } +#[cfg(feature = "frame")] impl<'a, F> ToFrameSlice<'a, F> for &'a [F] where F: Frame, @@ -913,6 +925,7 @@ where } } +#[cfg(feature = "frame")] impl<'a, F> ToFrameSliceMut<'a, F> for &'a mut [F] where F: Frame, @@ -923,6 +936,7 @@ where } } +#[cfg(feature = "frame")] impl ToBoxedFrameSlice for Box<[F]> where F: Frame, @@ -933,6 +947,7 @@ where } } +#[cfg(feature = "frame")] /// A macro for implementing all audio slice conversion traits for each fixed-size array. macro_rules! impl_from_slice_conversions { ($($N:expr)*) => { @@ -1114,6 +1129,7 @@ macro_rules! impl_from_slice_conversions { }; } +#[cfg(feature = "frame")] impl_from_slice_conversions! { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 } @@ -1129,12 +1145,14 @@ where { } +#[cfg(feature = "frame")] pub trait DuplexFrameSlice<'a, F>: FromFrameSlice<'a, F> + ToFrameSlice<'a, F> where F: Frame { } +#[cfg(feature = "frame")] pub trait DuplexSlice<'a, S, F> : DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F> where @@ -1150,6 +1168,7 @@ where { } +#[cfg(feature = "frame")] pub trait DuplexFrameSliceMut<'a, F> : FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F> where @@ -1157,6 +1176,7 @@ where { } +#[cfg(feature = "frame")] pub trait DuplexSliceMut<'a, S, F> : DuplexSampleSliceMut<'a, S> + DuplexFrameSliceMut<'a, F> where @@ -1172,6 +1192,7 @@ where { } +#[cfg(feature = "frame")] pub trait DuplexBoxedFrameSlice : FromBoxedFrameSlice + ToBoxedFrameSlice where @@ -1179,6 +1200,7 @@ where { } +#[cfg(feature = "frame")] pub trait DuplexBoxedSlice : DuplexBoxedSampleSlice + DuplexBoxedFrameSlice where @@ -1197,6 +1219,7 @@ where { } +#[cfg(feature = "frame")] impl<'a, F, T> DuplexFrameSlice<'a, F> for T where F: Frame, @@ -1204,6 +1227,7 @@ where { } +#[cfg(feature = "frame")] impl<'a, S, F, T> DuplexSlice<'a, S, F> for T where S: Sample, @@ -1217,6 +1241,7 @@ where S: Sample, T: FromSampleSliceMut<'a, S> + ToSampleSliceMut<'a, S> {} +#[cfg(feature = "frame")] impl<'a, F, T> DuplexFrameSliceMut<'a, F> for T where F: Frame, @@ -1224,6 +1249,7 @@ where { } +#[cfg(feature = "frame")] impl<'a, S, F, T> DuplexSliceMut<'a, S, F> for T where S: Sample, @@ -1240,6 +1266,7 @@ where { } +#[cfg(feature = "frame")] impl DuplexBoxedFrameSlice for T where F: Frame, @@ -1247,6 +1274,7 @@ where { } +#[cfg(feature = "frame")] impl DuplexBoxedSlice for T where S: Sample, diff --git a/src/lib.rs b/src/lib.rs index 6142892e..511e7fc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,24 +47,51 @@ type Rc = std::rc::Rc; pub use conv::{FromSample, ToSample, Duplex, FromSampleSlice, ToSampleSlice, DuplexSampleSlice, FromSampleSliceMut, ToSampleSliceMut, DuplexSampleSliceMut, FromBoxedSampleSlice, - ToBoxedSampleSlice, DuplexBoxedSampleSlice, FromFrameSlice, ToFrameSlice, + ToBoxedSampleSlice, DuplexBoxedSampleSlice}; + +#[cfg(feature = "frame")] +pub use conv::{FromFrameSlice, ToFrameSlice, DuplexFrameSlice, FromFrameSliceMut, ToFrameSliceMut, DuplexFrameSliceMut, FromBoxedFrameSlice, ToBoxedFrameSlice, DuplexBoxedFrameSlice, DuplexSlice, DuplexSliceMut, DuplexBoxedSlice}; + +#[cfg(feature = "frame")] pub use frame::Frame; + +#[cfg(feature = "signal")] pub use signal::Signal; + pub use types::{I24, U24, I48, U48}; -pub mod slice; pub mod conv; + +#[cfg(feature = "slice")] +pub mod slice; + +#[cfg(feature = "envelope")] pub mod envelope; + +#[cfg(feature = "frame")] pub mod frame; + +#[cfg(feature = "peak")] pub mod peak; + +#[cfg(feature = "ring_buffer")] pub mod ring_buffer; + +#[cfg(feature = "rms")] pub mod rms; + +#[cfg(feature = "envelope")] pub mod signal; + pub mod types; + +#[cfg(feature = "window")] pub mod window; + +#[cfg(feature = "interpolate")] pub mod interpolate; mod ops { @@ -285,6 +312,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// assert_eq!(I24::from_sample(0.0), I24::new(0).unwrap()); /// } /// ``` + #[inline] fn from_sample(s: S) -> Self where diff --git a/tests/interpolate.rs b/tests/interpolate.rs index ec4f2f8f..539ad427 100644 --- a/tests/interpolate.rs +++ b/tests/interpolate.rs @@ -2,10 +2,14 @@ extern crate sample; +#[cfg(feature="interpolate")] use sample::interpolate::{Converter, Floor, Linear, Sinc}; +#[cfg(feature="ring_buffer")] use sample::ring_buffer; +#[cfg(feature="signal")] use sample::{signal, Signal}; +#[cfg(feature="interpolate")] #[test] fn test_floor_converter() { let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]]; @@ -25,6 +29,7 @@ fn test_floor_converter() { assert_eq!(conv.next(), [2.0]); } +#[cfg(all(feature="interpolate", feature = "signal"))] #[test] fn test_linear_converter() { let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]]; @@ -42,6 +47,7 @@ fn test_linear_converter() { assert_eq!(conv.next(), [1.0]); } +#[cfg(all(feature="interpolate", feature = "signal"))] #[test] fn test_scale_playback_rate() { // Scale the playback rate by `0.5` @@ -55,6 +61,7 @@ fn test_scale_playback_rate() { ); } +#[cfg(all(feature="interpolate", feature = "signal"))] #[test] fn test_sinc() { let foo = [[0.0f64], [1.0], [0.0], [-1.0]]; @@ -68,4 +75,4 @@ fn test_sinc() { resampled.until_exhausted().find(|sample| sample[0].is_nan()), None ); -} \ No newline at end of file +} diff --git a/tests/ring_buffer.rs b/tests/ring_buffer.rs index 25e5cb81..720a7305 100644 --- a/tests/ring_buffer.rs +++ b/tests/ring_buffer.rs @@ -1,7 +1,9 @@ extern crate sample; +#[cfg(feature="ring_buffer")] use sample::ring_buffer; +#[cfg(feature="ring_buffer")] #[test] fn test_bounded_boxed_slice() { let mut rb = ring_buffer::Bounded::boxed_slice(3); @@ -11,6 +13,7 @@ fn test_bounded_boxed_slice() { assert_eq!(rb.push(4), Some(1)); } +#[cfg(feature="ring_buffer")] #[test] fn test_bounded_array() { let mut rb = ring_buffer::Bounded::<[i32; 3]>::array(); @@ -20,12 +23,14 @@ fn test_bounded_array() { assert_eq!(rb.push(4), Some(1)); } +#[cfg(feature="ring_buffer")] #[test] #[should_panic] fn text_bounded_from_empty_vec() { ring_buffer::Bounded::from(Vec::::new()); } +#[cfg(feature="ring_buffer")] #[test] fn test_bounded_from_vec() { let mut rb = ring_buffer::Bounded::from(vec![1, 2, 3]); @@ -35,6 +40,7 @@ fn test_bounded_from_vec() { assert_eq!(rb.push(7), Some(4)); } +#[cfg(feature="ring_buffer")] #[test] #[should_panic] fn test_bounded_get_out_of_range() { diff --git a/tests/signal.rs b/tests/signal.rs index 94a46803..d323edf5 100644 --- a/tests/signal.rs +++ b/tests/signal.rs @@ -2,14 +2,17 @@ extern crate sample; +#[cfg(feature = "signal")] use sample::{signal, Signal}; +#[cfg(feature = "signal")] #[test] fn test_equilibrium() { let equilibrium: Vec<[i8; 1]> = sample::signal::equilibrium().take(4).collect(); assert_eq!(equilibrium, vec![[0], [0], [0], [0]]); } +#[cfg(feature = "signal")] #[test] fn test_scale_amp() { let foo = [[0.5], [0.8], [-0.4], [-0.2]]; @@ -21,6 +24,7 @@ fn test_scale_amp() { assert_eq!(amp_scaled, vec![[0.25], [0.4], [-0.2], [-0.1]]); } +#[cfg(feature = "signal")] #[test] fn test_offset_amp() { let foo = [[0.5], [0.9], [-0.4], [-0.2]]; diff --git a/tests/slice.rs b/tests/slice.rs index bcfd1a96..1980a4b2 100644 --- a/tests/slice.rs +++ b/tests/slice.rs @@ -1,5 +1,6 @@ extern crate sample; +#[cfg(feature="slice")] #[test] fn test_add_slice() { let mut a = [[-0.5]; 32]; @@ -8,6 +9,7 @@ fn test_add_slice() { assert_eq!([[0.0]; 32], a); } +#[cfg(feature="slice")] #[test] #[should_panic] fn test_add_slice_panic() { @@ -16,6 +18,7 @@ fn test_add_slice_panic() { sample::slice::add_in_place(&mut a, &b); } +#[cfg(feature="slice")] #[test] fn test_write_slice() { let mut a = [[0.0]; 32]; @@ -24,6 +27,7 @@ fn test_write_slice() { assert_eq!([[1.0]; 32], a); } +#[cfg(feature="slice")] #[test] #[should_panic] fn test_write_slice_panic() { @@ -32,6 +36,7 @@ fn test_write_slice_panic() { sample::slice::write(&mut a, &b); } +#[cfg(feature="slice")] #[test] fn test_add_slice_with_amp_per_channel() { let mut a = [[0.5]; 32]; @@ -41,6 +46,7 @@ fn test_add_slice_with_amp_per_channel() { assert_eq!([[1.0]; 32], a); } +#[cfg(feature="slice")] #[test] #[should_panic] fn test_add_slice_with_amp_per_channel_panic() { diff --git a/tests/window.rs b/tests/window.rs index ba4ab14c..b5e6815e 100644 --- a/tests/window.rs +++ b/tests/window.rs @@ -1,8 +1,11 @@ extern crate sample; +#[cfg(feature = "frame")] use sample::frame::Frame; +#[cfg(feature = "window")] use sample::window::Windower; +#[cfg(feature = "window")] #[test] fn test_window_at_phase() { let window = sample::window::hanning::<[f64; 1]>(9); @@ -22,8 +25,9 @@ fn test_window_at_phase() { println!("Expected: {}\t\tFound: {}", e, r[0]); assert!((r[0] - e).abs() < 0.001); } -} +} +#[cfg(feature = "window")] #[test] fn test_windower() { let data = [[0.1f64], [0.1], [0.2], [0.2], [0.3], [0.3], [0.4], [0.4]]; @@ -47,6 +51,7 @@ fn test_windower() { } } +#[cfg(feature = "window")] #[test] fn test_window_size() { let v = [[1f32; 1]; 16]; From 76be6614d760683e38fe240c7217e561c226e32d Mon Sep 17 00:00:00 2001 From: Pieter Penninckx Date: Sat, 28 Dec 2019 15:08:25 +0100 Subject: [PATCH 02/29] Update .travis.yml --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3fc6bc00..afd0f1c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,16 @@ script: - | ([ "$TRAVIS_RUST_VERSION" = "nightly" ] && cargo check -v --no-default-features) || [ "$TRAVIS_RUST_VERSION" != "nightly" ] - cargo check -v +- cargo check --tests --examples --no-default-features --features std +- cargo check --tests --examples --no-default-features --features std,slice +- cargo check --tests --examples --no-default-features --features std,envelope +- cargo check --tests --examples --no-default-features --features std,frame +- cargo check --tests --examples --no-default-features --features std,peak +- cargo check --tests --examples --no-default-features --features std,ring_buffer +- cargo check --tests --examples --no-default-features --features std,rms +- cargo check --tests --examples --no-default-features --features std,signal +- cargo check --tests --examples --no-default-features --features std,window +- cargo check --tests --examples --no-default-features --features std,interpolate - cargo test -v - cargo test --release -v - cargo doc -v From aa274b65ac0e32ecf0ecb25c83384b6b82066c7b Mon Sep 17 00:00:00 2001 From: Pieter Penninckx Date: Fri, 14 Feb 2020 09:11:36 +0100 Subject: [PATCH 03/29] Move impl blocks to lower the number of cfg's. --- src/conv.rs | 273 --------------------------------------------------- src/frame.rs | 263 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+), 273 deletions(-) diff --git a/src/conv.rs b/src/conv.rs index aa98acf3..ed45940c 100644 --- a/src/conv.rs +++ b/src/conv.rs @@ -851,39 +851,6 @@ where } } -#[cfg(feature = "frame")] -impl<'a, F> FromFrameSlice<'a, F> for &'a [F] -where - F: Frame, -{ - #[inline] - fn from_frame_slice(slice: &'a [F]) -> Self { - slice - } -} - -#[cfg(feature = "frame")] -impl<'a, F> FromFrameSliceMut<'a, F> for &'a mut [F] -where - F: Frame, -{ - #[inline] - fn from_frame_slice_mut(slice: &'a mut [F]) -> Self { - slice - } -} - -#[cfg(feature = "frame")] -impl FromBoxedFrameSlice for Box<[F]> -where - F: Frame, -{ - #[inline] - fn from_boxed_frame_slice(slice: Box<[F]>) -> Self { - slice - } -} - impl<'a, S> ToSampleSlice<'a, S> for &'a [S] where S: Sample, @@ -947,194 +914,6 @@ where } } -#[cfg(feature = "frame")] -/// A macro for implementing all audio slice conversion traits for each fixed-size array. -macro_rules! impl_from_slice_conversions { - ($($N:expr)*) => { - $( - - impl<'a, S> FromSampleSlice<'a, S> for &'a [[S; $N]] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn from_sample_slice(slice: &'a [S]) -> Option { - let len = slice.len(); - if len % $N == 0 { - let new_len = len / $N; - let ptr = slice.as_ptr() as *const _; - let new_slice = unsafe { - core::slice::from_raw_parts(ptr, new_len) - }; - Some(new_slice) - } else { - None - } - } - } - - impl<'a, S> FromSampleSliceMut<'a, S> for &'a mut [[S; $N]] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn from_sample_slice_mut(slice: &'a mut [S]) -> Option { - let len = slice.len(); - if len % $N == 0 { - let new_len = len / $N; - let ptr = slice.as_ptr() as *mut _; - let new_slice = unsafe { - core::slice::from_raw_parts_mut(ptr, new_len) - }; - Some(new_slice) - } else { - None - } - } - } - - impl FromBoxedSampleSlice for Box<[[S; $N]]> - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn from_boxed_sample_slice(mut slice: Box<[S]>) -> Option { - - // First, we need a raw pointer to the slice and to make sure that the `Box` is - // forgotten so that our slice does not get deallocated. - let len = slice.len(); - let slice_ptr = &mut slice as &mut [S] as *mut [S]; - core::mem::forget(slice); - let sample_slice = unsafe { - core::slice::from_raw_parts_mut((*slice_ptr).as_mut_ptr(), len) - }; - - // Convert to our frame slice if possible. - let frame_slice = match <&mut [[S; $N]]>::from_sample_slice_mut(sample_slice) { - Some(slice) => slice, - None => return None, - }; - let ptr = frame_slice as *mut [[S; $N]]; - - // Take ownership over the slice again before returning it. - let new_slice = unsafe { - Box::from_raw(ptr) - }; - - Some(new_slice) - } - } - - impl<'a, S> FromFrameSlice<'a, [S; $N]> for &'a [S] - where [S; $N]: Frame, - { - #[inline] - fn from_frame_slice(slice: &'a [[S; $N]]) -> Self { - let new_len = slice.len() * $N; - let ptr = slice.as_ptr() as *const _; - unsafe { - core::slice::from_raw_parts(ptr, new_len) - } - } - } - - impl<'a, S> FromFrameSliceMut<'a, [S; $N]> for &'a mut [S] - where [S; $N]: Frame, - { - #[inline] - fn from_frame_slice_mut(slice: &'a mut [[S; $N]]) -> Self { - let new_len = slice.len() * $N; - let ptr = slice.as_ptr() as *mut _; - unsafe { - core::slice::from_raw_parts_mut(ptr, new_len) - } - } - } - - impl FromBoxedFrameSlice<[S; $N]> for Box<[S]> - where [S; $N]: Frame, - { - #[inline] - fn from_boxed_frame_slice(mut slice: Box<[[S; $N]]>) -> Self { - let new_len = slice.len() * $N; - let frame_slice_ptr = &mut slice as &mut [[S; $N]] as *mut [[S; $N]]; - core::mem::forget(slice); - let sample_slice_ptr = frame_slice_ptr as *mut [S]; - unsafe { - let ptr = (*sample_slice_ptr).as_mut_ptr(); - let sample_slice = core::slice::from_raw_parts_mut(ptr, new_len); - Box::from_raw(sample_slice as *mut _) - } - } - } - - impl<'a, S> ToSampleSlice<'a, S> for &'a [[S; $N]] - where S: Sample, - { - #[inline] - fn to_sample_slice(self) -> &'a [S] { - FromFrameSlice::from_frame_slice(self) - } - } - - impl<'a, S> ToSampleSliceMut<'a, S> for &'a mut [[S; $N]] - where S: Sample, - { - #[inline] - fn to_sample_slice_mut(self) -> &'a mut [S] { - FromFrameSliceMut::from_frame_slice_mut(self) - } - } - - impl ToBoxedSampleSlice for Box<[[S; $N]]> - where S: Sample, - { - #[inline] - fn to_boxed_sample_slice(self) -> Box<[S]> { - FromBoxedFrameSlice::from_boxed_frame_slice(self) - } - } - - impl<'a, S> ToFrameSlice<'a, [S; $N]> for &'a [S] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn to_frame_slice(self) -> Option<&'a [[S; $N]]> { - FromSampleSlice::from_sample_slice(self) - } - } - - impl<'a, S> ToFrameSliceMut<'a, [S; $N]> for &'a mut [S] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn to_frame_slice_mut(self) -> Option<&'a mut [[S; $N]]> { - FromSampleSliceMut::from_sample_slice_mut(self) - } - } - - impl ToBoxedFrameSlice<[S; $N]> for Box<[S]> - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn to_boxed_frame_slice(self) -> Option> { - FromBoxedSampleSlice::from_boxed_sample_slice(self) - } - } - - )* - }; -} - -#[cfg(feature = "frame")] -impl_from_slice_conversions! { - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 -} - - ///// Bi-Directional DSP Slice Conversion Traits @@ -1219,66 +998,14 @@ where { } -#[cfg(feature = "frame")] -impl<'a, F, T> DuplexFrameSlice<'a, F> for T -where - F: Frame, - T: FromFrameSlice<'a, F> + ToFrameSlice<'a, F>, -{ -} - -#[cfg(feature = "frame")] -impl<'a, S, F, T> DuplexSlice<'a, S, F> for T -where - S: Sample, - F: Frame, - T: DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F>, -{ -} - impl<'a, S, T> DuplexSampleSliceMut<'a, S> for T where S: Sample, T: FromSampleSliceMut<'a, S> + ToSampleSliceMut<'a, S> {} -#[cfg(feature = "frame")] -impl<'a, F, T> DuplexFrameSliceMut<'a, F> for T -where - F: Frame, - T: FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F>, -{ -} - -#[cfg(feature = "frame")] -impl<'a, S, F, T> DuplexSliceMut<'a, S, F> for T -where - S: Sample, - F: Frame, - T: DuplexSampleSliceMut<'a, S> - + DuplexFrameSliceMut<'a, F>, -{ -} - impl DuplexBoxedSampleSlice for T where S: Sample, T: FromBoxedSampleSlice + ToBoxedSampleSlice, { } - -#[cfg(feature = "frame")] -impl DuplexBoxedFrameSlice for T -where - F: Frame, - T: FromBoxedFrameSlice + ToBoxedFrameSlice, -{ -} - -#[cfg(feature = "frame")] -impl DuplexBoxedSlice for T -where - S: Sample, - F: Frame, - T: DuplexBoxedSampleSlice + DuplexBoxedFrameSlice, -{ -} diff --git a/src/frame.rs b/src/frame.rs index d0bc873b..6297c04b 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -4,6 +4,7 @@ //! Implementations are provided for all fixed-size arrays up to 32 elements in length. use Sample; +use conv::{FromBoxedSampleSlice, FromFrameSlice, FromFrameSliceMut, FromBoxedFrameSlice, FromSampleSlice, FromSampleSliceMut, ToSampleSlice, ToSampleSliceMut, ToBoxedSampleSlice, ToFrameSlice, DuplexFrameSlice, DuplexSlice, DuplexSampleSlice, DuplexBoxedFrameSlice, DuplexBoxedSampleSlice, DuplexBoxedSlice, ToBoxedFrameSlice, DuplexFrameSliceMut, DuplexSampleSliceMut, DuplexSliceMut, ToFrameSliceMut}; pub type Mono = [S; 1]; pub type Stereo = [S; 2]; @@ -456,3 +457,265 @@ where F::n_channels() - self.next_idx } } + + +impl<'a, F> FromFrameSlice<'a, F> for &'a [F] +where + F: Frame, +{ + #[inline] + fn from_frame_slice(slice: &'a [F]) -> Self { + slice + } +} + +impl<'a, F> FromFrameSliceMut<'a, F> for &'a mut [F] +where + F: Frame, +{ + #[inline] + fn from_frame_slice_mut(slice: &'a mut [F]) -> Self { + slice + } +} + +impl FromBoxedFrameSlice for Box<[F]> +where + F: Frame, +{ + #[inline] + fn from_boxed_frame_slice(slice: Box<[F]>) -> Self { + slice + } +} + +/// A macro for implementing all audio slice conversion traits for each fixed-size array. +macro_rules! impl_from_slice_conversions { + ($($N:expr)*) => { + $( + + impl<'a, S> FromSampleSlice<'a, S> for &'a [[S; $N]] + where S: Sample, + [S; $N]: Frame, + { + #[inline] + fn from_sample_slice(slice: &'a [S]) -> Option { + let len = slice.len(); + if len % $N == 0 { + let new_len = len / $N; + let ptr = slice.as_ptr() as *const _; + let new_slice = unsafe { + core::slice::from_raw_parts(ptr, new_len) + }; + Some(new_slice) + } else { + None + } + } + } + + impl<'a, S> FromSampleSliceMut<'a, S> for &'a mut [[S; $N]] + where S: Sample, + [S; $N]: Frame, + { + #[inline] + fn from_sample_slice_mut(slice: &'a mut [S]) -> Option { + let len = slice.len(); + if len % $N == 0 { + let new_len = len / $N; + let ptr = slice.as_ptr() as *mut _; + let new_slice = unsafe { + core::slice::from_raw_parts_mut(ptr, new_len) + }; + Some(new_slice) + } else { + None + } + } + } + + impl FromBoxedSampleSlice for Box<[[S; $N]]> + where S: Sample, + [S; $N]: Frame, + { + #[inline] + fn from_boxed_sample_slice(mut slice: Box<[S]>) -> Option { + + // First, we need a raw pointer to the slice and to make sure that the `Box` is + // forgotten so that our slice does not get deallocated. + let len = slice.len(); + let slice_ptr = &mut slice as &mut [S] as *mut [S]; + core::mem::forget(slice); + let sample_slice = unsafe { + core::slice::from_raw_parts_mut((*slice_ptr).as_mut_ptr(), len) + }; + + // Convert to our frame slice if possible. + let frame_slice = match <&mut [[S; $N]]>::from_sample_slice_mut(sample_slice) { + Some(slice) => slice, + None => return None, + }; + let ptr = frame_slice as *mut [[S; $N]]; + + // Take ownership over the slice again before returning it. + let new_slice = unsafe { + Box::from_raw(ptr) + }; + + Some(new_slice) + } + } + + impl<'a, S> FromFrameSlice<'a, [S; $N]> for &'a [S] + where [S; $N]: Frame, + { + #[inline] + fn from_frame_slice(slice: &'a [[S; $N]]) -> Self { + let new_len = slice.len() * $N; + let ptr = slice.as_ptr() as *const _; + unsafe { + core::slice::from_raw_parts(ptr, new_len) + } + } + } + + impl<'a, S> FromFrameSliceMut<'a, [S; $N]> for &'a mut [S] + where [S; $N]: Frame, + { + #[inline] + fn from_frame_slice_mut(slice: &'a mut [[S; $N]]) -> Self { + let new_len = slice.len() * $N; + let ptr = slice.as_ptr() as *mut _; + unsafe { + core::slice::from_raw_parts_mut(ptr, new_len) + } + } + } + + impl FromBoxedFrameSlice<[S; $N]> for Box<[S]> + where [S; $N]: Frame, + { + #[inline] + fn from_boxed_frame_slice(mut slice: Box<[[S; $N]]>) -> Self { + let new_len = slice.len() * $N; + let frame_slice_ptr = &mut slice as &mut [[S; $N]] as *mut [[S; $N]]; + core::mem::forget(slice); + let sample_slice_ptr = frame_slice_ptr as *mut [S]; + unsafe { + let ptr = (*sample_slice_ptr).as_mut_ptr(); + let sample_slice = core::slice::from_raw_parts_mut(ptr, new_len); + Box::from_raw(sample_slice as *mut _) + } + } + } + + impl<'a, S> ToSampleSlice<'a, S> for &'a [[S; $N]] + where S: Sample, + { + #[inline] + fn to_sample_slice(self) -> &'a [S] { + FromFrameSlice::from_frame_slice(self) + } + } + + impl<'a, S> ToSampleSliceMut<'a, S> for &'a mut [[S; $N]] + where S: Sample, + { + #[inline] + fn to_sample_slice_mut(self) -> &'a mut [S] { + FromFrameSliceMut::from_frame_slice_mut(self) + } + } + + impl ToBoxedSampleSlice for Box<[[S; $N]]> + where S: Sample, + { + #[inline] + fn to_boxed_sample_slice(self) -> Box<[S]> { + FromBoxedFrameSlice::from_boxed_frame_slice(self) + } + } + + impl<'a, S> ToFrameSlice<'a, [S; $N]> for &'a [S] + where S: Sample, + [S; $N]: Frame, + { + #[inline] + fn to_frame_slice(self) -> Option<&'a [[S; $N]]> { + FromSampleSlice::from_sample_slice(self) + } + } + + impl<'a, S> ToFrameSliceMut<'a, [S; $N]> for &'a mut [S] + where S: Sample, + [S; $N]: Frame, + { + #[inline] + fn to_frame_slice_mut(self) -> Option<&'a mut [[S; $N]]> { + FromSampleSliceMut::from_sample_slice_mut(self) + } + } + + impl ToBoxedFrameSlice<[S; $N]> for Box<[S]> + where S: Sample, + [S; $N]: Frame, + { + #[inline] + fn to_boxed_frame_slice(self) -> Option> { + FromBoxedSampleSlice::from_boxed_sample_slice(self) + } + } + + )* + }; +} + +impl_from_slice_conversions! { + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 +} + +impl<'a, F, T> DuplexFrameSlice<'a, F> for T +where + F: Frame, + T: FromFrameSlice<'a, F> + ToFrameSlice<'a, F>, +{ +} + +impl<'a, S, F, T> DuplexSlice<'a, S, F> for T +where + S: Sample, + F: Frame, + T: DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F>, +{ +} + +impl<'a, F, T> DuplexFrameSliceMut<'a, F> for T +where + F: Frame, + T: FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F>, +{ +} + +impl<'a, S, F, T> DuplexSliceMut<'a, S, F> for T +where + S: Sample, + F: Frame, + T: DuplexSampleSliceMut<'a, S> + + DuplexFrameSliceMut<'a, F>, +{ +} + +impl DuplexBoxedFrameSlice for T +where + F: Frame, + T: FromBoxedFrameSlice + ToBoxedFrameSlice, +{ +} + +impl DuplexBoxedSlice for T +where + S: Sample, + F: Frame, + T: DuplexBoxedSampleSlice + DuplexBoxedFrameSlice, +{ +} From 8412880319e188fd065fd08ca9cc3f92f85e2bbc Mon Sep 17 00:00:00 2001 From: Pieter Penninckx Date: Fri, 14 Feb 2020 09:16:30 +0100 Subject: [PATCH 04/29] rustfmt frame.rs --- src/frame.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/frame.rs b/src/frame.rs index 6297c04b..bf3683fe 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -3,8 +3,14 @@ //! //! Implementations are provided for all fixed-size arrays up to 32 elements in length. +use conv::{ + DuplexBoxedFrameSlice, DuplexBoxedSampleSlice, DuplexBoxedSlice, DuplexFrameSlice, + DuplexFrameSliceMut, DuplexSampleSlice, DuplexSampleSliceMut, DuplexSlice, DuplexSliceMut, + FromBoxedFrameSlice, FromBoxedSampleSlice, FromFrameSlice, FromFrameSliceMut, FromSampleSlice, + FromSampleSliceMut, ToBoxedFrameSlice, ToBoxedSampleSlice, ToFrameSlice, ToFrameSliceMut, + ToSampleSlice, ToSampleSliceMut, +}; use Sample; -use conv::{FromBoxedSampleSlice, FromFrameSlice, FromFrameSliceMut, FromBoxedFrameSlice, FromSampleSlice, FromSampleSliceMut, ToSampleSlice, ToSampleSliceMut, ToBoxedSampleSlice, ToFrameSlice, DuplexFrameSlice, DuplexSlice, DuplexSampleSlice, DuplexBoxedFrameSlice, DuplexBoxedSampleSlice, DuplexBoxedSlice, ToBoxedFrameSlice, DuplexFrameSliceMut, DuplexSampleSliceMut, DuplexSliceMut, ToFrameSliceMut}; pub type Mono = [S; 1]; pub type Stereo = [S; 2]; @@ -398,7 +404,7 @@ macro_rules! impl_frame { }; } -impl_frame!{ +impl_frame! { N1 1, [0], N2 2, [0 1], N3 3, [0 1 2], @@ -433,7 +439,6 @@ impl_frame!{ N32 32, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31], } - impl Iterator for Channels where F: Frame, @@ -458,7 +463,6 @@ where } } - impl<'a, F> FromFrameSlice<'a, F> for &'a [F] where F: Frame, @@ -700,8 +704,7 @@ impl<'a, S, F, T> DuplexSliceMut<'a, S, F> for T where S: Sample, F: Frame, - T: DuplexSampleSliceMut<'a, S> - + DuplexFrameSliceMut<'a, F>, + T: DuplexSampleSliceMut<'a, S> + DuplexFrameSliceMut<'a, F>, { } From e6cf78cdbe8c46814e7f6da2c11071b4f5d9e2e5 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 16:10:12 +0200 Subject: [PATCH 05/29] Rename `sample` to `dasp`. Split features into multiple crates. This PR is based on @PieterPenninckx's great work in #116 and some of the past discussion that has occurred around improving the modularity of the`sample` crate. Having the crate split into features as in #116 made it easier for me to identify how to untangle the various modules into independent crates. **sample** -> **dasp** The direction of splitting up the crate into multiple sub crates has been on my mind for a long time, though the name `sample` always seemed unsuitable to as an umbrella term for the sub-crates of the project. After spending a bit of time throwing around different acronyms and abbreviations, @JoshuaBatty suggested **dasp** for digital audio signal processing. I think it rolls off the tongue pretty well! **Repo structure** The repository is now structured as a workspace consisting of a suite of subcrates. The **dasp** crate is the top-level crate, re-exporting each of the sub-crates and providing features that map not only to each of the sub-crates, but also to each of the sub-crate features too. **TODO** I thought I'd post this PR now to allow for feedback and thoughts. That said, there are still a few things I'd like to address before landing this: - [ ] Remove travis-ci in favour of github workflow CI with auto-publishing and a nice way of testing feature set permutations and with/without std. - [ ] Port synth.rs and play_wav.rs examples from portaudio to cpal. - [ ] Add CHANGELOG wich section on migrating from `sample` -> `dasp`. - [ ] Complete the "crates" table within the top-level README. - [ ] Add a graphviz png of the dasp dependency tree to the README. - [ ] Add a note to the docs of feature-gated items specifying required features. - [ ] Publish a new version of `sample` with a README notice about switching to `dasp`. There are many other items I'd like to address, but they are likely best left for future PRs. cc @PieterPenninckx, @andrewcsmith Closes #116. Closes #112. --- .gitignore | 35 +- Cargo.toml | 43 +- README.md | 206 +-- assets/thumbpiano A#3.wav | Bin assets/two_vowels_10k.wav | Bin 0 -> 56704 bytes dasp/Cargo.toml | 58 + dasp/src/lib.rs | 36 + dasp_envelope/Cargo.toml | 21 + dasp_envelope/src/detect/mod.rs | 86 ++ dasp_envelope/src/detect/ops.rs | 12 + dasp_envelope/src/detect/peak.rs | 93 ++ dasp_envelope/src/detect/rms.rs | 28 + dasp_envelope/src/lib.rs | 6 + dasp_frame/Cargo.toml | 15 + src/frame.rs => dasp_frame/src/lib.rs | 309 +---- dasp_interpolate/Cargo.toml | 20 + dasp_interpolate/src/floor.rs | 31 + dasp_interpolate/src/lib.rs | 26 + dasp_interpolate/src/linear.rs | 44 + dasp_interpolate/src/sinc/mod.rs | 107 ++ dasp_interpolate/src/sinc/ops.rs | 23 + dasp_peak/Cargo.toml | 16 + src/peak.rs => dasp_peak/src/lib.rs | 93 +- dasp_ring_buffer/Cargo.toml | 9 + .../src/lib.rs | 150 +-- .../tests}/ring_buffer.rs | 10 +- dasp_rms/Cargo.toml | 17 + src/rms.rs => dasp_rms/src/lib.rs | 49 +- dasp_sample/Cargo.toml | 18 + dasp_sample/README.md | 201 +++ {src => dasp_sample/src}/conv.rs | 309 +---- {src => dasp_sample/src}/lib.rs | 211 +--- dasp_sample/src/ops.rs | 27 + {src => dasp_sample/src}/types.rs | 41 +- {tests => dasp_sample/tests}/conv.rs | 10 +- {tests => dasp_sample/tests}/types.rs | 12 +- dasp_signal/Cargo.toml | 43 + dasp_signal/src/boxed.rs | 20 + dasp_signal/src/bus.rs | 254 ++++ dasp_signal/src/envelope.rs | 89 ++ dasp_signal/src/interpolate.rs | 143 +++ src/signal.rs => dasp_signal/src/lib.rs | 1104 +++-------------- dasp_signal/src/ops.rs | 33 + dasp_signal/src/rms.rs | 91 ++ dasp_signal/src/window/hanning.rs | 21 + .../src/window/mod.rs | 116 +- dasp_signal/src/window/rectangle.rs | 21 + {tests => dasp_signal/tests}/interpolate.rs | 25 +- {tests => dasp_signal/tests}/signal.rs | 10 +- {tests => dasp_signal/tests}/window.rs | 17 +- dasp_slice/Cargo.toml | 17 + dasp_slice/src/boxed.rs | 235 ++++ dasp_slice/src/frame/fixed_size_array.rs | 210 ++++ dasp_slice/src/frame/mod.rs | 204 +++ dasp_slice/src/lib.rs | 351 ++++++ {tests => dasp_slice/tests}/slice.rs | 20 +- dasp_window/Cargo.toml | 17 + dasp_window/src/hanning/mod.rs | 21 + dasp_window/src/hanning/ops.rs | 13 + dasp_window/src/lib.rs | 15 + dasp_window/src/rectangle.rs | 14 + examples/Cargo.toml | 24 + examples/play_wav.rs | 16 +- examples/resample.rs | 15 +- examples/synth.rs | 28 +- examples/test.rs | 4 +- src/envelope/detect.rs | 195 --- src/envelope/mod.rs | 3 - src/interpolate.rs | 356 ------ src/slice.rs | 410 ------ 70 files changed, 3222 insertions(+), 3305 deletions(-) mode change 100755 => 100644 assets/thumbpiano A#3.wav create mode 100644 assets/two_vowels_10k.wav create mode 100644 dasp/Cargo.toml create mode 100644 dasp/src/lib.rs create mode 100644 dasp_envelope/Cargo.toml create mode 100644 dasp_envelope/src/detect/mod.rs create mode 100644 dasp_envelope/src/detect/ops.rs create mode 100644 dasp_envelope/src/detect/peak.rs create mode 100644 dasp_envelope/src/detect/rms.rs create mode 100644 dasp_envelope/src/lib.rs create mode 100644 dasp_frame/Cargo.toml rename src/frame.rs => dasp_frame/src/lib.rs (62%) create mode 100644 dasp_interpolate/Cargo.toml create mode 100644 dasp_interpolate/src/floor.rs create mode 100644 dasp_interpolate/src/lib.rs create mode 100644 dasp_interpolate/src/linear.rs create mode 100644 dasp_interpolate/src/sinc/mod.rs create mode 100644 dasp_interpolate/src/sinc/ops.rs create mode 100644 dasp_peak/Cargo.toml rename src/peak.rs => dasp_peak/src/lib.rs (95%) create mode 100644 dasp_ring_buffer/Cargo.toml rename src/ring_buffer.rs => dasp_ring_buffer/src/lib.rs (88%) rename {tests => dasp_ring_buffer/tests}/ring_buffer.rs (80%) create mode 100644 dasp_rms/Cargo.toml rename src/rms.rs => dasp_rms/src/lib.rs (88%) create mode 100644 dasp_sample/Cargo.toml create mode 100644 dasp_sample/README.md rename {src => dasp_sample/src}/conv.rs (75%) rename {src => dasp_sample/src}/lib.rs (70%) create mode 100644 dasp_sample/src/ops.rs rename {src => dasp_sample/src}/types.rs (99%) rename {tests => dasp_sample/tests}/conv.rs (98%) rename {tests => dasp_sample/tests}/types.rs (86%) create mode 100644 dasp_signal/Cargo.toml create mode 100644 dasp_signal/src/boxed.rs create mode 100644 dasp_signal/src/bus.rs create mode 100644 dasp_signal/src/envelope.rs create mode 100644 dasp_signal/src/interpolate.rs rename src/signal.rs => dasp_signal/src/lib.rs (73%) create mode 100644 dasp_signal/src/ops.rs create mode 100644 dasp_signal/src/rms.rs create mode 100644 dasp_signal/src/window/hanning.rs rename src/window.rs => dasp_signal/src/window/mod.rs (56%) create mode 100644 dasp_signal/src/window/rectangle.rs rename {tests => dasp_signal/tests}/interpolate.rs (79%) rename {tests => dasp_signal/tests}/signal.rs (74%) rename {tests => dasp_signal/tests}/window.rs (83%) create mode 100644 dasp_slice/Cargo.toml create mode 100644 dasp_slice/src/boxed.rs create mode 100644 dasp_slice/src/frame/fixed_size_array.rs create mode 100644 dasp_slice/src/frame/mod.rs create mode 100644 dasp_slice/src/lib.rs rename {tests => dasp_slice/tests}/slice.rs (61%) create mode 100644 dasp_window/Cargo.toml create mode 100644 dasp_window/src/hanning/mod.rs create mode 100644 dasp_window/src/hanning/ops.rs create mode 100644 dasp_window/src/lib.rs create mode 100644 dasp_window/src/rectangle.rs create mode 100644 examples/Cargo.toml delete mode 100644 src/envelope/detect.rs delete mode 100644 src/envelope/mod.rs delete mode 100644 src/interpolate.rs delete mode 100644 src/slice.rs diff --git a/.gitignore b/.gitignore index 91784e50..2c96eb1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,2 @@ -# RUST STUFF - -# Compiled files -*.o -*.so -*.rlib -*.dll - -# Executables -*.exe - -# Generated by Cargo -/target/ +target/ Cargo.lock - - - -# MAC STUFF - -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear on external disk -.Spotlight-V100 -.Trashes - -# Ignore the generated WAV file -/assets/two_vowels_10k.wav diff --git a/Cargo.toml b/Cargo.toml index dd535466..223b9ca8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,15 @@ -[package] -name = "sample" -description = "A crate providing the fundamentals for working with audio PCM DSP." -version = "0.10.0" -authors = ["mitchmindtree "] -readme = "README.md" -keywords = ["dsp", "bit-depth", "rate", "pcm", "audio"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/RustAudio/sample.git" -homepage = "https://github.com/RustAudio/sample" - -[dev-dependencies] -find_folder = "0.3" -hound = "2.0" -portaudio = "0.7" - -[features] -default = ["std", "slice", "envelope", "frame", "peak", "ring_buffer", "rms", "signal", "window", "interpolate"] -std = [] -slice = ["frame"] -envelope = ["frame", "peak", "ring_buffer", "rms", "interpolate", "signal"] -frame = [] -peak = ["frame"] -ring_buffer = [] -rms = ["frame", "ring_buffer"] -signal = ["frame", "envelope", "ring_buffer", "interpolate", "rms"] -window = ["frame", "signal"] -interpolate = ["ring_buffer", "frame", "signal"] +[workspace] +members = [ + "dasp", + "dasp_envelope", + "dasp_frame", + "dasp_interpolate", + "dasp_peak", + "dasp_ring_buffer", + "dasp_rms", + "dasp_sample", + "dasp_signal", + "dasp_slice", + "dasp_window", + "examples", +] diff --git a/README.md b/README.md index 7781c079..ecf7307f 100644 --- a/README.md +++ b/README.md @@ -1,191 +1,33 @@ -# sample [![Build Status](https://travis-ci.org/RustAudio/sample.svg?branch=master)](https://travis-ci.org/RustAudio/sample) [![Crates.io](https://img.shields.io/crates/v/sample.svg)](https://crates.io/crates/sample) [![Crates.io](https://img.shields.io/crates/l/sample.svg)](https://github.com/RustAudio/sample/blob/master/LICENSE-MIT) [![docs.rs](https://docs.rs/sample/badge.svg)](https://docs.rs/sample/) - -A crate providing the fundamentals for working with PCM (pulse-code modulation) -DSP (digital signal processing). In other words, `sample` provides a suite of -low-level, high-performance tools including types, traits and functions for -working with digital audio signals. - -The `sample` crate requires **no dynamic allocations**1 and has **no -dependencies**. The goal is to design a library akin to the **std, but for audio -DSP**; keeping the focus on portable and fast fundamentals. - -1: Besides the `Signal::bus` method, which is only necessary when -converting a `Signal` tree into a directed acyclic graph. - -Find the [API documentation here](https://docs.rs/sample/). - - -Features --------- - -Use the **Sample** trait to convert between and remain generic over any -bit-depth in an optimal, performance-sensitive manner. Implementations are -provided for all signed integer, unsigned integer and floating point primitive -types along with some custom types including 11, 20, 24 and 48-bit signed and -unsigned unpacked integers. For example: - -```rust -assert_eq!((-1.0).to_sample::(), 0); -assert_eq!(0.0.to_sample::(), 128); -assert_eq!(0i32.to_sample::(), 2_147_483_648); -assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); -assert_eq!(0.0, Sample::equilibrium()); -``` - -Use the **Frame** trait to remain generic over the number of channels at a -discrete moment in time. Implementations are provided for all fixed-size arrays -up to 32 elements in length. - -```rust -let foo = [0.1, 0.2, -0.1, -0.2]; -let bar = foo.scale_amp(2.0); -assert_eq!(bar, [0.2, 0.4, -0.2, -0.4]); - -assert_eq!(Mono::::equilibrium(), [0.0]); -assert_eq!(Stereo::::equilibrium(), [0.0, 0.0]); -assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); - -let foo = [0i16, 0]; -let bar: [u8; 2] = foo.map(Sample::to_sample); -assert_eq!(bar, [128u8, 128]); -``` - -Use the **Signal** trait for working with infinite-iterator-like types that -yield `Frame`s. **Signal** provides methods for adding, scaling, offsetting, -multiplying, clipping, generating, monitoring and buffering streams of `Frame`s. -Working with **Signal**s allows for easy, readable creation of rich and complex -DSP graphs with a simple and familiar API. - -```rust -// Clip to an amplitude of 0.9. -let frames = [[1.2, 0.8], [-0.7, -1.4]]; -let clipped: Vec<_> = signal::from_iter(frames.iter().cloned()).clip_amp(0.9).take(2).collect(); -assert_eq!(clipped, vec![[0.9, 0.8], [-0.7, -0.9]]); - -// Add `a` with `b` and yield the result. -let a = [[0.2], [-0.6], [0.5]]; -let b = [[0.2], [0.1], [-0.8]]; -let a_signal = signal::from_iter(a.iter().cloned()); -let b_signal = signal::from_iter(b.iter().cloned()); -let added: Vec<[f32; 1]> = a_signal.add_amp(b_signal).take(3).collect(); -assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); - -// Scale the playback rate by `0.5`. -let foo = [[0.0], [1.0], [0.0], [-1.0]]; -let mut source = signal::from_iter(foo.iter().cloned()); -let interp = Linear::from_source(&mut source); -let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); -assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); - -// Convert a signal to its RMS. -let signal = signal::rate(44_100.0).const_hz(440.0).sine();; -let ring_buffer = ring_buffer::Fixed::from([[0.0]; WINDOW_SIZE]); -let mut rms_signal = signal.rms(ring_buffer); -``` - -The **signal** module also provides a series of **Signal** source types, -including: - -- `FromIterator` -- `FromInterleavedSamplesIterator` -- `Equilibrium` (silent signal) -- `Phase` -- `Sine` -- `Saw` -- `Square` -- `Noise` -- `NoiseSimplex` -- `Gen` (generate frames from a Fn() -> F) -- `GenMut` (generate frames from a FnMut() -> F) - -Use the **slice** module functions for processing chunks of `Frame`s. -Conversion functions are provided for safely converting between slices of -interleaved `Sample`s and slices of `Frame`s without requiring any allocation. -For example: - -```rust -let frames = &[[0.0, 0.5], [0.0, -0.5]][..]; -let samples = sample::slice::to_sample_slice(frames); -assert_eq!(samples, &[0.0, 0.5, 0.0, -0.5][..]); - -let samples = &[0.0, 0.5, 0.0, -0.5][..]; -let frames = sample::slice::to_frame_slice(samples); -assert_eq!(frames, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); - -let samples = &[0.0, 0.5, 0.0][..]; -let frames = sample::slice::to_frame_slice(samples); -assert_eq!(frames, None::<&[[f32; 2]]>); -``` - -The **conv** module provides pure functions and traits for more specific -conversions. A function is provided for converting between every possible pair -of sample types. Traits include: - -- `FromSample`, `ToSample`, `Duplex`, -- `FromSampleSlice`, `ToSampleSlice`, `DuplexSampleSlice`, -- `FromSampleSliceMut`, `ToSampleSliceMut`, `DuplexSampleSliceMut`, -- `FromFrameSlice`, `ToFrameSlice`, `DuplexFrameSlice`, -- `FromFrameSliceMut`, `ToFrameSliceMut`, `DuplexFrameSliceMut`, -- `DuplexSlice`, `DuplexSliceMut`, - -The **interpolate** module provides a **Converter** type, for converting and -interpolating the rate of **Signal**s. This can be useful for both sample rate -conversion and playback rate multiplication. **Converter**s can use a range of -interpolation methods, with Floor, Linear, and Sinc interpolation provided in -the library. (NB: Sinc interpolation currently requires heap allocation, as it -uses VecDeque.) - -The **ring_buffer** module provides generic **Fixed** and **Bounded** ring -buffer types, both of which may be used with owned, borrowed, stack and -allocated buffers. - -The **peak** module can be used for monitoring the peak of a signal. Provided -peak rectifiers include `full_wave`, `positive_half_wave` and -`negative_half_wave`. - -The **rms** module provides a flexible **Rms** type that can be used for RMS -(root mean square) detection. Any **Fixed** ring buffer can be used as the -window for the RMS detection. - -The **envelope** module provides a **Detector** type (also known as a -*Follower*) that allows for detecting the envelope of a signal. **Detector** is -generic over the type of **Detect**ion - **Rms** and **Peak** detection are -provided. For example: - -```rust -let signal = signal::rate(4.0).const_hz(1.0).sine(); -let attack = 1.0; -let release = 1.0; -let detector = envelope::Detector::peak(attack, release); -let mut envelope = signal.detect_envelope(detector); -assert_eq!( - envelope.take(4).collect::>(), - vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] -); -``` - - -Using in a `no_std` environment -------------------------------- - -This crate is largely dependency free, even of things outside `core`. The -`no_std` cargo feature will enable using `sample` in these environments. -Currently, only nightly is supported, because it explicitly depends on the -`alloc` and `collections` for datastructures and `core_intrinsics` for some of -the math. If this restriction is onerous for you, it can be lifted with minor -loss of functionality (the `Signal::bus` method), so open an issue! - - -Contributions -------------- +# dasp + +**Digital Audio Signal Processing in Rust.** + +A suite of crates providing the fundamentals for working with PCM (pulse-code +modulation) DSP (digital signal processing). In other words, `dasp` provides a +suite of low-level, high-performance tools including types, traits and functions +for working with digital audio signals. + + +## Crates + +**dasp** is a modular collection of crates, allowing users to select the precise +set of tools required for their project. The following crates are included +within this repository: + +| Crate | Badges | Description | +| --- | --- | --- | +| dasp_sample | | | +| dasp | | | + + +## Contributing If the **sample** crate is missing types, conversions or other fundamental functionality that you wish it had, feel free to open an issue or pull request! The more hands on deck, the merrier :) -License -------- +## License Licensed under either of diff --git a/assets/thumbpiano A#3.wav b/assets/thumbpiano A#3.wav old mode 100755 new mode 100644 diff --git a/assets/two_vowels_10k.wav b/assets/two_vowels_10k.wav new file mode 100644 index 0000000000000000000000000000000000000000..97dbcc6c656274456c56743b5c3f3155b354dde5 GIT binary patch literal 56704 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Ð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 literal 0 HcmV?d00001 diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml new file mode 100644 index 00000000..9623d4b5 --- /dev/null +++ b/dasp/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "dasp" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_envelope = { path = "../dasp_envelope", default-features = false, optional = true } +dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_interpolate = { path = "../dasp_interpolate", default-features = false, optional = true } +dasp_peak = { path = "../dasp_peak", default-features = false, optional = true } +dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false, optional = true } +dasp_rms = { path = "../dasp_rms", default-features = false, optional = true } +dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_signal = { path = "../dasp_signal", default-features = false, optional = true } +dasp_slice = { path = "../dasp_slice", default-features = false, optional = true } +dasp_window = { path = "../dasp_window", default-features = false, optional = true } + +[features] +default = ["std"] +std = [ + "dasp_envelope/std", + "dasp_frame/std", + "dasp_interpolate/std", + "dasp_peak/std", + "dasp_ring_buffer/std", + "dasp_rms/std", + "dasp_sample/std", + "dasp_signal/std", + "dasp_slice/std", + "dasp_window/std", +] +envelope = ["dasp_envelope"] +envelope-peak = ["dasp_envelope/peak"] +envelope-rms = ["dasp_envelope/rms"] +interpolate = ["dasp_interpolate"] +interpolate-floor = ["dasp_interpolate/floor"] +interpolate-linear = ["dasp_interpolate/linear"] +interpolate-sinc = ["dasp_interpolate/sinc"] +peak = ["dasp_peak"] +ring_buffer = ["dasp_ring_buffer"] +rms = ["dasp_rms"] +signal = ["dasp_signal"] +signal-boxed = ["dasp_signal/boxed"] +signal-bus = ["dasp_signal/bus"] +signal-envelope = ["dasp_signal/envelope", "envelope"] +signal-rms = ["dasp_signal/rms", "rms"] +signal-window = ["dasp_signal/window", "window"] +signal-window-hanning = ["dasp_signal/window-hanning", "window-hanning"] +signal-window-rectangle = ["dasp_signal/window-rectangle", "window-rectangle"] +slice = ["dasp_slice"] +slice-boxed = ["dasp_slice/boxed"] +window = ["dasp_window"] +window-hanning = ["dasp_window/hanning"] +window-rectangle = ["dasp_window/rectangle"] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs new file mode 100644 index 00000000..9f1aab00 --- /dev/null +++ b/dasp/src/lib.rs @@ -0,0 +1,36 @@ +//! A crate of fundamentals for audio PCM DSP. +//! +//! - Use the [**Sample** trait](./trait.Sample.html) to remain generic across bit-depth. +//! - Use the [**Frame** trait](./frame/trait.Frame.html) to remain generic over channel layout. +//! - Use the [**Signal** trait](./signal/trait.Signal.html) for working with **Iterators** that yield **Frames**. +//! - Use the [**slice** module](./slice/index.html) for working with slices of **Samples** and **Frames**. +//! - See the [**conv** module](./conv/index.html) for fast conversions between slices, frames and samples. +//! - See the [**types** module](./types/index.html) for provided custom sample types. +//! - See the [**interpolate** module](./interpolate/index.html) for sample rate conversion and scaling. +//! - See the [**ring_buffer** module](./ring_buffer/index.html) for fast FIFO queue options. + +#[cfg(feature = "envelope")] +#[doc(inline)] +pub use dasp_envelope as envelope; +#[doc(inline)] +pub use dasp_frame::{self as frame, Frame}; +#[cfg(feature = "interpolate")] +#[doc(inline)] +pub use dasp_interpolate as interpolate; +#[cfg(feature = "peak")] +#[doc(inline)] +pub use dasp_peak as peak; +#[cfg(feature = "ring_buffer")] +#[doc(inline)] +pub use dasp_ring_buffer as ring_buffer; +#[doc(inline)] +pub use dasp_sample::{self as sample, Sample}; +#[cfg(feature = "signal")] +#[doc(inline)] +pub use dasp_signal::{self as signal, Signal}; +#[cfg(feature = "slice")] +#[doc(inline)] +pub use dasp_slice as slice; +#[cfg(feature = "signal")] +#[doc(inline)] +pub use dasp_window as window; diff --git a/dasp_envelope/Cargo.toml b/dasp_envelope/Cargo.toml new file mode 100644 index 00000000..e783b13d --- /dev/null +++ b/dasp_envelope/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "dasp_envelope" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_peak = { path = "../dasp_peak", default-features = false, optional = true } +dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } +dasp_rms = { path = "../dasp_rms", default-features = false, optional = true } +dasp_sample = { path = "../dasp_sample", default-features = false } + +[features] +default = ["std"] +peak = ["dasp_peak"] +rms = ["dasp_rms"] +std = ["dasp_frame/std", "dasp_peak/std", "dasp_ring_buffer/std", "dasp_rms/std", "dasp_sample/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_envelope/src/detect/mod.rs b/dasp_envelope/src/detect/mod.rs new file mode 100644 index 00000000..5728110d --- /dev/null +++ b/dasp_envelope/src/detect/mod.rs @@ -0,0 +1,86 @@ +use dasp_frame::Frame; +use dasp_sample::Sample; +use ops::f32::powf32; + +mod ops; +#[cfg(feature = "peak")] +mod peak; +#[cfg(feature = "rms")] +mod rms; + +/// A type that can be used to detect the envelope of a signal. +#[derive(Clone, Debug)] +pub struct Detector +where + F: Frame, + D: Detect, +{ + last_env_frame: D::Output, + attack_gain: f32, + release_gain: f32, + detect: D, +} + +/// Types that may be used to detect an envelope over a signal. +pub trait Detect +where + F: Frame, +{ + /// The result of detection. + type Output: Frame; + /// Given some frame, return the detected envelope over each channel. + fn detect(&mut self, frame: F) -> Self::Output; +} + +fn calc_gain(n_frames: f32) -> f32 { + if n_frames == 0.0 { + 0.0 + } else { + powf32(core::f32::consts::E, -1.0 / n_frames) + } +} + +impl Detector +where + F: Frame, + D: Detect, +{ + #[cfg(any(feature = "peak", feature = "rms"))] + fn new(detect: D, attack_frames: f32, release_frames: f32) -> Self { + Detector { + last_env_frame: D::Output::equilibrium(), + attack_gain: calc_gain(attack_frames), + release_gain: calc_gain(release_frames), + detect: detect, + } + } + + /// Set the **Detector**'s attack time as a number of frames. + pub fn set_attack_frames(&mut self, frames: f32) { + self.attack_gain = calc_gain(frames); + } + + /// Set the **Detector**'s release time as a number of frames. + pub fn set_release_frames(&mut self, frames: f32) { + self.release_gain = calc_gain(frames); + } + + /// Given the next input signal frame, detect and return the next envelope frame. + pub fn next(&mut self, frame: F) -> D::Output { + let Detector { + attack_gain, + release_gain, + ref mut detect, + ref mut last_env_frame, + } = *self; + + let detected_frame = detect.detect(frame); + let new_env_frame = last_env_frame.zip_map(detected_frame, |l, d| { + let gain = if l < d { attack_gain } else { release_gain }; + let diff = l.add_amp(-d.to_signed_sample()); + d.add_amp(diff.mul_amp(gain.to_sample()).to_sample()) + }); + *last_env_frame = new_env_frame; + new_env_frame + } +} diff --git a/dasp_envelope/src/detect/ops.rs b/dasp_envelope/src/detect/ops.rs new file mode 100644 index 00000000..252c7667 --- /dev/null +++ b/dasp_envelope/src/detect/ops.rs @@ -0,0 +1,12 @@ +#![allow(dead_code)] + +pub mod f32 { + #[cfg(feature = "std")] + pub fn powf32(a: f32, b: f32) -> f32 { + a.powf(b) + } + #[cfg(not(feature = "std"))] + pub fn powf32(a: f32, b: f32) -> f32 { + unsafe { core::intrinsics::powf32(a, b) } + } +} diff --git a/dasp_envelope/src/detect/peak.rs b/dasp_envelope/src/detect/peak.rs new file mode 100644 index 00000000..caa0aca1 --- /dev/null +++ b/dasp_envelope/src/detect/peak.rs @@ -0,0 +1,93 @@ +use crate::{Detect, Detector}; +use dasp_frame::Frame; +use dasp_peak as peak; + +/// A `Peak` detector, generic over the `FullWave`, `PositiveHalfWave`, `NegativeHalfWave` +/// rectifiers. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Peak { + rectifier: R, +} + +impl Peak { + /// A signal rectifier that produces the absolute amplitude from samples. + pub fn full_wave() -> Self { + peak::FullWave.into() + } +} + +impl Peak { + /// A signal rectifier that produces only the positive samples. + pub fn positive_half_wave() -> Self { + peak::PositiveHalfWave.into() + } +} + +impl Peak { + /// A signal rectifier that produces only the negative samples. + pub fn negative_half_wave() -> Self { + peak::NegativeHalfWave.into() + } +} + +impl Detector> +where + F: Frame, + R: peak::Rectifier, +{ + /// Construct a new **Peak** **Detector** that uses the given rectifier. + pub fn peak_from_rectifier(rectifier: R, attack_frames: f32, release_frames: f32) -> Self { + let peak = rectifier.into(); + Self::new(peak, attack_frames, release_frames) + } +} + +impl Detector> +where + F: Frame, +{ + /// Construct a new full wave **Peak** **Detector**. + pub fn peak(attack_frames: f32, release_frames: f32) -> Self { + let peak = Peak::full_wave(); + Self::new(peak, attack_frames, release_frames) + } +} + +impl Detector> +where + F: Frame, +{ + /// Construct a new positive half wave **Peak** **Detector**. + pub fn peak_positive_half_wave(attack_frames: f32, release_frames: f32) -> Self { + let peak = Peak::positive_half_wave(); + Self::new(peak, attack_frames, release_frames) + } +} + +impl Detector> +where + F: Frame, +{ + /// Construct a new positive half wave **Peak** **Detector**. + pub fn peak_negative_half_wave(attack_frames: f32, release_frames: f32) -> Self { + let peak = Peak::negative_half_wave(); + Self::new(peak, attack_frames, release_frames) + } +} + +impl Detect for Peak +where + F: Frame, + R: peak::Rectifier, +{ + type Output = R::Output; + fn detect(&mut self, frame: F) -> Self::Output { + self.rectifier.rectify(frame) + } +} + +impl From for Peak { + fn from(rectifier: R) -> Self { + Peak { rectifier: rectifier } + } +} diff --git a/dasp_envelope/src/detect/rms.rs b/dasp_envelope/src/detect/rms.rs new file mode 100644 index 00000000..27542b77 --- /dev/null +++ b/dasp_envelope/src/detect/rms.rs @@ -0,0 +1,28 @@ +use crate::{Detect, Detector}; +use dasp_frame::Frame; +use dasp_rms as rms; +use dasp_ring_buffer as ring_buffer; + +impl Detect for rms::Rms +where + F: Frame, + S: ring_buffer::Slice + + ring_buffer::SliceMut, +{ + type Output = F::Float; + fn detect(&mut self, frame: F) -> Self::Output { + self.next(frame) + } +} + +impl Detector> +where + F: Frame, + S: ring_buffer::Slice + ring_buffer::SliceMut, +{ + /// Construct a new **Rms** **Detector**. + pub fn rms(buffer: ring_buffer::Fixed, attack_frames: f32, release_frames: f32) -> Self { + let rms = rms::Rms::new(buffer); + Self::new(rms, attack_frames, release_frames) + } +} diff --git a/dasp_envelope/src/lib.rs b/dasp_envelope/src/lib.rs new file mode 100644 index 00000000..051d721a --- /dev/null +++ b/dasp_envelope/src/lib.rs @@ -0,0 +1,6 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] + +pub mod detect; + +pub use self::detect::{Detect, Detector}; diff --git a/dasp_frame/Cargo.toml b/dasp_frame/Cargo.toml new file mode 100644 index 00000000..321b758a --- /dev/null +++ b/dasp_frame/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "dasp_frame" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_sample = { path = "../dasp_sample", default-features = false } + +[features] +default = ["std"] +std = ["dasp_sample/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/src/frame.rs b/dasp_frame/src/lib.rs similarity index 62% rename from src/frame.rs rename to dasp_frame/src/lib.rs index bf3683fe..5c878c03 100644 --- a/src/frame.rs +++ b/dasp_frame/src/lib.rs @@ -3,17 +3,9 @@ //! //! Implementations are provided for all fixed-size arrays up to 32 elements in length. -use conv::{ - DuplexBoxedFrameSlice, DuplexBoxedSampleSlice, DuplexBoxedSlice, DuplexFrameSlice, - DuplexFrameSliceMut, DuplexSampleSlice, DuplexSampleSliceMut, DuplexSlice, DuplexSliceMut, - FromBoxedFrameSlice, FromBoxedSampleSlice, FromFrameSlice, FromFrameSliceMut, FromSampleSlice, - FromSampleSliceMut, ToBoxedFrameSlice, ToBoxedSampleSlice, ToFrameSlice, ToFrameSliceMut, - ToSampleSlice, ToSampleSliceMut, -}; -use Sample; +#![cfg_attr(not(feature = "std"), no_std)] -pub type Mono = [S; 1]; -pub type Stereo = [S; 2]; +use dasp_sample::Sample; /// Represents one sample from each channel at a single discrete instance in time within a /// PCM signal. @@ -41,10 +33,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Examples /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; - /// use sample::frame::{Mono, Stereo}; + /// use dasp_frame::{Frame, Mono, Stereo}; /// /// fn main() { /// assert_eq!(Mono::::equilibrium(), [0.0]); @@ -95,9 +84,8 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{Frame, Sample}; + /// use dasp_frame::Frame; + /// use dasp_sample::Sample; /// /// fn main() { /// let foo = [0i16, 0]; @@ -126,9 +114,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; + /// use dasp_frame::Frame; /// /// fn main() { /// let foo = [128u8; 2]; @@ -143,9 +129,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; + /// use dasp_frame::Frame; /// /// fn main() { /// let foo = [128u8; 2]; @@ -161,9 +145,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; + /// use dasp_frame::Frame; /// /// fn main() { /// assert_eq!([0.25, -0.5].offset_amp(0.5), [0.75, 0.0]); @@ -187,9 +169,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; + /// use dasp_frame::Frame; /// /// fn main() { /// assert_eq!([0.1, 0.2, -0.1, -0.2].scale_amp(2.0), [0.2, 0.4, -0.2, -0.4]); @@ -205,9 +185,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; + /// use dasp_frame::Frame; /// /// fn main() { /// let foo = [0.25, 0.5].add_amp([-0.75, 0.25]); @@ -227,9 +205,7 @@ pub trait Frame: Copy + Clone + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Frame; + /// use dasp_frame::Frame; /// /// fn main() { /// let foo = [0.25, 0.4].mul_amp([0.2, 0.5]); @@ -248,6 +224,9 @@ pub trait Frame: Copy + Clone + PartialEq { } } +pub type Mono = [S; 1]; +pub type Stereo = [S; 2]; + /// An iterator that yields the sample for each channel in the frame by value. #[derive(Clone)] pub struct Channels { @@ -462,263 +441,3 @@ where F::n_channels() - self.next_idx } } - -impl<'a, F> FromFrameSlice<'a, F> for &'a [F] -where - F: Frame, -{ - #[inline] - fn from_frame_slice(slice: &'a [F]) -> Self { - slice - } -} - -impl<'a, F> FromFrameSliceMut<'a, F> for &'a mut [F] -where - F: Frame, -{ - #[inline] - fn from_frame_slice_mut(slice: &'a mut [F]) -> Self { - slice - } -} - -impl FromBoxedFrameSlice for Box<[F]> -where - F: Frame, -{ - #[inline] - fn from_boxed_frame_slice(slice: Box<[F]>) -> Self { - slice - } -} - -/// A macro for implementing all audio slice conversion traits for each fixed-size array. -macro_rules! impl_from_slice_conversions { - ($($N:expr)*) => { - $( - - impl<'a, S> FromSampleSlice<'a, S> for &'a [[S; $N]] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn from_sample_slice(slice: &'a [S]) -> Option { - let len = slice.len(); - if len % $N == 0 { - let new_len = len / $N; - let ptr = slice.as_ptr() as *const _; - let new_slice = unsafe { - core::slice::from_raw_parts(ptr, new_len) - }; - Some(new_slice) - } else { - None - } - } - } - - impl<'a, S> FromSampleSliceMut<'a, S> for &'a mut [[S; $N]] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn from_sample_slice_mut(slice: &'a mut [S]) -> Option { - let len = slice.len(); - if len % $N == 0 { - let new_len = len / $N; - let ptr = slice.as_ptr() as *mut _; - let new_slice = unsafe { - core::slice::from_raw_parts_mut(ptr, new_len) - }; - Some(new_slice) - } else { - None - } - } - } - - impl FromBoxedSampleSlice for Box<[[S; $N]]> - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn from_boxed_sample_slice(mut slice: Box<[S]>) -> Option { - - // First, we need a raw pointer to the slice and to make sure that the `Box` is - // forgotten so that our slice does not get deallocated. - let len = slice.len(); - let slice_ptr = &mut slice as &mut [S] as *mut [S]; - core::mem::forget(slice); - let sample_slice = unsafe { - core::slice::from_raw_parts_mut((*slice_ptr).as_mut_ptr(), len) - }; - - // Convert to our frame slice if possible. - let frame_slice = match <&mut [[S; $N]]>::from_sample_slice_mut(sample_slice) { - Some(slice) => slice, - None => return None, - }; - let ptr = frame_slice as *mut [[S; $N]]; - - // Take ownership over the slice again before returning it. - let new_slice = unsafe { - Box::from_raw(ptr) - }; - - Some(new_slice) - } - } - - impl<'a, S> FromFrameSlice<'a, [S; $N]> for &'a [S] - where [S; $N]: Frame, - { - #[inline] - fn from_frame_slice(slice: &'a [[S; $N]]) -> Self { - let new_len = slice.len() * $N; - let ptr = slice.as_ptr() as *const _; - unsafe { - core::slice::from_raw_parts(ptr, new_len) - } - } - } - - impl<'a, S> FromFrameSliceMut<'a, [S; $N]> for &'a mut [S] - where [S; $N]: Frame, - { - #[inline] - fn from_frame_slice_mut(slice: &'a mut [[S; $N]]) -> Self { - let new_len = slice.len() * $N; - let ptr = slice.as_ptr() as *mut _; - unsafe { - core::slice::from_raw_parts_mut(ptr, new_len) - } - } - } - - impl FromBoxedFrameSlice<[S; $N]> for Box<[S]> - where [S; $N]: Frame, - { - #[inline] - fn from_boxed_frame_slice(mut slice: Box<[[S; $N]]>) -> Self { - let new_len = slice.len() * $N; - let frame_slice_ptr = &mut slice as &mut [[S; $N]] as *mut [[S; $N]]; - core::mem::forget(slice); - let sample_slice_ptr = frame_slice_ptr as *mut [S]; - unsafe { - let ptr = (*sample_slice_ptr).as_mut_ptr(); - let sample_slice = core::slice::from_raw_parts_mut(ptr, new_len); - Box::from_raw(sample_slice as *mut _) - } - } - } - - impl<'a, S> ToSampleSlice<'a, S> for &'a [[S; $N]] - where S: Sample, - { - #[inline] - fn to_sample_slice(self) -> &'a [S] { - FromFrameSlice::from_frame_slice(self) - } - } - - impl<'a, S> ToSampleSliceMut<'a, S> for &'a mut [[S; $N]] - where S: Sample, - { - #[inline] - fn to_sample_slice_mut(self) -> &'a mut [S] { - FromFrameSliceMut::from_frame_slice_mut(self) - } - } - - impl ToBoxedSampleSlice for Box<[[S; $N]]> - where S: Sample, - { - #[inline] - fn to_boxed_sample_slice(self) -> Box<[S]> { - FromBoxedFrameSlice::from_boxed_frame_slice(self) - } - } - - impl<'a, S> ToFrameSlice<'a, [S; $N]> for &'a [S] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn to_frame_slice(self) -> Option<&'a [[S; $N]]> { - FromSampleSlice::from_sample_slice(self) - } - } - - impl<'a, S> ToFrameSliceMut<'a, [S; $N]> for &'a mut [S] - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn to_frame_slice_mut(self) -> Option<&'a mut [[S; $N]]> { - FromSampleSliceMut::from_sample_slice_mut(self) - } - } - - impl ToBoxedFrameSlice<[S; $N]> for Box<[S]> - where S: Sample, - [S; $N]: Frame, - { - #[inline] - fn to_boxed_frame_slice(self) -> Option> { - FromBoxedSampleSlice::from_boxed_sample_slice(self) - } - } - - )* - }; -} - -impl_from_slice_conversions! { - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 -} - -impl<'a, F, T> DuplexFrameSlice<'a, F> for T -where - F: Frame, - T: FromFrameSlice<'a, F> + ToFrameSlice<'a, F>, -{ -} - -impl<'a, S, F, T> DuplexSlice<'a, S, F> for T -where - S: Sample, - F: Frame, - T: DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F>, -{ -} - -impl<'a, F, T> DuplexFrameSliceMut<'a, F> for T -where - F: Frame, - T: FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F>, -{ -} - -impl<'a, S, F, T> DuplexSliceMut<'a, S, F> for T -where - S: Sample, - F: Frame, - T: DuplexSampleSliceMut<'a, S> + DuplexFrameSliceMut<'a, F>, -{ -} - -impl DuplexBoxedFrameSlice for T -where - F: Frame, - T: FromBoxedFrameSlice + ToBoxedFrameSlice, -{ -} - -impl DuplexBoxedSlice for T -where - S: Sample, - F: Frame, - T: DuplexBoxedSampleSlice + DuplexBoxedFrameSlice, -{ -} diff --git a/dasp_interpolate/Cargo.toml b/dasp_interpolate/Cargo.toml new file mode 100644 index 00000000..d79ce0fe --- /dev/null +++ b/dasp_interpolate/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "dasp_interpolate" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } +dasp_sample = { path = "../dasp_sample", default-features = false } + +[features] +default = ["std"] +floor = [] +linear = [] +sinc = [] +std = ["dasp_frame/std", "dasp_ring_buffer/std", "dasp_sample/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_interpolate/src/floor.rs b/dasp_interpolate/src/floor.rs new file mode 100644 index 00000000..d97c51dc --- /dev/null +++ b/dasp_interpolate/src/floor.rs @@ -0,0 +1,31 @@ +use crate::Interpolator; +use dasp_frame::Frame; +use dasp_sample::Duplex; + +/// Interpolator that rounds off any values to the previous value from the source. +pub struct Floor { + left: F, +} + +impl Floor { + /// Create a new Floor Interpolator. + pub fn new(left: F) -> Floor { + Floor { left: left } + } +} + +impl Interpolator for Floor +where + F: Frame, + F::Sample: Duplex, +{ + type Frame = F; + + fn interpolate(&self, _x: f64) -> Self::Frame { + self.left + } + + fn next_source_frame(&mut self, source_frame: Self::Frame) { + self.left = source_frame; + } +} diff --git a/dasp_interpolate/src/lib.rs b/dasp_interpolate/src/lib.rs new file mode 100644 index 00000000..e1ae551f --- /dev/null +++ b/dasp_interpolate/src/lib.rs @@ -0,0 +1,26 @@ +//! The Interpolate module allows for conversion between various sample rates. + +use dasp_frame::Frame; + +#[cfg(feature = "floor")] +pub mod floor; +#[cfg(feature = "linear")] +pub mod linear; +#[cfg(feature = "sinc")] +pub mod sinc; + +/// Types that can interpolate between two values. +/// +/// Implementations should keep track of the necessary data both before and after the current +/// frame. +pub trait Interpolator { + /// The type of frame over which the interpolate may operate. + type Frame: Frame; + + /// Given a distance between [0.0 and 1.0) toward the following sample, return the interpolated + /// value. + fn interpolate(&self, x: f64) -> Self::Frame; + + /// To be called whenever the Interpolator value steps passed 1.0. + fn next_source_frame(&mut self, source_frame: Self::Frame); +} diff --git a/dasp_interpolate/src/linear.rs b/dasp_interpolate/src/linear.rs new file mode 100644 index 00000000..09b02e14 --- /dev/null +++ b/dasp_interpolate/src/linear.rs @@ -0,0 +1,44 @@ +use crate::Interpolator; +use dasp_frame::Frame; +use dasp_sample::{Duplex, Sample}; + +/// Interpolator that interpolates linearly between the previous value and the next value +pub struct Linear { + left: F, + right: F, +} + +impl Linear { + /// Create a new Linear Interpolator. + pub fn new(left: F, right: F) -> Linear { + Linear { + left: left, + right: right, + } + } +} + +impl Interpolator for Linear +where + F: Frame, + F::Sample: Duplex, +{ + type Frame = F; + + /// Converts linearly from the previous value, using the next value to interpolate. It is + /// possible, although not advisable, to provide an x > 1.0 or < 0.0, but this will just + /// continue to be a linear ramp in one direction or another. + fn interpolate(&self, x: f64) -> Self::Frame { + self.left.zip_map(self.right, |l, r| { + let l_f = l.to_sample::(); + let r_f = r.to_sample::(); + let diff = r_f - l_f; + ((diff * x) + l_f).to_sample::<::Sample>() + }) + } + + fn next_source_frame(&mut self, source_frame: Self::Frame) { + self.left = self.right; + self.right = source_frame; + } +} diff --git a/dasp_interpolate/src/sinc/mod.rs b/dasp_interpolate/src/sinc/mod.rs new file mode 100644 index 00000000..429b6ed3 --- /dev/null +++ b/dasp_interpolate/src/sinc/mod.rs @@ -0,0 +1,107 @@ +use core::f64::consts::PI; +use crate::Interpolator; +use dasp_frame::Frame; +use dasp_ring_buffer as ring_buffer; +use dasp_sample::{Duplex, Sample}; +use ops::f64::{cos, sin}; + +mod ops; + +/// Interpolator for sinc interpolation. +/// +/// Generally accepted as one of the better sample rate converters, although it uses significantly +/// more computation. +pub struct Sinc { + frames: ring_buffer::Fixed, + idx: usize, +} + +impl Sinc { + /// Create a new **Sinc** interpolator with the given ring buffer. + /// + /// The given ring buffer should have a length twice that of the desired sinc interpolation + /// `depth`. + /// + /// The initial contents of the ring_buffer will act as padding for the interpolated signal. + /// + /// **panic!**s if the given ring buffer's length is not a multiple of `2`. + pub fn new(frames: ring_buffer::Fixed) -> Self + where + S: ring_buffer::SliceMut, + S::Element: Frame, + { + assert!(frames.len() % 2 == 0); + Sinc { + frames: frames, + idx: 0, + } + } + + fn depth(&self) -> usize + where + S: ring_buffer::Slice, + { + self.frames.len() / 2 + } +} + +impl Interpolator for Sinc +where + S: ring_buffer::SliceMut, + S::Element: Frame, + ::Sample: Duplex, +{ + type Frame = S::Element; + + /// Sinc interpolation + fn interpolate(&self, x: f64) -> Self::Frame { + let phil = x; + let phir = 1.0 - x; + let nl = self.idx; + let nr = self.idx + 1; + let depth = self.depth(); + + let rightmost = nl + depth; + let leftmost = nr as isize - depth as isize; + let max_depth = if rightmost >= self.frames.len() { + self.frames.len() - depth + } else if leftmost < 0 { + (depth as isize + leftmost) as usize + } else { + depth + }; + + (0..max_depth).fold(Self::Frame::equilibrium(), |mut v, n| { + v = { + let a = PI * (phil + n as f64); + let first = if a == 0.0 { 1.0 } else { sin(a) / a }; + let second = 0.5 + 0.5 * cos(a / (phil + max_depth as f64)); + v.zip_map(self.frames[nr - n], |vs, r_lag| { + vs.add_amp( + (first * second * r_lag.to_sample::()) + .to_sample::<::Sample>() + .to_signed_sample(), + ) + }) + }; + + let a = PI * (phir + n as f64); + let first = if a == 0.0 { 1.0 } else { sin(a) / a }; + let second = 0.5 + 0.5 * cos(a / (phir + max_depth as f64)); + v.zip_map(self.frames[nl + n], |vs, r_lag| { + vs.add_amp( + (first * second * r_lag.to_sample::()) + .to_sample::<::Sample>() + .to_signed_sample(), + ) + }) + }) + } + + fn next_source_frame(&mut self, source_frame: Self::Frame) { + let _old_frame = self.frames.push(source_frame); + if self.idx < self.depth() { + self.idx += 1; + } + } +} diff --git a/dasp_interpolate/src/sinc/ops.rs b/dasp_interpolate/src/sinc/ops.rs new file mode 100644 index 00000000..e2c15db4 --- /dev/null +++ b/dasp_interpolate/src/sinc/ops.rs @@ -0,0 +1,23 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] +#![allow(dead_code)] + +pub mod f64 { + #[cfg(not(feature = "std"))] + pub fn sin(x: f64) -> f64 { + unsafe { core::intrinsics::sinf64(x) } + } + #[cfg(feature = "std")] + pub fn sin(x: f64) -> f64 { + x.sin() + } + + #[cfg(not(feature = "std"))] + pub fn cos(x: f64) -> f64 { + unsafe { core::intrinsics::cosf64(x) } + } + #[cfg(feature = "std")] + pub fn cos(x: f64) -> f64 { + x.cos() + } +} diff --git a/dasp_peak/Cargo.toml b/dasp_peak/Cargo.toml new file mode 100644 index 00000000..5dbd6c03 --- /dev/null +++ b/dasp_peak/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dasp_peak" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_sample = { path = "../dasp_sample", default-features = false } + +[features] +default = ["std"] +std = ["dasp_frame/std", "dasp_sample/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/src/peak.rs b/dasp_peak/src/lib.rs similarity index 95% rename from src/peak.rs rename to dasp_peak/src/lib.rs index bea83cea..86768df4 100644 --- a/src/peak.rs +++ b/dasp_peak/src/lib.rs @@ -1,44 +1,19 @@ //! Peak envelope detection over a signal. -use {Frame, Sample}; +#![cfg_attr(not(feature = "std"), no_std)] -/// A signal rectifier that produces the absolute amplitude from samples. -pub fn full_wave(frame: F) -> F::Signed -where - F: Frame, -{ - frame.map(|s| { - let signed = s.to_signed_sample(); - if signed < Sample::equilibrium() { - -signed - } else { - signed - } - }) -} +use dasp_frame::Frame; +use dasp_sample::Sample; -/// A signal rectifier that produces only the positive samples. -pub fn positive_half_wave(frame: F) -> F -where - F: Frame, -{ - frame.map(|s| if s < Sample::equilibrium() { - Sample::equilibrium() - } else { - s - }) -} - -/// A signal rectifier that produces only the negative samples. -pub fn negative_half_wave(frame: F) -> F +/// Types that may be used to rectify a signal of frames `F` for a `Peak` detector. +pub trait Rectifier where F: Frame, { - frame.map(|s| if s > Sample::equilibrium() { - Sample::equilibrium() - } else { - s - }) + /// Frames that can be detected. + type Output: Frame; + /// Rectify the given frame. + fn rectify(&mut self, frame: F) -> Self::Output; } /// A signal rectifier that produces the absolute amplitude from samples. @@ -51,17 +26,6 @@ pub struct PositiveHalfWave; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub struct NegativeHalfWave; -/// Types that may be used to rectify a signal of frames `F` for a `Peak` detector. -pub trait Rectifier -where - F: Frame, -{ - /// Frames that can be detected. - type Output: Frame; - /// Rectify the given frame. - fn rectify(&mut self, frame: F) -> Self::Output; -} - impl Rectifier for FullWave where F: Frame, @@ -91,3 +55,42 @@ where negative_half_wave(frame) } } + +/// A signal rectifier that produces the absolute amplitude from samples. +pub fn full_wave(frame: F) -> F::Signed +where + F: Frame, +{ + frame.map(|s| { + let signed = s.to_signed_sample(); + if signed < Sample::equilibrium() { + -signed + } else { + signed + } + }) +} + +/// A signal rectifier that produces only the positive samples. +pub fn positive_half_wave(frame: F) -> F +where + F: Frame, +{ + frame.map(|s| if s < Sample::equilibrium() { + Sample::equilibrium() + } else { + s + }) +} + +/// A signal rectifier that produces only the negative samples. +pub fn negative_half_wave(frame: F) -> F +where + F: Frame, +{ + frame.map(|s| if s > Sample::equilibrium() { + Sample::equilibrium() + } else { + s + }) +} diff --git a/dasp_ring_buffer/Cargo.toml b/dasp_ring_buffer/Cargo.toml new file mode 100644 index 00000000..deb04f17 --- /dev/null +++ b/dasp_ring_buffer/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dasp_ring_buffer" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[features] +default = ["std"] +std = [] diff --git a/src/ring_buffer.rs b/dasp_ring_buffer/src/lib.rs similarity index 88% rename from src/ring_buffer.rs rename to dasp_ring_buffer/src/lib.rs index 9b6b9de4..cf38d86a 100644 --- a/src/ring_buffer.rs +++ b/dasp_ring_buffer/src/lib.rs @@ -7,13 +7,27 @@ //! - The [Fixed](./struct.Fixed.html) ring buffer type. //! - The [Bounded](./struct.Bounded.html) ring buffer type. -use Box; +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + use core::mem; use core::iter::{Chain, Cycle, FromIterator, Skip, Take}; use core::ptr; use core::ops::{Index, IndexMut}; use core::slice; -use Vec; + +#[cfg(not(feature = "std"))] +type Vec = alloc::vec::Vec; +#[cfg(feature = "std")] +#[allow(dead_code)] +type Vec = std::vec::Vec; + +#[cfg(not(feature = "std"))] +type Box = alloc::boxed::Box; +#[cfg(feature = "std")] +type Box = std::boxed::Box; //////////////////////// ///// SLICE TRAITS ///// @@ -137,25 +151,23 @@ impl_slice_for_arrays! { /// A `Fixed` ring buffer can be created around any type with a slice to write to. /// /// ``` -/// extern crate sample; -/// /// fn main() { /// // From a fixed size array. -/// sample::ring_buffer::Fixed::from([1, 2, 3, 4]); +/// dasp_ring_buffer::Fixed::from([1, 2, 3, 4]); /// /// // From a Vec. -/// sample::ring_buffer::Fixed::from(vec![1, 2, 3, 4]); +/// dasp_ring_buffer::Fixed::from(vec![1, 2, 3, 4]); /// /// // From a Boxed slice. -/// sample::ring_buffer::Fixed::from(vec![1, 2, 3].into_boxed_slice()); +/// dasp_ring_buffer::Fixed::from(vec![1, 2, 3].into_boxed_slice()); /// /// // From a mutably borrowed slice. /// let mut slice = [1, 2, 3, 4]; -/// sample::ring_buffer::Fixed::from(&mut slice[..]); +/// dasp_ring_buffer::Fixed::from(&mut slice[..]); /// /// // An immutable ring buffer from an immutable slice. /// let slice = [1, 2, 3, 4]; -/// sample::ring_buffer::Fixed::from(&slice[..]); +/// dasp_ring_buffer::Fixed::from(&slice[..]); /// } /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -171,12 +183,8 @@ where /// The fixed length of the buffer. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let rb = ring_buffer::Fixed::from([0; 4]); + /// let rb = dasp_ring_buffer::Fixed::from([0; 4]); /// assert_eq!(rb.len(), 4); /// } /// ``` @@ -189,12 +197,8 @@ where /// queue, ensuring that the length is retained. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Fixed::from([0, 1, 2, 3]); + /// let mut rb = dasp_ring_buffer::Fixed::from([0, 1, 2, 3]); /// assert_eq!(rb.push(4), 0); /// assert_eq!(rb.push(5), 1); /// assert_eq!(rb.push(6), 2); @@ -223,12 +227,8 @@ where /// If `index` is out of range it will be looped around the length of the data slice. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Fixed::from([0, 1, 2]); + /// let mut rb = dasp_ring_buffer::Fixed::from([0, 1, 2]); /// assert_eq!(*rb.get(0), 0); /// assert_eq!(*rb.get(1), 1); /// assert_eq!(*rb.get(2), 2); @@ -260,12 +260,8 @@ where /// If `index` is out of range it will be looped around the length of the data slice. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Fixed::from([0, 1, 2, 3]); + /// let mut rb = dasp_ring_buffer::Fixed::from([0, 1, 2, 3]); /// assert_eq!(rb[0], 0); /// rb.set_first(2); /// assert_eq!(rb[0], 2); @@ -285,10 +281,8 @@ where /// The first slice is always aligned contiguously behind the second slice. /// /// ``` - /// extern crate sample; - /// /// fn main() { - /// let mut ring_buffer = sample::ring_buffer::Fixed::from([0; 4]); + /// let mut ring_buffer = dasp_ring_buffer::Fixed::from([0; 4]); /// assert_eq!(ring_buffer.slices(), (&[0, 0, 0, 0][..], &[][..])); /// ring_buffer.push(1); /// ring_buffer.push(2); @@ -433,27 +427,23 @@ where /// popping elements. /// /// ``` -/// extern crate sample; -/// -/// use sample::ring_buffer; -/// /// fn main() { /// // From a fixed size array. -/// ring_buffer::Bounded::from([0; 4]); +/// dasp_ring_buffer::Bounded::from([0; 4]); /// /// // From a Vec. -/// ring_buffer::Bounded::from(vec![0; 4]); +/// dasp_ring_buffer::Bounded::from(vec![0; 4]); /// /// // From a Boxed slice. -/// ring_buffer::Bounded::from(vec![0; 3].into_boxed_slice()); +/// dasp_ring_buffer::Bounded::from(vec![0; 3].into_boxed_slice()); /// /// // From a mutably borrowed slice. /// let mut slice = [0; 4]; -/// ring_buffer::Bounded::from(&mut slice[..]); +/// dasp_ring_buffer::Bounded::from(&mut slice[..]); /// /// // An immutable ring buffer from an immutable slice. /// let slice = [0; 4]; -/// ring_buffer::Bounded::from(&slice[..]); +/// dasp_ring_buffer::Bounded::from(&slice[..]); /// } /// ``` /// @@ -461,16 +451,12 @@ where /// These are generally more efficient as they do not require initialising elements. /// /// ``` -/// extern crate sample; -/// -/// use sample::ring_buffer; -/// /// fn main() { /// // Fixed-size array. -/// ring_buffer::Bounded::<[i32; 4]>::array(); +/// dasp_ring_buffer::Bounded::<[i32; 4]>::array(); /// /// // Boxed slice. -/// let mut rb = ring_buffer::Bounded::boxed_slice(4); +/// let mut rb = dasp_ring_buffer::Bounded::boxed_slice(4); /// rb.push(1); /// } /// ``` @@ -500,12 +486,8 @@ where /// underlying slice with uninitialised memory. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::boxed_slice(4); + /// let mut rb = dasp_ring_buffer::Bounded::boxed_slice(4); /// assert_eq!(rb.max_len(), 4); /// assert_eq!(rb.len(), 0); /// rb.push(1); @@ -534,12 +516,8 @@ where /// underlying array with uninitialised memory. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::<[f32; 3]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::<[f32; 3]>::array(); /// assert_eq!(rb.len(), 0); /// assert_eq!(rb.max_len(), 3); /// } @@ -558,12 +536,8 @@ where /// elements and initialises the ring buffer with a length equal to `max_len`. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::from_full([0, 1, 2, 3]); + /// let mut rb = dasp_ring_buffer::Bounded::from_full([0, 1, 2, 3]); /// assert_eq!(rb.len(), rb.max_len()); /// assert_eq!(rb.pop(), Some(0)); /// assert_eq!(rb.pop(), Some(1)); @@ -580,10 +554,8 @@ where /// front of the buffer. /// /// ``` - /// extern crate sample; - /// /// fn main() { - /// let mut ring_buffer = sample::ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); /// assert_eq!(ring_buffer.max_len(), 3); /// } /// ``` @@ -595,10 +567,8 @@ where /// The current length of the ring buffer. /// /// ``` - /// extern crate sample; - /// /// fn main() { - /// let mut ring_buffer = sample::ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); /// assert_eq!(ring_buffer.len(), 0); /// } /// ``` @@ -612,12 +582,8 @@ where /// Equivalent to `self.len() == 0`. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::<[i32; 2]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 2]>::array(); /// assert!(rb.is_empty()); /// rb.push(0); /// assert!(!rb.is_empty()); @@ -632,12 +598,8 @@ where /// Equivalent to `self.len() == self.max_len()`. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::<[i32; 2]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 2]>::array(); /// assert!(!rb.is_full()); /// rb.push(0); /// rb.push(1); @@ -656,10 +618,8 @@ where /// The first slice is always aligned contiguously behind the second slice. /// /// ``` - /// extern crate sample; - /// /// fn main() { - /// let mut ring_buffer = sample::ring_buffer::Bounded::<[i32; 4]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 4]>::array(); /// assert_eq!(ring_buffer.slices(), (&[][..], &[][..])); /// ring_buffer.push(1); /// ring_buffer.push(2); @@ -703,12 +663,8 @@ where /// This method uses the `slices` method internally. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); /// assert_eq!(rb.iter().count(), 0); /// rb.push(1); /// rb.push(2); @@ -738,12 +694,8 @@ where /// Returns `None` if there is no element at the given index. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::<[i32; 4]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 4]>::array(); /// assert_eq!(rb.get(1), None); /// rb.push(0); /// rb.push(1); @@ -786,10 +738,8 @@ where /// the buffer and increases the length of the buffer by `1`. `None` is returned. /// /// ``` - /// extern crate sample; - /// /// fn main() { - /// let mut ring_buffer = sample::ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); /// assert_eq!(ring_buffer.push(1), None); /// assert_eq!(ring_buffer.push(2), None); /// assert_eq!(ring_buffer.len(), 2); @@ -834,12 +784,8 @@ where /// If the buffer is empty, this returns `None`. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::from_full([0, 1, 2]); + /// let mut rb = dasp_ring_buffer::Bounded::from_full([0, 1, 2]); /// assert_eq!(rb.len(), rb.max_len()); /// assert_eq!(rb.pop(), Some(0)); /// assert_eq!(rb.pop(), Some(1)); @@ -877,12 +823,8 @@ where /// That is, all non-yielded elements will remain in the ring buffer. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// /// fn main() { - /// let mut rb = ring_buffer::Bounded::from_full([0, 1, 2, 3]); + /// let mut rb = dasp_ring_buffer::Bounded::from_full([0, 1, 2, 3]); /// assert_eq!(rb.drain().take(2).collect::>(), vec![0, 1]); /// assert_eq!(rb.pop(), Some(2)); /// assert_eq!(rb.pop(), Some(3)); diff --git a/tests/ring_buffer.rs b/dasp_ring_buffer/tests/ring_buffer.rs similarity index 80% rename from tests/ring_buffer.rs rename to dasp_ring_buffer/tests/ring_buffer.rs index 720a7305..948e2578 100644 --- a/tests/ring_buffer.rs +++ b/dasp_ring_buffer/tests/ring_buffer.rs @@ -1,9 +1,5 @@ -extern crate sample; +use dasp_ring_buffer as ring_buffer; -#[cfg(feature="ring_buffer")] -use sample::ring_buffer; - -#[cfg(feature="ring_buffer")] #[test] fn test_bounded_boxed_slice() { let mut rb = ring_buffer::Bounded::boxed_slice(3); @@ -13,7 +9,6 @@ fn test_bounded_boxed_slice() { assert_eq!(rb.push(4), Some(1)); } -#[cfg(feature="ring_buffer")] #[test] fn test_bounded_array() { let mut rb = ring_buffer::Bounded::<[i32; 3]>::array(); @@ -23,14 +18,12 @@ fn test_bounded_array() { assert_eq!(rb.push(4), Some(1)); } -#[cfg(feature="ring_buffer")] #[test] #[should_panic] fn text_bounded_from_empty_vec() { ring_buffer::Bounded::from(Vec::::new()); } -#[cfg(feature="ring_buffer")] #[test] fn test_bounded_from_vec() { let mut rb = ring_buffer::Bounded::from(vec![1, 2, 3]); @@ -40,7 +33,6 @@ fn test_bounded_from_vec() { assert_eq!(rb.push(7), Some(4)); } -#[cfg(feature="ring_buffer")] #[test] #[should_panic] fn test_bounded_get_out_of_range() { diff --git a/dasp_rms/Cargo.toml b/dasp_rms/Cargo.toml new file mode 100644 index 00000000..3602c4da --- /dev/null +++ b/dasp_rms/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dasp_rms" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } +dasp_sample = { path = "../dasp_sample", default-features = false } + +[features] +default = ["std"] +std = ["dasp_frame/std", "dasp_ring_buffer/std", "dasp_sample/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/src/rms.rs b/dasp_rms/src/lib.rs similarity index 88% rename from src/rms.rs rename to dasp_rms/src/lib.rs index 621b80c1..45e7f84b 100644 --- a/src/rms.rs +++ b/dasp_rms/src/lib.rs @@ -2,10 +2,13 @@ //! //! The primary type of interest in this module is the [**Rms**](./struct.Rms). -use {FloatSample, Frame, Sample}; +#![cfg_attr(not(feature = "std"), no_std)] + use core::fmt; use core::marker::PhantomData; -use ring_buffer; +use dasp_frame::Frame; +use dasp_ring_buffer as ring_buffer; +use dasp_sample::{FloatSample, Sample}; /// Iteratively extracts the RMS (root mean square) envelope from a window over a signal of sample /// `Frame`s. @@ -37,10 +40,8 @@ where /// The window size of the **Rms** is equal to the length of the given ring buffer. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::rms::Rms; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_rms::Rms; /// /// fn main() { /// let window = ring_buffer::Fixed::from([[0.0]; 4]); @@ -59,10 +60,8 @@ where /// Zeroes the square_sum and the buffer of the `window`. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::rms::Rms; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_rms::Rms; /// /// fn main() { /// let window = ring_buffer::Fixed::from([[0.0]; 4]); @@ -86,10 +85,8 @@ where /// The length of the window as a number of frames. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::rms::Rms; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_rms::Rms; /// /// fn main() { /// let window = ring_buffer::Fixed::from([[0.0]; 4]); @@ -114,10 +111,8 @@ where /// This method uses `Rms::next_squared` internally and then calculates the square root. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::rms::Rms; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_rms::Rms; /// /// fn main() { /// let window = ring_buffer::Fixed::from([[0.0]; 4]); @@ -173,10 +168,8 @@ where /// Calculates the RMS of all frames currently stored within the inner window. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::rms::Rms; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_rms::Rms; /// /// fn main() { /// let window = ring_buffer::Fixed::from([[0.0]; 4]); @@ -206,12 +199,10 @@ where S: fmt::Debug + ring_buffer::Slice, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - f, - "Rms {{ frame: {:?}, window: {:?}, square_sum: {:?} }}", - &self.frame, - &self.window, - &self.square_sum - ) + f.debug_struct("Rms") + .field("frame", &self.frame) + .field("window", &self.window) + .field("square_sum", &self.square_sum) + .finish() } } diff --git a/dasp_sample/Cargo.toml b/dasp_sample/Cargo.toml new file mode 100644 index 00000000..466a7bbc --- /dev/null +++ b/dasp_sample/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "dasp_sample" +description = "A crate providing the fundamentals for working with audio PCM DSP." +version = "0.10.0" +authors = ["mitchmindtree "] +readme = "README.md" +keywords = ["dsp", "bit-depth", "rate", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/RustAudio/sample.git" +homepage = "https://github.com/RustAudio/sample" +edition = "2018" + +[features] +default = ["std"] +std = [] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_sample/README.md b/dasp_sample/README.md new file mode 100644 index 00000000..7781c079 --- /dev/null +++ b/dasp_sample/README.md @@ -0,0 +1,201 @@ +# sample [![Build Status](https://travis-ci.org/RustAudio/sample.svg?branch=master)](https://travis-ci.org/RustAudio/sample) [![Crates.io](https://img.shields.io/crates/v/sample.svg)](https://crates.io/crates/sample) [![Crates.io](https://img.shields.io/crates/l/sample.svg)](https://github.com/RustAudio/sample/blob/master/LICENSE-MIT) [![docs.rs](https://docs.rs/sample/badge.svg)](https://docs.rs/sample/) + +A crate providing the fundamentals for working with PCM (pulse-code modulation) +DSP (digital signal processing). In other words, `sample` provides a suite of +low-level, high-performance tools including types, traits and functions for +working with digital audio signals. + +The `sample` crate requires **no dynamic allocations**1 and has **no +dependencies**. The goal is to design a library akin to the **std, but for audio +DSP**; keeping the focus on portable and fast fundamentals. + +1: Besides the `Signal::bus` method, which is only necessary when +converting a `Signal` tree into a directed acyclic graph. + +Find the [API documentation here](https://docs.rs/sample/). + + +Features +-------- + +Use the **Sample** trait to convert between and remain generic over any +bit-depth in an optimal, performance-sensitive manner. Implementations are +provided for all signed integer, unsigned integer and floating point primitive +types along with some custom types including 11, 20, 24 and 48-bit signed and +unsigned unpacked integers. For example: + +```rust +assert_eq!((-1.0).to_sample::(), 0); +assert_eq!(0.0.to_sample::(), 128); +assert_eq!(0i32.to_sample::(), 2_147_483_648); +assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); +assert_eq!(0.0, Sample::equilibrium()); +``` + +Use the **Frame** trait to remain generic over the number of channels at a +discrete moment in time. Implementations are provided for all fixed-size arrays +up to 32 elements in length. + +```rust +let foo = [0.1, 0.2, -0.1, -0.2]; +let bar = foo.scale_amp(2.0); +assert_eq!(bar, [0.2, 0.4, -0.2, -0.4]); + +assert_eq!(Mono::::equilibrium(), [0.0]); +assert_eq!(Stereo::::equilibrium(), [0.0, 0.0]); +assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); + +let foo = [0i16, 0]; +let bar: [u8; 2] = foo.map(Sample::to_sample); +assert_eq!(bar, [128u8, 128]); +``` + +Use the **Signal** trait for working with infinite-iterator-like types that +yield `Frame`s. **Signal** provides methods for adding, scaling, offsetting, +multiplying, clipping, generating, monitoring and buffering streams of `Frame`s. +Working with **Signal**s allows for easy, readable creation of rich and complex +DSP graphs with a simple and familiar API. + +```rust +// Clip to an amplitude of 0.9. +let frames = [[1.2, 0.8], [-0.7, -1.4]]; +let clipped: Vec<_> = signal::from_iter(frames.iter().cloned()).clip_amp(0.9).take(2).collect(); +assert_eq!(clipped, vec![[0.9, 0.8], [-0.7, -0.9]]); + +// Add `a` with `b` and yield the result. +let a = [[0.2], [-0.6], [0.5]]; +let b = [[0.2], [0.1], [-0.8]]; +let a_signal = signal::from_iter(a.iter().cloned()); +let b_signal = signal::from_iter(b.iter().cloned()); +let added: Vec<[f32; 1]> = a_signal.add_amp(b_signal).take(3).collect(); +assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); + +// Scale the playback rate by `0.5`. +let foo = [[0.0], [1.0], [0.0], [-1.0]]; +let mut source = signal::from_iter(foo.iter().cloned()); +let interp = Linear::from_source(&mut source); +let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); +assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); + +// Convert a signal to its RMS. +let signal = signal::rate(44_100.0).const_hz(440.0).sine();; +let ring_buffer = ring_buffer::Fixed::from([[0.0]; WINDOW_SIZE]); +let mut rms_signal = signal.rms(ring_buffer); +``` + +The **signal** module also provides a series of **Signal** source types, +including: + +- `FromIterator` +- `FromInterleavedSamplesIterator` +- `Equilibrium` (silent signal) +- `Phase` +- `Sine` +- `Saw` +- `Square` +- `Noise` +- `NoiseSimplex` +- `Gen` (generate frames from a Fn() -> F) +- `GenMut` (generate frames from a FnMut() -> F) + +Use the **slice** module functions for processing chunks of `Frame`s. +Conversion functions are provided for safely converting between slices of +interleaved `Sample`s and slices of `Frame`s without requiring any allocation. +For example: + +```rust +let frames = &[[0.0, 0.5], [0.0, -0.5]][..]; +let samples = sample::slice::to_sample_slice(frames); +assert_eq!(samples, &[0.0, 0.5, 0.0, -0.5][..]); + +let samples = &[0.0, 0.5, 0.0, -0.5][..]; +let frames = sample::slice::to_frame_slice(samples); +assert_eq!(frames, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); + +let samples = &[0.0, 0.5, 0.0][..]; +let frames = sample::slice::to_frame_slice(samples); +assert_eq!(frames, None::<&[[f32; 2]]>); +``` + +The **conv** module provides pure functions and traits for more specific +conversions. A function is provided for converting between every possible pair +of sample types. Traits include: + +- `FromSample`, `ToSample`, `Duplex`, +- `FromSampleSlice`, `ToSampleSlice`, `DuplexSampleSlice`, +- `FromSampleSliceMut`, `ToSampleSliceMut`, `DuplexSampleSliceMut`, +- `FromFrameSlice`, `ToFrameSlice`, `DuplexFrameSlice`, +- `FromFrameSliceMut`, `ToFrameSliceMut`, `DuplexFrameSliceMut`, +- `DuplexSlice`, `DuplexSliceMut`, + +The **interpolate** module provides a **Converter** type, for converting and +interpolating the rate of **Signal**s. This can be useful for both sample rate +conversion and playback rate multiplication. **Converter**s can use a range of +interpolation methods, with Floor, Linear, and Sinc interpolation provided in +the library. (NB: Sinc interpolation currently requires heap allocation, as it +uses VecDeque.) + +The **ring_buffer** module provides generic **Fixed** and **Bounded** ring +buffer types, both of which may be used with owned, borrowed, stack and +allocated buffers. + +The **peak** module can be used for monitoring the peak of a signal. Provided +peak rectifiers include `full_wave`, `positive_half_wave` and +`negative_half_wave`. + +The **rms** module provides a flexible **Rms** type that can be used for RMS +(root mean square) detection. Any **Fixed** ring buffer can be used as the +window for the RMS detection. + +The **envelope** module provides a **Detector** type (also known as a +*Follower*) that allows for detecting the envelope of a signal. **Detector** is +generic over the type of **Detect**ion - **Rms** and **Peak** detection are +provided. For example: + +```rust +let signal = signal::rate(4.0).const_hz(1.0).sine(); +let attack = 1.0; +let release = 1.0; +let detector = envelope::Detector::peak(attack, release); +let mut envelope = signal.detect_envelope(detector); +assert_eq!( + envelope.take(4).collect::>(), + vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] +); +``` + + +Using in a `no_std` environment +------------------------------- + +This crate is largely dependency free, even of things outside `core`. The +`no_std` cargo feature will enable using `sample` in these environments. +Currently, only nightly is supported, because it explicitly depends on the +`alloc` and `collections` for datastructures and `core_intrinsics` for some of +the math. If this restriction is onerous for you, it can be lifted with minor +loss of functionality (the `Signal::bus` method), so open an issue! + + +Contributions +------------- + +If the **sample** crate is missing types, conversions or other fundamental +functionality that you wish it had, feel free to open an issue or pull request! +The more hands on deck, the merrier :) + + +License +------- + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +**Contributions** + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/src/conv.rs b/dasp_sample/src/conv.rs similarity index 75% rename from src/conv.rs rename to dasp_sample/src/conv.rs index ed45940c..500b0355 100644 --- a/src/conv.rs +++ b/dasp_sample/src/conv.rs @@ -13,15 +13,9 @@ //! Note that floating point conversions use the range -1.0 <= v < 1.0: //! `(1.0 as f64).to_sample::()` will overflow! -use {Box, Sample}; -#[cfg(feature = "frame")] -use Frame; -use core; -use types::{I24, U24, I48, U48}; - +use crate::types::{I24, U24, I48, U48}; macro_rules! conversion_fn { - ($Rep:ty, $s:ident to_i8 { $body:expr }) => { #[inline] pub fn to_i8($s: $Rep) -> i8 { @@ -291,7 +285,7 @@ conversions!(i32, i32 { } s to_u32 { if s < 0 { - ((s + 2_147_483_647 + 1) as u32) + (s + 2_147_483_647 + 1) as u32 } else { s as u32 + 2_147_483_648 } @@ -710,302 +704,3 @@ where T: FromSample + ToSample, { } - - -///// DSP Slice Conversion Traits - - -/// For converting from a slice of `Sample`s to a slice of `Frame`s. -pub trait FromSampleSlice<'a, S>: Sized -where - S: Sample, -{ - fn from_sample_slice(slice: &'a [S]) -> Option; -} - -/// For converting from a mutable slice of `Sample`s to a mutable slice of `Frame`s. -pub trait FromSampleSliceMut<'a, S>: Sized -where - S: Sample, -{ - fn from_sample_slice_mut(slice: &'a mut [S]) -> Option; -} - -/// For converting a boxed slice of `Sample`s to a boxed slice of `Frame`s. -pub trait FromBoxedSampleSlice: Sized -where - S: Sample, -{ - fn from_boxed_sample_slice(slice: Box<[S]>) -> Option; -} - -#[cfg(feature = "frame")] -/// For converting from a slice of `Frame`s to a slice of `Sample`s. -pub trait FromFrameSlice<'a, F> -where - F: Frame, -{ - fn from_frame_slice(slice: &'a [F]) -> Self; -} - -#[cfg(feature = "frame")] -/// For converting from a slice of `Frame`s to a slice of `Sample`s. -pub trait FromFrameSliceMut<'a, F> -where - F: Frame, -{ - fn from_frame_slice_mut(slice: &'a mut [F]) -> Self; -} - -#[cfg(feature = "frame")] -/// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. -pub trait FromBoxedFrameSlice -where - F: Frame, -{ - fn from_boxed_frame_slice(slice: Box<[F]>) -> Self; -} - -/// For converting from a slice of `Frame`s to a slice of `Sample`s. -pub trait ToSampleSlice<'a, S> -where - S: Sample, -{ - fn to_sample_slice(self) -> &'a [S]; -} - -/// For converting from a mutable slice of `Frame`s to a mutable slice of `Sample`s. -pub trait ToSampleSliceMut<'a, S> -where - S: Sample, -{ - fn to_sample_slice_mut(self) -> &'a mut [S]; -} - -/// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. -pub trait ToBoxedSampleSlice -where - S: Sample, -{ - fn to_boxed_sample_slice(self) -> Box<[S]>; -} - -#[cfg(feature = "frame")] -/// For converting from a slice of `Sample`s to a slice of `Frame`s. -pub trait ToFrameSlice<'a, F> -where - F: Frame, -{ - fn to_frame_slice(self) -> Option<&'a [F]>; -} - -#[cfg(feature = "frame")] -/// For converting from a mutable slice of `Sample`s to a mutable slice of `Frame`s. -pub trait ToFrameSliceMut<'a, F> -where - F: Frame, -{ - fn to_frame_slice_mut(self) -> Option<&'a mut [F]>; -} - -#[cfg(feature = "frame")] -/// For converting from a boxed slice of `Sample`s to a boxed slice of `Frame`s. -pub trait ToBoxedFrameSlice -where - F: Frame, -{ - fn to_boxed_frame_slice(self) -> Option>; -} - - -///// DSP Slice Conversion Trait Implementations - - -impl<'a, S> FromSampleSlice<'a, S> for &'a [S] -where - S: Sample, -{ - #[inline] - fn from_sample_slice(slice: &'a [S]) -> Option { - Some(slice) - } -} - -impl<'a, S> FromSampleSliceMut<'a, S> for &'a mut [S] -where - S: Sample, -{ - #[inline] - fn from_sample_slice_mut(slice: &'a mut [S]) -> Option { - Some(slice) - } -} - -impl FromBoxedSampleSlice for Box<[S]> -where - S: Sample, -{ - #[inline] - fn from_boxed_sample_slice(slice: Box<[S]>) -> Option { - Some(slice) - } -} - -impl<'a, S> ToSampleSlice<'a, S> for &'a [S] -where - S: Sample, -{ - #[inline] - fn to_sample_slice(self) -> &'a [S] { - self - } -} - -impl<'a, S> ToSampleSliceMut<'a, S> for &'a mut [S] -where - S: Sample, -{ - #[inline] - fn to_sample_slice_mut(self) -> &'a mut [S] { - self - } -} - -impl ToBoxedSampleSlice for Box<[S]> -where - S: Sample, -{ - #[inline] - fn to_boxed_sample_slice(self) -> Box<[S]> { - self - } -} - -#[cfg(feature = "frame")] -impl<'a, F> ToFrameSlice<'a, F> for &'a [F] -where - F: Frame, -{ - #[inline] - fn to_frame_slice(self) -> Option<&'a [F]> { - Some(self) - } -} - -#[cfg(feature = "frame")] -impl<'a, F> ToFrameSliceMut<'a, F> for &'a mut [F] -where - F: Frame, -{ - #[inline] - fn to_frame_slice_mut(self) -> Option<&'a mut [F]> { - Some(self) - } -} - -#[cfg(feature = "frame")] -impl ToBoxedFrameSlice for Box<[F]> -where - F: Frame, -{ - #[inline] - fn to_boxed_frame_slice(self) -> Option> { - Some(self) - } -} - -///// Bi-Directional DSP Slice Conversion Traits - - -pub trait DuplexSampleSlice<'a, S> - : FromSampleSlice<'a, S> + ToSampleSlice<'a, S> -where - S: Sample -{ -} - -#[cfg(feature = "frame")] -pub trait DuplexFrameSlice<'a, F>: FromFrameSlice<'a, F> + ToFrameSlice<'a, F> -where - F: Frame -{ -} - -#[cfg(feature = "frame")] -pub trait DuplexSlice<'a, S, F> - : DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F> -where - S: Sample, - F: Frame -{ -} - -pub trait DuplexSampleSliceMut<'a, S> - : FromSampleSliceMut<'a, S> + ToSampleSliceMut<'a, S> -where - S: Sample -{ -} - -#[cfg(feature = "frame")] -pub trait DuplexFrameSliceMut<'a, F> - : FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F> -where - F: Frame -{ -} - -#[cfg(feature = "frame")] -pub trait DuplexSliceMut<'a, S, F> - : DuplexSampleSliceMut<'a, S> + DuplexFrameSliceMut<'a, F> -where - S: Sample, - F: Frame -{ -} - -pub trait DuplexBoxedSampleSlice - : FromBoxedSampleSlice + ToBoxedSampleSlice -where - S: Sample -{ -} - -#[cfg(feature = "frame")] -pub trait DuplexBoxedFrameSlice - : FromBoxedFrameSlice + ToBoxedFrameSlice -where - F: Frame -{ -} - -#[cfg(feature = "frame")] -pub trait DuplexBoxedSlice - : DuplexBoxedSampleSlice + DuplexBoxedFrameSlice -where - S: Sample, - F: Frame -{ -} - -///// Bi-Directional DSP Slice Conversion Trait Implementations - - -impl<'a, S, T> DuplexSampleSlice<'a, S> for T -where - S: Sample, - T: FromSampleSlice<'a, S> + ToSampleSlice<'a, S>, -{ -} - -impl<'a, S, T> DuplexSampleSliceMut<'a, S> for T -where - S: Sample, - T: FromSampleSliceMut<'a, S> + ToSampleSliceMut<'a, S> {} - -impl DuplexBoxedSampleSlice for T -where - S: Sample, - T: FromBoxedSampleSlice + ToBoxedSampleSlice, -{ -} diff --git a/src/lib.rs b/dasp_sample/src/lib.rs similarity index 70% rename from src/lib.rs rename to dasp_sample/src/lib.rs index 511e7fc0..4e5358d8 100644 --- a/src/lib.rs +++ b/dasp_sample/src/lib.rs @@ -9,169 +9,19 @@ //! - See the [**interpolate** module](./interpolate/index.html) for sample rate conversion and scaling. //! - See the [**ring_buffer** module](./ring_buffer/index.html) for fast FIFO queue options. -#![recursion_limit="512"] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(not(feature = "std"), feature(alloc, core_intrinsics))] - -#[cfg(feature = "std")] -extern crate core; +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] #[cfg(not(feature = "std"))] extern crate alloc; -#[cfg(not(feature = "std"))] -type BTreeMap = alloc::collections::btree_map::BTreeMap; -#[cfg(feature = "std")] -type BTreeMap = std::collections::BTreeMap; - -#[cfg(not(feature = "std"))] -type Vec = alloc::vec::Vec; -#[cfg(feature = "std")] -#[allow(dead_code)] -type Vec = std::vec::Vec; - -#[cfg(not(feature = "std"))] -type VecDeque = alloc::collections::vec_deque::VecDeque; -#[cfg(feature = "std")] -type VecDeque = std::collections::vec_deque::VecDeque; - -#[cfg(not(feature = "std"))] -pub type Box = alloc::boxed::Box; -#[cfg(feature = "std")] -pub type Box = std::boxed::Box; - -#[cfg(not(feature = "std"))] -type Rc = alloc::rc::Rc; -#[cfg(feature = "std")] -type Rc = std::rc::Rc; - -pub use conv::{FromSample, ToSample, Duplex, FromSampleSlice, ToSampleSlice, DuplexSampleSlice, - FromSampleSliceMut, ToSampleSliceMut, DuplexSampleSliceMut, FromBoxedSampleSlice, - ToBoxedSampleSlice, DuplexBoxedSampleSlice}; - -#[cfg(feature = "frame")] -pub use conv::{FromFrameSlice, ToFrameSlice, - DuplexFrameSlice, FromFrameSliceMut, ToFrameSliceMut, DuplexFrameSliceMut, - FromBoxedFrameSlice, ToBoxedFrameSlice, DuplexBoxedFrameSlice, DuplexSlice, - DuplexSliceMut, DuplexBoxedSlice}; - -#[cfg(feature = "frame")] -pub use frame::Frame; - -#[cfg(feature = "signal")] -pub use signal::Signal; - -pub use types::{I24, U24, I48, U48}; +pub use conv::{Duplex, FromSample, ToSample}; +pub use types::{I24, I48, U24, U48}; pub mod conv; - -#[cfg(feature = "slice")] -pub mod slice; - -#[cfg(feature = "envelope")] -pub mod envelope; - -#[cfg(feature = "frame")] -pub mod frame; - -#[cfg(feature = "peak")] -pub mod peak; - -#[cfg(feature = "ring_buffer")] -pub mod ring_buffer; - -#[cfg(feature = "rms")] -pub mod rms; - -#[cfg(feature = "envelope")] -pub mod signal; - +mod ops; pub mod types; -#[cfg(feature = "window")] -pub mod window; - -#[cfg(feature = "interpolate")] -pub mod interpolate; - -mod ops { - pub mod f32 { - #[allow(unused_imports)] - use core; - - #[cfg(not(feature = "std"))] - pub fn sqrt(x: f32) -> f32 { - unsafe { core::intrinsics::sqrtf32(x) } - } - #[cfg(feature = "std")] - pub fn sqrt(x: f32) -> f32 { - x.sqrt() - } - - #[cfg(feature = "std")] - pub fn powf32(a: f32, b: f32) -> f32 { - a.powf(b) - } - #[cfg(not(feature = "std"))] - pub fn powf32(a: f32, b: f32) -> f32 { - unsafe { core::intrinsics::powf32(a, b) } - } - } - - pub mod f64 { - #[allow(unused_imports)] - use core; - - #[cfg(not(feature = "std"))] - pub fn floor(x: f64) -> f64 { - unsafe { core::intrinsics::floorf64(x) } - } - #[cfg(feature = "std")] - pub fn floor(x: f64) -> f64 { - x.floor() - } - - #[cfg(not(feature = "std"))] - #[allow(dead_code)] - pub fn ceil(x: f64) -> f64 { - unsafe { core::intrinsics::ceilf64(x) } - } - #[cfg(feature = "std")] - #[allow(dead_code)] - pub fn ceil(x: f64) -> f64 { - x.ceil() - } - - #[cfg(not(feature = "std"))] - pub fn sin(x: f64) -> f64 { - unsafe { core::intrinsics::sinf64(x) } - } - #[cfg(feature = "std")] - pub fn sin(x: f64) -> f64 { - x.sin() - } - - #[cfg(not(feature = "std"))] - pub fn cos(x: f64) -> f64 { - unsafe { core::intrinsics::cosf64(x) } - } - #[cfg(feature = "std")] - pub fn cos(x: f64) -> f64 { - x.cos() - } - - #[cfg(not(feature = "std"))] - pub fn sqrt(x: f64) -> f64 { - unsafe { core::intrinsics::sqrtf64(x) } - } - #[cfg(feature = "std")] - pub fn sqrt(x: f64) -> f64 { - x.sqrt() - } - } -} - - /// A trait for working generically across different **Sample** format types. /// /// Provides methods for converting to and from any type that implements the @@ -181,9 +31,7 @@ mod ops { /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{I24, Sample}; +/// use dasp_sample::{I24, Sample}; /// /// fn main() { /// assert_eq!((-1.0).to_sample::(), 0); @@ -229,9 +77,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Sample; + /// use dasp_sample::Sample; /// /// fn main() { /// assert_eq!(0.0, f32::equilibrium()); @@ -254,9 +100,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{Sample, U48}; + /// use dasp_sample::{Sample, U48}; /// /// fn main() { /// assert_eq!(1.0, f32::identity()); @@ -277,9 +121,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Sample; + /// use dasp_sample::Sample; /// /// fn main() { /// assert_eq!(0.0.to_sample::(), 0); @@ -302,9 +144,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{Sample, I24}; + /// use dasp_sample::{Sample, I24}; /// /// fn main() { /// assert_eq!(f32::from_sample(128_u8), 0.0); @@ -329,9 +169,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Sample; + /// use dasp_sample::Sample; /// /// fn main() { /// assert_eq!(128_u8.to_signed_sample(), 0i8); @@ -349,9 +187,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Sample; + /// use dasp_sample::Sample; /// /// fn main() { /// assert_eq!(128_u8.to_float_sample(), 0.0); @@ -370,9 +206,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Sample; + /// use dasp_sample::Sample; /// /// fn main() { /// assert_eq!(0.25.add_amp(0.5), 0.75); @@ -399,9 +233,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::Sample; + /// use dasp_sample::Sample; /// /// fn main() { /// assert_eq!(64_i8.mul_amp(0.5), 32); @@ -437,7 +269,7 @@ macro_rules! impl_sample { } // Expands to `Sample` implementations for all of the following types. -impl_sample!{ +impl_sample! { i8: Signed: i8, Float: f32, equilibrium: 0, i16: Signed: i16, Float: f32, equilibrium: 0, I24: Signed: I24, Float: f32, equilibrium: types::i24::EQUILIBRIUM, @@ -454,16 +286,16 @@ impl_sample!{ f64: Signed: f64, Float: f64, equilibrium: 0.0 } - /// Integral and floating-point **Sample** format types whose equilibrium is at 0. /// /// **Sample**s often need to be converted to some mutual **SignedSample** type for signal /// addition. -pub trait SignedSample - : Sample +pub trait SignedSample: + Sample + core::ops::Add + core::ops::Sub - + core::ops::Neg { + + core::ops::Neg +{ } macro_rules! impl_signed_sample { ($($T:ty)*) => { $( impl SignedSample for $T {} )* } } impl_signed_sample!(i8 i16 I24 i32 I48 i64 f32 f64); @@ -472,13 +304,14 @@ impl_signed_sample!(i8 i16 I24 i32 I48 i64 f32 f64); /// /// **Sample**s often need to be converted to some mutual **FloatSample** type for signal scaling /// and modulation. -pub trait FloatSample - : Sample +pub trait FloatSample: + Sample + SignedSample + core::ops::Mul + core::ops::Div + Duplex - + Duplex { + + Duplex +{ /// Represents the multiplicative identity of the floating point signal. fn identity() -> Self; /// Calculate the square root of `Self`. diff --git a/dasp_sample/src/ops.rs b/dasp_sample/src/ops.rs new file mode 100644 index 00000000..dbafe7ac --- /dev/null +++ b/dasp_sample/src/ops.rs @@ -0,0 +1,27 @@ +pub mod f32 { + #[allow(unused_imports)] + use core; + + #[cfg(not(feature = "std"))] + pub fn sqrt(x: f32) -> f32 { + unsafe { core::intrinsics::sqrtf32(x) } + } + #[cfg(feature = "std")] + pub fn sqrt(x: f32) -> f32 { + x.sqrt() + } +} + +pub mod f64 { + #[allow(unused_imports)] + use core; + + #[cfg(not(feature = "std"))] + pub fn sqrt(x: f64) -> f64 { + unsafe { core::intrinsics::sqrtf64(x) } + } + #[cfg(feature = "std")] + pub fn sqrt(x: f64) -> f64 { + x.sqrt() + } +} diff --git a/src/types.rs b/dasp_sample/src/types.rs similarity index 99% rename from src/types.rs rename to dasp_sample/src/types.rs index 79369ac8..6c48b821 100644 --- a/src/types.rs +++ b/dasp_sample/src/types.rs @@ -1,15 +1,14 @@ //! A collection of custom, non-std **Sample** types. pub use self::i11::I11; -pub use self::u11::U11; pub use self::i20::I20; -pub use self::u20::U20; pub use self::i24::I24; -pub use self::u24::U24; pub use self::i48::I48; +pub use self::u11::U11; +pub use self::u20::U20; +pub use self::u24::U24; pub use self::u48::U48; - macro_rules! impl_from { ($T:ident: $Rep:ident from {$U:ident : $URep:ty}) => { impl From<$U> for $T { @@ -241,23 +240,12 @@ pub mod i11 { impl_neg!(I11); } -pub mod u11 { - new_sample_type!(U11: i16, eq: 1024, min: 0, max: 2047, total: 2048, - from: u8); - impl_neg!(U11); -} - pub mod i20 { use super::{I11, U11}; new_sample_type!(I20: i32, eq: 0, min: -524_288, max: 524_287, total: 1_048_576, from: i8, {I11:i16}, i16, u8, {U11:i16}, u16); } -pub mod u20 { - new_sample_type!(U20: i32, eq: 524_288, min: 0, max: 1_048_575, total: 1_048_576, - from: u8, u16); -} - pub mod i24 { use super::{I20, U20}; new_sample_type!(I24: i32, eq: 0, min: -8_388_608, max: 8_388_607, total: 16_777_216, @@ -265,12 +253,6 @@ pub mod i24 { impl_neg!(I24); } -pub mod u24 { - use super::U20; - new_sample_type!(U24: i32, eq: 8_388_608, min: 0, max: 16_777_215, total: 16_777_216, - from: u8, u16, {U20:i32}); -} - pub mod i48 { use super::{I20, I24, U20, U24}; new_sample_type!(I48: i64, eq: 0, min: -140_737_488_355_328, max: 140_737_488_355_327, total: 281_474_976_710_656, @@ -278,6 +260,23 @@ pub mod i48 { impl_neg!(I48); } +pub mod u11 { + new_sample_type!(U11: i16, eq: 1024, min: 0, max: 2047, total: 2048, + from: u8); + impl_neg!(U11); +} + +pub mod u20 { + new_sample_type!(U20: i32, eq: 524_288, min: 0, max: 1_048_575, total: 1_048_576, + from: u8, u16); +} + +pub mod u24 { + use super::U20; + new_sample_type!(U24: i32, eq: 8_388_608, min: 0, max: 16_777_215, total: 16_777_216, + from: u8, u16, {U20:i32}); +} + pub mod u48 { use super::{U20, U24}; new_sample_type!(U48: i64, eq: 140_737_488_355_328, min: 0, max: 281_474_976_710_655, total: 281_474_976_710_656, diff --git a/tests/conv.rs b/dasp_sample/tests/conv.rs similarity index 98% rename from tests/conv.rs rename to dasp_sample/tests/conv.rs index 25490a09..3fb0bbc9 100644 --- a/tests/conv.rs +++ b/dasp_sample/tests/conv.rs @@ -4,8 +4,6 @@ //! We assert that each sample type's minimum, maximum and centre are correctly converted to the //! min, max and centre of every other available sample type. -extern crate sample; - /// Expands to an `assert_eq` for each pre-conversion and post-conversion pair. /// /// Literals that must be wrapped by a custom sample type are wrapped using $T/$U::new_unchecked. @@ -273,15 +271,15 @@ macro_rules! test_fns { macro_rules! tests { ($T:ident { $($rest:tt)* }) => { pub mod $T { - use sample::conv::$T::*; - use sample::types::{I24, U24, I48, U48}; + use dasp_sample::conv::$T::*; + use dasp_sample::types::{I24, U24, I48, U48}; test_fns!($($rest)*); } }; ($T:ident: $mod_name:ident { $($rest:tt)* }) => { pub mod $mod_name { - use sample::conv::$mod_name::*; - use sample::types::{I24, U24, I48, U48}; + use dasp_sample::conv::$mod_name::*; + use dasp_sample::types::{I24, U24, I48, U48}; test_fns!($T: $($rest)*); } }; diff --git a/tests/types.rs b/dasp_sample/tests/types.rs similarity index 86% rename from tests/types.rs rename to dasp_sample/tests/types.rs index 19f0d393..9c199095 100644 --- a/tests/types.rs +++ b/dasp_sample/tests/types.rs @@ -1,5 +1,3 @@ -extern crate sample; - /// Expands to a unique module with a variety of tests for the given sample newtype. /// /// Tests include basic operations and over/underflow checks. @@ -8,7 +6,7 @@ macro_rules! test_type { mod $mod_name { #[test] fn ops() { - use sample::types::$mod_name::$T; + use dasp_sample::types::$mod_name::$T; assert_eq!($T::new(8).unwrap() + $T::new(12).unwrap(), $T::new(20).unwrap()); assert_eq!($T::new(12).unwrap() - $T::new(4).unwrap(), $T::new(8).unwrap()); assert_eq!($T::new(2).unwrap() * $T::new(2).unwrap(), $T::new(4).unwrap()); @@ -22,7 +20,7 @@ macro_rules! test_type { #[test] #[should_panic] fn add_panic_debug() { - use sample::types::$mod_name::{self, $T}; + use dasp_sample::types::$mod_name::{self, $T}; let _ = $mod_name::MAX + $T::new(1).unwrap(); } @@ -30,7 +28,7 @@ macro_rules! test_type { #[test] #[should_panic] fn sub_panic_debug() { - use sample::types::$mod_name::{self, $T}; + use dasp_sample::types::$mod_name::{self, $T}; let _ = $mod_name::MIN - $T::new(1).unwrap(); } @@ -38,14 +36,14 @@ macro_rules! test_type { #[test] #[should_panic] fn mul_panic_debug() { - use sample::types::$mod_name::{self, $T}; + use dasp_sample::types::$mod_name::{self, $T}; let _ = $mod_name::MAX * $T::new(2).unwrap(); } #[cfg(not(debug_assertions))] #[test] fn release_wrapping() { - use sample::types::$mod_name::{self, $T}; + use dasp_sample::types::$mod_name::{self, $T}; assert_eq!($mod_name::MIN - $T::new(1).unwrap(), $mod_name::MAX); assert_eq!($mod_name::MAX + $T::new(1).unwrap(), $mod_name::MIN); } diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml new file mode 100644 index 00000000..352acec2 --- /dev/null +++ b/dasp_signal/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "dasp_signal" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_envelope = { path = "../dasp_envelope", default-features = false, optional = true } +dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_interpolate = { path = "../dasp_interpolate", default-features = false } +dasp_peak = { path = "../dasp_peak", default-features = false } +dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } +dasp_rms = { path = "../dasp_rms", default-features = false, optional = true } +dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_window = { path = "../dasp_window", default-features = false, optional = true } + +[dev-dependencies] +dasp_envelope = { path = "../dasp_envelope", default-features = false, features = ["peak"] } +dasp_interpolate = { path = "../dasp_interpolate", default-features = false, features = ["floor", "linear", "sinc"] } +dasp_window = { path = "../dasp_window", default-features = false, features = ["hanning"] } + +[features] +default = ["std"] +boxed = [] +bus = [] +envelope = ["dasp_envelope"] +rms = ["dasp_rms"] +std = [ + "dasp_envelope/std", + "dasp_frame/std", + "dasp_interpolate/std", + "dasp_peak/std", + "dasp_ring_buffer/std", + "dasp_rms/std", + "dasp_sample/std", + "dasp_window/std", +] +window = ["dasp_window"] +window-hanning = ["dasp_window/hanning"] +window-rectangle = ["dasp_window/rectangle"] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_signal/src/boxed.rs b/dasp_signal/src/boxed.rs new file mode 100644 index 00000000..2cbfa489 --- /dev/null +++ b/dasp_signal/src/boxed.rs @@ -0,0 +1,20 @@ +#[cfg(not(feature = "std"))] +type Box = alloc::boxed::Box; +#[cfg(feature = "std")] +type Box = std::boxed::Box; + +impl Signal for Box +where + S: Signal + ?Sized, +{ + type Frame = S::Frame; + #[inline] + fn next(&mut self) -> Self::Frame { + (**self).next() + } + + #[inline] + fn is_exhausted(&self) -> bool { + (**self).is_exhausted() + } +} diff --git a/dasp_signal/src/bus.rs b/dasp_signal/src/bus.rs new file mode 100644 index 00000000..1549c7ee --- /dev/null +++ b/dasp_signal/src/bus.rs @@ -0,0 +1,254 @@ +use crate::{Rc, Signal}; + +#[cfg(not(feature = "std"))] +type BTreeMap = alloc::collections::btree_map::BTreeMap; +#[cfg(feature = "std")] +type BTreeMap = std::collections::BTreeMap; + +#[cfg(not(feature = "std"))] +type VecDeque = alloc::collections::vec_deque::VecDeque; +#[cfg(feature = "std")] +type VecDeque = std::collections::vec_deque::VecDeque; + +pub trait SignalBus: Signal { + /// Moves the `Signal` into a `Bus` from which its output may be divided into multiple other + /// `Signal`s in the form of `Output`s. + /// + /// This method allows to create more complex directed acyclic graph structures that + /// incorporate concepts like sends, side-chaining, etc, rather than being restricted to tree + /// structures where signals can only ever be joined but never divided. + /// + /// Note: When using multiple `Output`s in this fashion, you will need to be sure to pull the + /// frames from each `Output` in sync (whether per frame or per buffer). This is because when + /// output A requests `Frame`s before output B, those frames must remain available for output + /// B and in turn must be stored in an intermediary ring buffer. + /// + /// # Example + /// + /// ```rust + /// use dasp_signal::{self as signal, Signal}; + /// use dasp_signal::bus::SignalBus; + /// + /// fn main() { + /// let frames = [[0.1], [0.2], [0.3], [0.4], [0.5], [0.6]]; + /// let signal = signal::from_iter(frames.iter().cloned()); + /// let bus = signal.bus(); + /// let mut a = bus.send(); + /// let mut b = bus.send(); + /// assert_eq!(a.by_ref().take(3).collect::>(), vec![[0.1], [0.2], [0.3]]); + /// assert_eq!(b.by_ref().take(3).collect::>(), vec![[0.1], [0.2], [0.3]]); + /// + /// let c = bus.send(); + /// assert_eq!(c.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); + /// assert_eq!(b.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); + /// assert_eq!(a.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); + /// } + /// ``` + fn bus(self) -> Bus + where + Self: Sized, + { + Bus::new(self, BTreeMap::new()) + } +} + +/// The data shared between each `Output`. +struct SharedNode +where + S: Signal, +{ + signal: S, + // The buffer of frames that have not yet been consumed by all outputs. + buffer: VecDeque, + // The number of frames in `buffer` that have already been read for each output. + frames_read: BTreeMap, + // The next output key. + next_key: usize, +} + +/// A type which allows for `send`ing a single `Signal` to multiple outputs. +/// +/// This type manages +pub struct Bus +where + S: Signal, +{ + node: Rc>>, +} + +/// An output node to which some signal `S` is `Output`ing its frames. +/// +/// It may be more accurate to say that the `Output` "pull"s frames from the signal. +pub struct Output +where + S: Signal, +{ + key: usize, + node: Rc>>, +} + +impl Bus +where + S: Signal, +{ + fn new(signal: S, frames_read: BTreeMap) -> Self { + Bus { + node: Rc::new(core::cell::RefCell::new(SharedNode { + signal: signal, + buffer: VecDeque::new(), + frames_read: frames_read, + next_key: 0, + })), + } + } + + /// Produce a new Output node to which the signal `S` will output its frames. + #[inline] + pub fn send(&self) -> Output { + let mut node = self.node.borrow_mut(); + + // Get the key and increment for the next output. + let key = node.next_key; + node.next_key = node.next_key.wrapping_add(1); + + // Insert the number of frames read by the new output. + let num_frames = node.buffer.len(); + node.frames_read.insert(key, num_frames); + + Output { + key: key, + node: self.node.clone(), + } + } +} + +impl SharedNode +where + S: Signal, +{ + // Requests the next frame for the `Output` at the given key. + // + // If there are no frames pending for the output, a new frame will be requested from the + // signal and appended to the ring buffer to be received by the other outputs. + fn next_frame(&mut self, key: usize) -> S::Frame { + let num_frames = self.buffer.len(); + let frames_read = self.frames_read.remove(&key).expect( + "no frames_read for Output", + ); + + let frame = if frames_read < num_frames { + self.buffer[frames_read] + } else { + let frame = self.signal.next(); + self.buffer.push_back(frame); + frame + }; + + // If the number of frames read by this output is the lowest, then we can pop the frame + // from the front. + let least_frames_read = !self.frames_read.values().any(|&other_frames_read| { + other_frames_read <= frames_read + }); + + // If this output had read the least number of frames, pop the front frame and decrement + // the frames read counters for each of the other outputs. + let new_frames_read = if least_frames_read { + self.buffer.pop_front(); + for other_frames_read in self.frames_read.values_mut() { + *other_frames_read -= 1; + } + frames_read + } else { + frames_read + 1 + }; + + self.frames_read.insert(key, new_frames_read); + + frame + } + + #[inline] + fn pending_frames(&self, key: usize) -> usize { + self.buffer.len() - self.frames_read[&key] + } + + // Drop the given output from the `Bus`. + // + // Called by the `Output::drop` implementation. + fn drop_output(&mut self, key: usize) { + self.frames_read.remove(&key); + let least_frames_read = self.frames_read.values().fold(self.buffer.len(), |a, &b| { + core::cmp::min(a, b) + }); + if least_frames_read > 0 { + for frames_read in self.frames_read.values_mut() { + *frames_read -= least_frames_read; + } + for _ in 0..least_frames_read { + self.buffer.pop_front(); + } + } + } +} + +impl Output +where + S: Signal, +{ + /// The number of frames that have been requested from the `Signal` `S` by some other `Output` + /// that have not yet been requested by this `Output`. + /// + /// This is useful when using an `Output` to "monitor" some signal, allowing the user to drain + /// only frames that have already been requested by some other `Output`. + /// + /// # Example + /// + /// ``` + /// use dasp_signal::{self as signal, Signal}; + /// use dasp_signal::bus::SignalBus; + /// + /// fn main() { + /// let frames = [[0.1], [0.2], [0.3]]; + /// let bus = signal::from_iter(frames.iter().cloned()).bus(); + /// let signal = bus.send(); + /// let mut monitor = bus.send(); + /// assert_eq!(signal.take(3).collect::>(), vec![[0.1], [0.2], [0.3]]); + /// assert_eq!(monitor.pending_frames(), 3); + /// assert_eq!(monitor.next(), [0.1]); + /// assert_eq!(monitor.pending_frames(), 2); + /// } + /// ``` + #[inline] + pub fn pending_frames(&self) -> usize { + self.node.borrow().pending_frames(self.key) + } +} + +impl SignalBus for T where T: Signal {} + +impl Signal for Output +where + S: Signal, +{ + type Frame = S::Frame; + + #[inline] + fn next(&mut self) -> Self::Frame { + self.node.borrow_mut().next_frame(self.key) + } + + #[inline] + fn is_exhausted(&self) -> bool { + let node = self.node.borrow(); + node.pending_frames(self.key) == 0 && node.signal.is_exhausted() + } +} + +impl Drop for Output +where + S: Signal, +{ + fn drop(&mut self) { + self.node.borrow_mut().drop_output(self.key) + } +} diff --git a/dasp_signal/src/envelope.rs b/dasp_signal/src/envelope.rs new file mode 100644 index 00000000..2342e7c3 --- /dev/null +++ b/dasp_signal/src/envelope.rs @@ -0,0 +1,89 @@ +use crate::Signal; +use dasp_envelope as envelope; + +pub trait SignalEnvelope: Signal { + /// An adaptor that detects and yields the envelope of the signal. + /// + /// # Example + /// + /// ``` + /// use dasp_envelope as envelope; + /// use dasp_signal::{self as signal, Signal}; + /// use dasp_signal::envelope::SignalEnvelope; + /// + /// fn main() { + /// let signal = signal::rate(4.0).const_hz(1.0).sine(); + /// let attack = 1.0; + /// let release = 1.0; + /// let detector = envelope::Detector::peak(attack, release); + /// let mut envelope = signal.detect_envelope(detector); + /// assert_eq!( + /// envelope.take(4).collect::>(), + /// vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] + /// ); + /// } + /// ``` + fn detect_envelope( + self, + detector: envelope::Detector, + ) -> DetectEnvelope + where + Self: Sized, + D: envelope::Detect, + { + DetectEnvelope { + signal: self, + detector: detector, + } + } +} + +/// An adaptor that detects and yields the envelope of the signal. +#[derive(Clone)] +pub struct DetectEnvelope +where + S: Signal, + D: envelope::Detect, +{ + signal: S, + detector: envelope::Detector, +} + +impl DetectEnvelope +where + S: Signal, + D: envelope::Detect, +{ + /// Set the **Detector**'s attack time as a number of frames. + pub fn set_attack_frames(&mut self, frames: f32) { + self.detector.set_attack_frames(frames); + } + + /// Set the **Detector**'s release time as a number of frames. + pub fn set_release_frames(&mut self, frames: f32) { + self.detector.set_release_frames(frames); + } + + /// Consumes `Self` and returns the inner signal `S` and `Detector`. + pub fn into_parts(self) -> (S, envelope::Detector) { + let DetectEnvelope { signal, detector } = self; + (signal, detector) + } +} + +impl Signal for DetectEnvelope +where + S: Signal, + D: envelope::Detect, +{ + type Frame = D::Output; + fn next(&mut self) -> Self::Frame { + self.detector.next(self.signal.next()) + } + + fn is_exhausted(&self) -> bool { + self.signal.is_exhausted() + } +} + +impl SignalEnvelope for T where T: Signal {} diff --git a/dasp_signal/src/interpolate.rs b/dasp_signal/src/interpolate.rs new file mode 100644 index 00000000..12c9dc0c --- /dev/null +++ b/dasp_signal/src/interpolate.rs @@ -0,0 +1,143 @@ +use crate::Signal; +use dasp_interpolate::Interpolator; + +/// An iterator that converts the rate at which frames are yielded from some given frame +/// Interpolator into a new type. +/// +/// Other names for `sample::interpolate::Converter` might include: +/// +/// - Sample rate converter +/// - {Up/Down}sampler +/// - Sample interpolater +/// - Sample decimator +/// +#[derive(Clone)] +pub struct Converter +where + S: Signal, + I: Interpolator, +{ + source: S, + interpolator: I, + interpolation_value: f64, + source_to_target_ratio: f64, +} + +impl Converter +where + S: Signal, + I: Interpolator, +{ + /// Construct a new `Converter` from the source frames and the source and target sample rates + /// (in Hz). + #[inline] + pub fn from_hz_to_hz(source: S, interpolator: I, source_hz: f64, target_hz: f64) -> Self { + Self::scale_playback_hz(source, interpolator, source_hz / target_hz) + } + + /// Construct a new `Converter` from the source frames and the amount by which the current + /// ***playback*** **rate** (not sample rate) should be multiplied to reach the new playback + /// rate. + /// + /// For example, if our `source_frames` is a sine wave oscillating at a frequency of 2hz and + /// we wanted to convert it to a frequency of 3hz, the given `scale` should be `1.5`. + #[inline] + pub fn scale_playback_hz(source: S, interpolator: I, scale: f64) -> Self { + assert!( + scale > 0.0, + "We can't yield any frames at 0 times a second!" + ); + Converter { + source: source, + interpolator: interpolator, + interpolation_value: 0.0, + source_to_target_ratio: scale, + } + } + + /// Construct a new `Converter` from the source frames and the amount by which the current + /// ***sample*** **rate** (not playback rate) should be multiplied to reach the new sample + /// rate. + /// + /// If our `source_frames` are being sampled at a rate of 44_100hz and we want to + /// convert to a sample rate of 96_000hz, the given `scale` should be `96_000.0 / 44_100.0`. + /// + /// This is the same as calling `Converter::scale_playback_hz(source_frames, 1.0 / scale)`. + #[inline] + pub fn scale_sample_hz(source: S, interpolator: I, scale: f64) -> Self { + Self::scale_playback_hz(source, interpolator, 1.0 / scale) + } + + /// Update the `source_to_target_ratio` internally given the source and target hz. + /// + /// This method might be useful for changing the sample rate during playback. + #[inline] + pub fn set_hz_to_hz(&mut self, source_hz: f64, target_hz: f64) { + self.set_playback_hz_scale(source_hz / target_hz) + } + + /// Update the `source_to_target_ratio` internally given a new **playback rate** multiplier. + /// + /// This method is useful for dynamically changing rates. + #[inline] + pub fn set_playback_hz_scale(&mut self, scale: f64) { + self.source_to_target_ratio = scale; + } + + /// Update the `source_to_target_ratio` internally given a new **sample rate** multiplier. + /// + /// This method is useful for dynamically changing rates. + #[inline] + pub fn set_sample_hz_scale(&mut self, scale: f64) { + self.set_playback_hz_scale(1.0 / scale); + } + + /// Borrow the `source_frames` Interpolator from the `Converter`. + #[inline] + pub fn source(&self) -> &S { + &self.source + } + + /// Mutably borrow the `source_frames` Iterator from the `Converter`. + #[inline] + pub fn source_mut(&mut self) -> &mut S { + &mut self.source + } + + /// Drop `self` and return the internal `source_frames` Iterator. + #[inline] + pub fn into_source(self) -> S { + self.source + } +} + +impl Signal for Converter +where + S: Signal, + I: Interpolator, +{ + type Frame = S::Frame; + + fn next(&mut self) -> Self::Frame { + let Converter { + ref mut source, + ref mut interpolator, + ref mut interpolation_value, + source_to_target_ratio, + } = *self; + + // Advance frames + while *interpolation_value >= 1.0 { + interpolator.next_source_frame(source.next()); + *interpolation_value -= 1.0; + } + + let out = interpolator.interpolate(*interpolation_value); + *interpolation_value += source_to_target_ratio; + out + } + + fn is_exhausted(&self) -> bool { + self.source.is_exhausted() && self.interpolation_value >= 1.0 + } +} diff --git a/src/signal.rs b/dasp_signal/src/lib.rs similarity index 73% rename from src/signal.rs rename to dasp_signal/src/lib.rs index a40f1bfc..f2f685c9 100644 --- a/src/signal.rs +++ b/dasp_signal/src/lib.rs @@ -20,14 +20,38 @@ //! Working with **Signal**s allows for easy, readable creation of rich and complex DSP graphs with //! a simple and familiar API. -use {BTreeMap, Duplex, Frame, Sample, Rc, VecDeque, Box}; +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] + +#[cfg(not(feature = "std"))] +extern crate alloc; + use core; use core::cell::RefCell; -use envelope; -use interpolate::{Converter, Interpolator}; -use ring_buffer; -use rms; - +use dasp_frame::Frame; +use dasp_interpolate::Interpolator; +use dasp_ring_buffer as ring_buffer; +use dasp_sample::{Duplex, Sample}; +use interpolate::Converter; + +pub mod interpolate; +mod ops; + +#[cfg(features = "boxed")] +pub mod boxed; +#[cfg(feature = "bus")] +pub mod bus; +#[cfg(feature = "envelope")] +pub mod envelope; +#[cfg(feature = "rms")] +pub mod rms; +#[cfg(feature = "window")] +pub mod window; + +#[cfg(not(feature = "std"))] +type Rc = alloc::rc::Rc; +#[cfg(feature = "std")] +type Rc = std::rc::Rc; /// Types that yield `Frame`s as a multi-channel PCM signal. /// @@ -42,9 +66,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.2], [-0.6], [0.4]]; @@ -75,9 +97,7 @@ pub trait Signal { /// when either `A` or `B` begins returning `None`. /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// // Infinite signals always return `false`. @@ -112,9 +132,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = signal::gen(|| [0.5]); @@ -128,9 +146,9 @@ pub trait Signal { /// This can also be useful for monitoring the peak values of a signal. /// /// ``` - /// extern crate sample; - /// - /// use sample::{peak, signal, Frame, Signal}; + /// use dasp_frame::Frame; + /// use dasp_peak as peak; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let sine_wave = signal::rate(4.0).const_hz(1.0).sine(); @@ -161,9 +179,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = signal::gen(|| [0.5]); @@ -195,9 +211,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let a = [[0.2], [-0.6], [0.4]]; @@ -227,9 +241,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let a = [[0.25], [-0.8], [-0.5]]; @@ -259,9 +271,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.25, 0.4], [-0.2, -0.5]]; @@ -290,9 +300,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.2], [-0.5], [-0.4], [0.3]]; @@ -318,9 +326,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.5, 0.3], [-0.25, 0.9]]; @@ -350,9 +356,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.2, -0.5], [-0.4, 0.3]]; @@ -384,16 +388,16 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; - /// use sample::interpolate::Linear; + /// use dasp_interpolate::linear::Linear; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; /// let mul = [[1.0], [1.0], [0.5], [0.5], [0.5], [0.5]]; /// let mut source = signal::from_iter(foo.iter().cloned()); - /// let interp = Linear::from_source(&mut source); + /// let a = source.next(); + /// let b = source.next(); + /// let interp = Linear::new(a, b); /// let hz_signal = signal::from_iter(mul.iter().cloned()); /// let frames: Vec<_> = source.mul_hz(interp, hz_signal).take(6).collect(); /// assert_eq!(&frames[..], &[[0.0], [1.0], [0.0], [-0.5], [-1.0], [-0.5]][..]); @@ -416,15 +420,15 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; - /// use sample::interpolate::Linear; + /// use dasp_interpolate::linear::Linear; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; /// let mut source = signal::from_iter(foo.iter().cloned()); - /// let interp = Linear::from_source(&mut source); + /// let a = source.next(); + /// let b = source.next(); + /// let interp = Linear::new(a, b); /// let frames: Vec<_> = source.from_hz_to_hz(interp, 1.0, 2.0).take(8).collect(); /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); /// } @@ -442,15 +446,15 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; - /// use sample::interpolate::Linear; + /// use dasp_interpolate::linear::Linear; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; /// let mut source = signal::from_iter(foo.iter().cloned()); - /// let interp = Linear::from_source(&mut source); + /// let a = source.next(); + /// let b = source.next(); + /// let interp = Linear::new(a, b); /// let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); /// } @@ -471,9 +475,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.2], [0.4]]; @@ -497,9 +499,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.1, 0.2], [0.3, 0.4]]; @@ -526,9 +526,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[1.2, 0.8], [-0.7, -1.4]]; @@ -552,9 +550,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let mut f = [0.0]; @@ -603,9 +599,8 @@ pub trait Signal { /// behaviour. /// /// ``` - /// extern crate sample; - /// - /// use sample::{ring_buffer, signal, Signal}; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let signal = signal::rate(44_100.0).const_hz(440.0).sine(); @@ -635,7 +630,7 @@ pub trait Signal { fn fork(self, ring_buffer: ring_buffer::Bounded) -> Fork where Self: Sized, - S: ring_buffer::SliceMut, + S: ring_buffer::SliceMut, { assert!(ring_buffer.is_empty()); let shared = ForkShared { @@ -643,48 +638,9 @@ pub trait Signal { ring_buffer: ring_buffer, pending: Fork::::B, }; - Fork { shared: RefCell::new(shared) } - } - - /// Moves the `Signal` into a `Bus` from which its output may be divided into multiple other - /// `Signal`s in the form of `Output`s. - /// - /// This method allows to create more complex directed acyclic graph structures that - /// incorporate concepts like sends, side-chaining, etc, rather than being restricted to tree - /// structures where signals can only ever be joined but never divided. - /// - /// Note: When using multiple `Output`s in this fashion, you will need to be sure to pull the - /// frames from each `Output` in sync (whether per frame or per buffer). This is because when - /// output A requests `Frame`s before output B, those frames must remain available for output - /// B and in turn must be stored in an intermediary ring buffer. - /// - /// # Example - /// - /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; - /// - /// fn main() { - /// let frames = [[0.1], [0.2], [0.3], [0.4], [0.5], [0.6]]; - /// let signal = signal::from_iter(frames.iter().cloned()); - /// let bus = signal.bus(); - /// let mut a = bus.send(); - /// let mut b = bus.send(); - /// assert_eq!(a.by_ref().take(3).collect::>(), vec![[0.1], [0.2], [0.3]]); - /// assert_eq!(b.by_ref().take(3).collect::>(), vec![[0.1], [0.2], [0.3]]); - /// - /// let c = bus.send(); - /// assert_eq!(c.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); - /// assert_eq!(b.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); - /// assert_eq!(a.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); - /// } - /// ``` - fn bus(self) -> Bus - where - Self: Sized, - { - Bus::new(self, BTreeMap::new()) + Fork { + shared: RefCell::new(shared), + } } /// Converts the `Signal` into an `Iterator` that will yield the given number for `Frame`s @@ -693,9 +649,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.1], [0.2], [0.3], [0.4]]; @@ -718,9 +672,7 @@ pub trait Signal { /// # Example /// /// ``` - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[1], [2]]; @@ -732,9 +684,7 @@ pub trait Signal { where Self: Sized, { - UntilExhausted { - signal: self, - } + UntilExhausted { signal: self } } /// Buffers the signal using the given ring buffer. @@ -745,10 +695,8 @@ pub trait Signal { /// popped from the front of the ring buffer and immediately returned. /// /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::{signal, Signal}; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.1], [0.2], [0.3], [0.4]]; @@ -766,9 +714,8 @@ pub trait Signal { /// If the given ring buffer already contains frames, those will be yielded first. /// /// ``` - /// extern crate sample; - /// use sample::ring_buffer; - /// use sample::{signal, Signal}; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.1], [0.2], [0.3], [0.4]]; @@ -795,75 +742,6 @@ pub trait Signal { } } - /// An adaptor that yields the RMS of the signal. - /// - /// The window size of the RMS detector is equal to the given ring buffer length. - /// - /// # Example - /// - /// ``` - /// extern crate sample; - /// - /// use sample::ring_buffer; - /// use sample::{signal, Signal}; - /// - /// fn main() { - /// let frames = [[0.9], [-0.8], [0.6], [-0.9]]; - /// let signal = signal::from_iter(frames.iter().cloned()); - /// let ring_buffer = ring_buffer::Fixed::from([[0.0]; 2]); - /// let mut rms_signal = signal.rms(ring_buffer); - /// assert_eq!( - /// [rms_signal.next(), rms_signal.next(), rms_signal.next()], - /// [[0.6363961030678927], [0.8514693182963201], [0.7071067811865476]] - /// ); - /// } - /// ``` - fn rms(self, ring_buffer: ring_buffer::Fixed) -> Rms - where - Self: Sized, - S: ring_buffer::Slice::Float> + ring_buffer::SliceMut, - { - Rms { - signal: self, - rms: rms::Rms::new(ring_buffer), - } - } - - /// An adaptor that detects and yields the envelope of the signal. - /// - /// # Example - /// - /// ``` - /// extern crate sample; - /// - /// use sample::{envelope, signal, Signal}; - /// - /// fn main() { - /// let signal = signal::rate(4.0).const_hz(1.0).sine(); - /// let attack = 1.0; - /// let release = 1.0; - /// let detector = envelope::Detector::peak(attack, release); - /// let mut envelope = signal.detect_envelope(detector); - /// assert_eq!( - /// envelope.take(4).collect::>(), - /// vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] - /// ); - /// } - /// ``` - fn detect_envelope( - self, - detector: envelope::Detector, - ) -> DetectEnvelope - where - Self: Sized, - D: envelope::Detect, - { - DetectEnvelope { - signal: self, - detector: detector, - } - } - /// Borrows a Signal rather than consuming it. /// /// This is useful to allow applying signal adaptors while still retaining ownership of the @@ -872,9 +750,7 @@ pub trait Signal { /// # Example /// /// ```rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0], [1], [2], [3], [4]]; @@ -893,7 +769,6 @@ pub trait Signal { } } - /// Consumes the given `Iterator`, converts it to a `Signal`, applies the given function to the /// `Signal` and returns an `Iterator` that will become exhausted when the consumed `Iterator` /// does. @@ -904,9 +779,7 @@ pub trait Signal { /// # Example /// /// ``` -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = vec![[0], [1], [2], [3]]; @@ -927,10 +800,8 @@ where new_signal.until_exhausted() } - ///// Signal Types - /// An iterator that endlessly yields `Frame`s of type `F` at equilibrium. #[derive(Clone)] pub struct Equilibrium { @@ -1011,7 +882,6 @@ pub struct Hz { rate: Rate, } - /// An iterator that yields a phase, useful for waveforms like Sine or Saw. #[derive(Clone)] pub struct Phase { @@ -1203,8 +1073,12 @@ impl Fork { pub fn by_rc(self) -> (BranchRcA, BranchRcB) { let Fork { shared } = self; let shared_fork = Rc::new(shared); - let a = BranchRcA { shared_fork: shared_fork.clone() }; - let b = BranchRcB { shared_fork: shared_fork }; + let a = BranchRcA { + shared_fork: shared_fork.clone(), + }; + let b = BranchRcB { + shared_fork: shared_fork, + }; (a, b) } @@ -1215,8 +1089,12 @@ impl Fork { /// less ergonomic in some cases as the returned branches are bound to the lifetime of `Fork`. pub fn by_ref(&mut self) -> (BranchRefA, BranchRefB) { let Fork { ref shared } = *self; - let a = BranchRefA { shared_fork: shared }; - let b = BranchRefB { shared_fork: shared }; + let a = BranchRefA { + shared_fork: shared, + }; + let b = BranchRefB { + shared_fork: shared, + }; (a, b) } } @@ -1237,7 +1115,7 @@ macro_rules! define_branch { impl Signal for $TRc where S: Signal, - D: ring_buffer::SliceMut, + D: ring_buffer::SliceMut, { type Frame = S::Frame; fn next(&mut self) -> Self::Frame { @@ -1257,7 +1135,7 @@ macro_rules! define_branch { impl<'a, S, D> Signal for $TRef<'a, S, D> where S: 'a + Signal, - D: 'a + ring_buffer::SliceMut, + D: 'a + ring_buffer::SliceMut, { type Frame = S::Frame; fn next(&mut self) -> Self::Frame { @@ -1311,42 +1189,6 @@ macro_rules! define_branch { define_branch!(BranchRcA, BranchRefA, A, B); define_branch!(BranchRcB, BranchRefB, B, A); - -/// A type which allows for `send`ing a single `Signal` to multiple outputs. -/// -/// This type manages -pub struct Bus -where - S: Signal, -{ - node: Rc>>, -} - -/// The data shared between each `Output`. -struct SharedNode -where - S: Signal, -{ - signal: S, - // The buffer of frames that have not yet been consumed by all outputs. - buffer: VecDeque, - // The number of frames in `buffer` that have already been read for each output. - frames_read: BTreeMap, - // The next output key. - next_key: usize, -} - -/// An output node to which some signal `S` is `Output`ing its frames. -/// -/// It may be more accurate to say that the `Output` "pull"s frames from the signal. -pub struct Output -where - S: Signal, -{ - key: usize, - node: Rc>>, -} - /// An iterator that yields `n` number of `Frame`s from the inner `signal`. #[derive(Clone)] pub struct Take @@ -1377,48 +1219,20 @@ pub struct BufferedFrames<'a, D: 'a> { ring_buffer: &'a mut ring_buffer::Bounded, } -/// An adaptor that yields the RMS of the signal. -/// -/// The window size of the RMS detector is equal to the given ring buffer length. -#[derive(Clone)] -pub struct Rms -where - S: Signal, - D: ring_buffer::Slice::Float>, -{ - signal: S, - rms: rms::Rms, -} - -/// An adaptor that detects and yields the envelope of the signal. -#[derive(Clone)] -pub struct DetectEnvelope -where - S: Signal, - D: envelope::Detect, -{ - signal: S, - detector: envelope::Detector, -} - - ///// Signal Constructors - /// Provides an iterator that endlessly yields `Frame`s of type `F` at equilibrium. /// /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::Signal; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { -/// let equilibrium: Vec<[f32; 1]> = sample::signal::equilibrium().take(4).collect(); +/// let equilibrium: Vec<[f32; 1]> = signal::equilibrium().take(4).collect(); /// assert_eq!(equilibrium, vec![[0.0], [0.0], [0.0], [0.0]]); /// -/// let equilibrium: Vec<[u8; 2]> = sample::signal::equilibrium().take(3).collect(); +/// let equilibrium: Vec<[u8; 2]> = signal::equilibrium().take(3).collect(); /// assert_eq!(equilibrium, vec![[128, 128], [128, 128], [128, 128]]); /// } /// ``` @@ -1426,10 +1240,11 @@ pub fn equilibrium() -> Equilibrium where F: Frame, { - Equilibrium { frame: core::marker::PhantomData } + Equilibrium { + frame: core::marker::PhantomData, + } } - /// A signal that generates frames using the given function. /// /// The resulting signal is assumed to be infinite and `is_exhausted` will always return `false`. @@ -1438,9 +1253,7 @@ where /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let mut frames = signal::gen(|| [0.5]); @@ -1460,7 +1273,6 @@ where } } - /// A signal that generates frames using the given function which may mutate some state. /// /// The resulting signal is assumed to be infinite and `is_exhausted` will always return `false`. @@ -1469,9 +1281,7 @@ where /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let mut f = [0.0]; @@ -1496,7 +1306,6 @@ where } } - /// Create a new `Signal` from the given `Frame`-yielding `Iterator`. /// /// When the `Iterator` is exhausted, the new `Signal` will yield `F::equilibrium`. @@ -1507,9 +1316,7 @@ where /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[1], [-3], [5], [6]]; @@ -1534,7 +1341,6 @@ where } } - /// Create a new `Signal` from the given `Frame`-yielding `Iterator`. /// /// When the `Iterator` is exhausted, the new `Signal` will yield `F::equilibrium`. @@ -1542,9 +1348,7 @@ where /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let foo = [0, 1, 2, 3]; @@ -1575,15 +1379,12 @@ where } } - /// Creates a `Phase` that continuously steps forward by the given `step` size yielder. /// /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let step = signal::rate(4.0).const_hz(1.0); @@ -1607,7 +1408,6 @@ where } } - /// Creates a frame `Rate` (aka sample rate) representing the rate at which a signal may be /// sampled. /// @@ -1617,15 +1417,12 @@ pub fn rate(hz: f64) -> Rate { Rate { hz: hz } } - /// Produces a `Signal` that yields a sine wave oscillating at the given hz. /// /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// // Generates a sine wave signal at 1hz to be sampled 4 times per second. @@ -1645,9 +1442,7 @@ pub fn sine(phase: Phase) -> Sine { /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// // Generates a saw wave signal at 1hz to be sampled 4 times per second. @@ -1667,9 +1462,7 @@ pub fn saw(phase: Phase) -> Saw { /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// // Generates a square wave signal at 1hz to be sampled 4 times per second. @@ -1689,12 +1482,10 @@ pub fn square(phase: Phase) -> Square { /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { -/// let mut noise = sample::signal::noise(0); +/// let mut noise = signal::noise(0); /// for n in noise.take(1_000_000) { /// assert!(-1.0 <= n[0] && n[0] < 1.0); /// } @@ -1711,9 +1502,7 @@ pub fn noise(seed: u64) -> Noise { /// # Example /// /// ```rust -/// extern crate sample; -/// -/// use sample::{signal, Signal}; +/// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// // Creates a simplex noise signal oscillating at 440hz sampled 44_100 times per second. @@ -1727,32 +1516,14 @@ pub fn noise_simplex(phase: Phase) -> NoiseSimplex { NoiseSimplex { phase: phase } } - //// Trait Implementations for Signal Types. - impl<'a, S> Signal for &'a mut S where S: Signal + ?Sized, { type Frame = S::Frame; - #[inline] - fn next(&mut self) -> Self::Frame { - (**self).next() - } - - #[inline] - fn is_exhausted(&self) -> bool { - (**self).is_exhausted() - } -} - -impl Signal for Box -where - S: Signal + ?Sized, -{ - type Frame = S::Frame; #[inline] fn next(&mut self) -> Self::Frame { (**self).next() @@ -1764,20 +1535,20 @@ where } } - impl Signal for FromIterator where I: Iterator, I::Item: Frame, { type Frame = I::Item; + #[inline] fn next(&mut self) -> Self::Frame { match self.next.take() { Some(frame) => { self.next = self.iter.next(); frame - }, + } None => Frame::equilibrium(), } } @@ -1788,7 +1559,6 @@ where } } - impl Signal for FromInterleavedSamplesIterator where I: Iterator, @@ -1796,13 +1566,14 @@ where F: Frame, { type Frame = F; + #[inline] fn next(&mut self) -> Self::Frame { match self.next.take() { Some(frame) => { self.next = F::from_samples(&mut self.samples); frame - }, + } None => F::equilibrium(), } } @@ -1813,45 +1584,44 @@ where } } - impl Signal for Equilibrium where F: Frame, { type Frame = F; + #[inline] fn next(&mut self) -> Self::Frame { F::equilibrium() } } - impl Signal for Gen where G: Fn() -> F, F: Frame, { type Frame = F; + #[inline] fn next(&mut self) -> Self::Frame { (self.gen)() } } - impl Signal for GenMut where G: FnMut() -> F, F: Frame, { type Frame = F; + #[inline] fn next(&mut self) -> Self::Frame { (self.gen_mut)() } } - impl Signal for Map where S: Signal, @@ -1859,6 +1629,7 @@ where F: Frame, { type Frame = F; + #[inline] fn next(&mut self) -> Self::Frame { (self.map)(self.signal.next()) @@ -1869,7 +1640,6 @@ where } } - impl Signal for ZipMap where S: Signal, @@ -1878,6 +1648,7 @@ where F: Frame, { type Frame = F; + #[inline] fn next(&mut self) -> Self::Frame { (self.map)(self.this.next(), self.other.next()) @@ -1888,12 +1659,12 @@ where } } - impl Signal for Hz where S: Signal, { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { [self.step()] @@ -1905,47 +1676,47 @@ where } } - impl Signal for ConstHz { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { [self.step()] } } - impl Signal for Phase where S: Step, { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { [self.next_phase()] } } - impl Signal for Sine where S: Step, { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { const PI_2: f64 = core::f64::consts::PI * 2.0; let phase = self.phase.next_phase(); - [super::ops::f64::sin(PI_2 * phase)] + [ops::f64::sin(PI_2 * phase)] } } - impl Signal for Saw where S: Step, { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { let phase = self.phase.next_phase(); @@ -1953,12 +1724,12 @@ where } } - impl Signal for Square where S: Step, { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { let phase = self.phase.next_phase(); @@ -1966,7 +1737,6 @@ where } } - impl Rate { /// Create a `ConstHz` signal which consistently yields `hz / rate`. pub fn const_hz(self, hz: f64) -> ConstHz { @@ -1975,14 +1745,12 @@ impl Rate { /// Create a `Hz` signal which yields phase step sizes controlled by an input /// signal `hz`. - /// + /// /// # Example - /// + /// /// ``` rust - /// extern crate sample; - /// - /// use sample::{signal, Signal}; - /// + /// use dasp_signal::{self as signal, Signal}; + /// /// fn main() { /// let step = signal::rate(4.0).hz(signal::gen(|| [1.0])); /// let mut phase = signal::phase(step); @@ -1998,10 +1766,7 @@ impl Rate { where S: Signal, { - Hz { - hz: hz, - rate: self, - } + Hz { hz: hz, rate: self } } } @@ -2104,7 +1869,6 @@ where } } - impl Phase where S: Step, @@ -2149,7 +1913,6 @@ where } } - impl Noise { #[inline] pub fn next_sample(&mut self) -> f64 { @@ -2163,10 +1926,15 @@ impl Noise { const PRIME_2: u64 = 789_221; const PRIME_3: u64 = 1_376_312_589; let x = (seed << 13) ^ seed; - 1.0 - - (x.wrapping_mul(x.wrapping_mul(x).wrapping_mul(PRIME_1).wrapping_add( - PRIME_2, - )).wrapping_add(PRIME_3) & 0x7fffffff) as f64 / 1_073_741_824.0 + 1.0 - (x + .wrapping_mul( + x.wrapping_mul(x) + .wrapping_mul(PRIME_1) + .wrapping_add(PRIME_2), + ) + .wrapping_add(PRIME_3) + & 0x7fffffff) as f64 + / 1_073_741_824.0 } let noise = noise_1(self.seed); @@ -2183,7 +1951,6 @@ impl Signal for Noise { } } - impl NoiseSimplex where S: Step, @@ -2208,265 +1975,23 @@ where // This function and the enclosing functions have been adapted from SRombauts' MIT licensed // C++ implementation at the following link: https://github.com/SRombauts/SimplexNoise fn simplex_noise_1d(x: f64) -> f64 { - // Permutation table. This is a random jumble of all numbers 0...255. const PERM: [u8; 256] = [ - 151, - 160, - 137, - 91, - 90, - 15, - 131, - 13, - 201, - 95, - 96, - 53, - 194, - 233, - 7, - 225, - 140, - 36, - 103, - 30, - 69, - 142, - 8, - 99, - 37, - 240, - 21, - 10, - 23, - 190, - 6, - 148, - 247, - 120, - 234, - 75, - 0, - 26, - 197, - 62, - 94, - 252, - 219, - 203, - 117, - 35, - 11, - 32, - 57, - 177, - 33, - 88, - 237, - 149, - 56, - 87, - 174, - 20, - 125, - 136, - 171, - 168, - 68, - 175, - 74, - 165, - 71, - 134, - 139, - 48, - 27, - 166, - 77, - 146, - 158, - 231, - 83, - 111, - 229, - 122, - 60, - 211, - 133, - 230, - 220, - 105, - 92, - 41, - 55, - 46, - 245, - 40, - 244, - 102, - 143, - 54, - 65, - 25, - 63, - 161, - 1, - 216, - 80, - 73, - 209, - 76, - 132, - 187, - 208, - 89, - 18, - 169, - 200, - 196, - 135, - 130, - 116, - 188, - 159, - 86, - 164, - 100, - 109, - 198, - 173, - 186, - 3, - 64, - 52, - 217, - 226, - 250, - 124, - 123, - 5, - 202, - 38, - 147, - 118, - 126, - 255, - 82, - 85, - 212, - 207, - 206, - 59, - 227, - 47, - 16, - 58, - 17, - 182, - 189, - 28, - 42, - 223, - 183, - 170, - 213, - 119, - 248, - 152, - 2, - 44, - 154, - 163, - 70, - 221, - 153, - 101, - 155, - 167, - 43, - 172, - 9, - 129, - 22, - 39, - 253, - 19, - 98, - 108, - 110, - 79, - 113, - 224, - 232, - 178, - 185, - 112, - 104, - 218, - 246, - 97, - 228, - 251, - 34, - 242, - 193, - 238, - 210, - 144, - 12, - 191, - 179, - 162, - 241, - 81, - 51, - 145, - 235, - 249, - 14, - 239, - 107, - 49, - 192, - 214, - 31, - 181, - 199, - 106, - 157, - 184, - 84, - 204, - 176, - 115, - 121, - 50, - 45, - 127, - 4, - 150, - 254, - 138, - 236, - 205, - 93, - 222, - 114, - 67, - 29, - 24, - 72, - 243, - 141, - 128, - 195, - 78, - 66, - 215, - 61, - 156, - 180, + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, + 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, + 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, + 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, + 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, + 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, + 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, + 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, + 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, + 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, + 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, + 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, + 215, 61, 156, 180, ]; // Hashes the given integer with the above permutation table. @@ -2489,7 +2014,7 @@ where } // Corners coordinates (nearest integer values). - let i0 = super::ops::f64::floor(x) as i64; + let i0 = ops::f64::floor(x) as i64; let i1 = i0 + 1; // Distances to corners (between 0 and 1); @@ -2519,21 +2044,24 @@ where S: Step, { type Frame = [f64; 1]; + #[inline] fn next(&mut self) -> Self::Frame { [self.next_sample()] } } - impl Signal for AddAmp where A: Signal, B: Signal, - B::Frame: Frame::Sample as Sample>::Signed, - NumChannels=::NumChannels>, + B::Frame: Frame< + Sample = <::Sample as Sample>::Signed, + NumChannels = ::NumChannels, + >, { type Frame = A::Frame; + #[inline] fn next(&mut self) -> Self::Frame { self.a.next().add_amp(self.b.next()) @@ -2545,15 +2073,17 @@ where } } - impl Signal for MulAmp where A: Signal, B: Signal, - B::Frame: Frame::Sample as Sample>::Float, - NumChannels=::NumChannels>, + B::Frame: Frame< + Sample = <::Sample as Sample>::Float, + NumChannels = ::NumChannels, + >, { type Frame = A::Frame; + #[inline] fn next(&mut self) -> Self::Frame { self.a.next().mul_amp(self.b.next()) @@ -2565,12 +2095,12 @@ where } } - impl Signal for ScaleAmp where S: Signal, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { self.signal.next().scale_amp(self.amp) @@ -2582,14 +2112,16 @@ where } } - impl Signal for ScaleAmpPerChannel where S: Signal, - F: Frame::Sample as Sample>::Float, - NumChannels=::NumChannels>, + F: Frame< + Sample = <::Sample as Sample>::Float, + NumChannels = ::NumChannels, + >, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { self.signal.next().mul_amp(self.amp_frame) @@ -2601,12 +2133,12 @@ where } } - impl Signal for OffsetAmp where S: Signal, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { self.signal.next().offset_amp(self.offset) @@ -2618,14 +2150,16 @@ where } } - impl Signal for OffsetAmpPerChannel where S: Signal, - F: Frame::Sample as Sample>::Signed, - NumChannels=::NumChannels>, + F: Frame< + Sample = <::Sample as Sample>::Signed, + NumChannels = ::NumChannels, + >, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { self.signal.next().add_amp(self.amp_frame) @@ -2637,7 +2171,6 @@ where } } - impl Signal for MulHz where S: Signal, @@ -2646,6 +2179,7 @@ where I: Interpolator, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { let mul = self.mul_per_frame.next()[0]; @@ -2659,12 +2193,12 @@ where } } - impl Signal for Delay where S: Signal, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { if self.n_frames > 0 { @@ -2681,13 +2215,13 @@ where } } - impl Signal for Inspect where S: Signal, F: FnMut(&S::Frame), { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { let out = self.signal.next(); @@ -2701,7 +2235,6 @@ where } } - impl IntoInterleavedSamples where S: Signal, @@ -2729,6 +2262,7 @@ where S: Signal, { type Item = ::Sample; + #[inline] fn next(&mut self) -> Option { Some(self.samples.next_sample()) @@ -2770,16 +2304,18 @@ where { #[inline] fn clone(&self) -> Self { - IntoInterleavedSamplesIterator { samples: self.samples.clone() } + IntoInterleavedSamplesIterator { + samples: self.samples.clone(), + } } } - impl Signal for ClipAmp where S: Signal, { type Frame = S::Frame; + #[inline] fn next(&mut self) -> Self::Frame { let f = self.signal.next(); @@ -2791,7 +2327,8 @@ where -self.thresh } else { s - }.to_sample() + } + .to_sample() }) } @@ -2801,177 +2338,12 @@ where } } - -impl Bus -where - S: Signal, -{ - fn new(signal: S, frames_read: BTreeMap) -> Self { - Bus { - node: Rc::new(core::cell::RefCell::new(SharedNode { - signal: signal, - buffer: VecDeque::new(), - frames_read: frames_read, - next_key: 0, - })), - } - } - - /// Produce a new Output node to which the signal `S` will output its frames. - #[inline] - pub fn send(&self) -> Output { - let mut node = self.node.borrow_mut(); - - // Get the key and increment for the next output. - let key = node.next_key; - node.next_key = node.next_key.wrapping_add(1); - - // Insert the number of frames read by the new output. - let num_frames = node.buffer.len(); - node.frames_read.insert(key, num_frames); - - Output { - key: key, - node: self.node.clone(), - } - } -} - -impl SharedNode -where - S: Signal, -{ - // Requests the next frame for the `Output` at the given key. - // - // If there are no frames pending for the output, a new frame will be requested from the - // signal and appended to the ring buffer to be received by the other outputs. - fn next_frame(&mut self, key: usize) -> S::Frame { - let num_frames = self.buffer.len(); - let frames_read = self.frames_read.remove(&key).expect( - "no frames_read for Output", - ); - - let frame = if frames_read < num_frames { - self.buffer[frames_read] - } else { - let frame = self.signal.next(); - self.buffer.push_back(frame); - frame - }; - - // If the number of frames read by this output is the lowest, then we can pop the frame - // from the front. - let least_frames_read = !self.frames_read.values().any(|&other_frames_read| { - other_frames_read <= frames_read - }); - - // If this output had read the least number of frames, pop the front frame and decrement - // the frames read counters for each of the other outputs. - let new_frames_read = if least_frames_read { - self.buffer.pop_front(); - for other_frames_read in self.frames_read.values_mut() { - *other_frames_read -= 1; - } - frames_read - } else { - frames_read + 1 - }; - - self.frames_read.insert(key, new_frames_read); - - frame - } - - #[inline] - fn pending_frames(&self, key: usize) -> usize { - self.buffer.len() - self.frames_read[&key] - } - - // Drop the given output from the `Bus`. - // - // Called by the `Output::drop` implementation. - fn drop_output(&mut self, key: usize) { - self.frames_read.remove(&key); - let least_frames_read = self.frames_read.values().fold(self.buffer.len(), |a, &b| { - core::cmp::min(a, b) - }); - if least_frames_read > 0 { - for frames_read in self.frames_read.values_mut() { - *frames_read -= least_frames_read; - } - for _ in 0..least_frames_read { - self.buffer.pop_front(); - } - } - } -} - -impl Output -where - S: Signal, -{ - /// The number of frames that have been requested from the `Signal` `S` by some other `Output` - /// that have not yet been requested by this `Output`. - /// - /// This is useful when using an `Output` to "monitor" some signal, allowing the user to drain - /// only frames that have already been requested by some other `Output`. - /// - /// # Example - /// - /// ``` - /// extern crate sample; - /// - /// use sample::{signal, Signal}; - /// - /// fn main() { - /// let frames = [[0.1], [0.2], [0.3]]; - /// let bus = signal::from_iter(frames.iter().cloned()).bus(); - /// let signal = bus.send(); - /// let mut monitor = bus.send(); - /// assert_eq!(signal.take(3).collect::>(), vec![[0.1], [0.2], [0.3]]); - /// assert_eq!(monitor.pending_frames(), 3); - /// assert_eq!(monitor.next(), [0.1]); - /// assert_eq!(monitor.pending_frames(), 2); - /// } - /// ``` - #[inline] - pub fn pending_frames(&self) -> usize { - self.node.borrow().pending_frames(self.key) - } -} - -impl Signal for Output -where - S: Signal, -{ - type Frame = S::Frame; - #[inline] - fn next(&mut self) -> Self::Frame { - self.node.borrow_mut().next_frame(self.key) - } - - #[inline] - fn is_exhausted(&self) -> bool { - let node = self.node.borrow(); - node.pending_frames(self.key) == 0 && node.signal.is_exhausted() - } -} - -impl Drop for Output -where - S: Signal, -{ - fn drop(&mut self) { - self.node.borrow_mut().drop_output(self.key) - } -} - - impl Iterator for Take where S: Signal, { type Item = S::Frame; + #[inline] fn next(&mut self) -> Option { if self.n == 0 { @@ -3008,10 +2380,8 @@ where /// filled using `Buffered`'s inner `signal` before `BufferedFrames` is returned. /// /// ``` - /// extern crate sample; - /// - /// use sample::signal::{self, Signal}; - /// use sample::ring_buffer; + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { /// let frames = [[0.1], [0.2], [0.3], [0.4]]; @@ -3033,7 +2403,9 @@ where ring_buffer.push(signal.next()); } } - BufferedFrames { ring_buffer: ring_buffer } + BufferedFrames { + ring_buffer: ring_buffer, + } } /// Consumes the `Buffered` signal and returns its inner signal `S` and bounded ring buffer. @@ -3049,17 +2421,23 @@ where impl Signal for Buffered where S: Signal, - D: ring_buffer::Slice + ring_buffer::SliceMut, + D: ring_buffer::Slice + ring_buffer::SliceMut, { type Frame = S::Frame; + fn next(&mut self) -> Self::Frame { - let Buffered { ref mut signal, ref mut ring_buffer } = *self; + let Buffered { + ref mut signal, + ref mut ring_buffer, + } = *self; loop { match ring_buffer.pop() { Some(frame) => return frame, - None => for _ in 0..ring_buffer.max_len() { - ring_buffer.push(signal.next()); - }, + None => { + for _ in 0..ring_buffer.max_len() { + ring_buffer.push(signal.next()); + } + } } } } @@ -3079,77 +2457,3 @@ where self.ring_buffer.pop() } } - -impl Rms -where - S: Signal, - D: ring_buffer::Slice::Float> + ring_buffer::SliceMut, -{ -/// The same as `Signal::next` but does not calculate the final square root required to -/// determine the RMS. - pub fn next_squared(&mut self) -> ::Frame { - self.rms.next_squared(self.signal.next()) - } - -/// Consumes the `Rms` signal and returns its inner signal `S` and `Rms` detector. - pub fn into_parts(self) -> (S, rms::Rms) { - let Rms { - signal, - rms, - } = self; - (signal, rms) - } -} - -impl Signal for Rms -where - S: Signal, - D: ring_buffer::Slice::Float> - + ring_buffer::SliceMut, -{ - type Frame = ::Float; - fn next(&mut self) -> Self::Frame { - self.rms.next(self.signal.next()) - } - - fn is_exhausted(&self) -> bool { - self.signal.is_exhausted() - } -} - -impl DetectEnvelope -where - S: Signal, - D: envelope::Detect, -{ - /// Set the **Detector**'s attack time as a number of frames. - pub fn set_attack_frames(&mut self, frames: f32) { - self.detector.set_attack_frames(frames); - } - - /// Set the **Detector**'s release time as a number of frames. - pub fn set_release_frames(&mut self, frames: f32) { - self.detector.set_release_frames(frames); - } - - /// Consumes `Self` and returns the inner signal `S` and `Detector`. - pub fn into_parts(self) -> (S, envelope::Detector) { - let DetectEnvelope { signal, detector } = self; - (signal, detector) - } -} - -impl Signal for DetectEnvelope -where - S: Signal, - D: envelope::Detect, -{ - type Frame = D::Output; - fn next(&mut self) -> Self::Frame { - self.detector.next(self.signal.next()) - } - - fn is_exhausted(&self) -> bool { - self.signal.is_exhausted() - } -} diff --git a/dasp_signal/src/ops.rs b/dasp_signal/src/ops.rs new file mode 100644 index 00000000..5df09ba2 --- /dev/null +++ b/dasp_signal/src/ops.rs @@ -0,0 +1,33 @@ +pub mod f64 { + #[allow(unused_imports)] + use core; + + #[cfg(not(feature = "std"))] + pub fn floor(x: f64) -> f64 { + unsafe { core::intrinsics::floorf64(x) } + } + #[cfg(feature = "std")] + pub fn floor(x: f64) -> f64 { + x.floor() + } + + #[cfg(not(feature = "std"))] + #[allow(dead_code)] + pub fn ceil(x: f64) -> f64 { + unsafe { core::intrinsics::ceilf64(x) } + } + #[cfg(feature = "std")] + #[allow(dead_code)] + pub fn ceil(x: f64) -> f64 { + x.ceil() + } + + #[cfg(not(feature = "std"))] + pub fn sin(x: f64) -> f64 { + unsafe { core::intrinsics::sinf64(x) } + } + #[cfg(feature = "std")] + pub fn sin(x: f64) -> f64 { + x.sin() + } +} diff --git a/dasp_signal/src/rms.rs b/dasp_signal/src/rms.rs new file mode 100644 index 00000000..343fd2aa --- /dev/null +++ b/dasp_signal/src/rms.rs @@ -0,0 +1,91 @@ +use crate::Signal; +use dasp_frame::Frame; +use dasp_ring_buffer as ring_buffer; +use dasp_rms as rms; + +pub trait SignalRms: Signal { + /// An adaptor that yields the RMS of the signal. + /// + /// The window size of the RMS detector is equal to the given ring buffer length. + /// + /// # Example + /// + /// ``` + /// use dasp_ring_buffer as ring_buffer; + /// use dasp_signal::{self as signal, Signal}; + /// use dasp_signal::rms::SignalRms; + /// + /// fn main() { + /// let frames = [[0.9], [-0.8], [0.6], [-0.9]]; + /// let signal = signal::from_iter(frames.iter().cloned()); + /// let ring_buffer = ring_buffer::Fixed::from([[0.0]; 2]); + /// let mut rms_signal = signal.rms(ring_buffer); + /// assert_eq!( + /// [rms_signal.next(), rms_signal.next(), rms_signal.next()], + /// [[0.6363961030678927], [0.8514693182963201], [0.7071067811865476]] + /// ); + /// } + /// ``` + fn rms(self, ring_buffer: ring_buffer::Fixed) -> Rms + where + Self: Sized, + S: ring_buffer::Slice::Float> + ring_buffer::SliceMut, + { + Rms { + signal: self, + rms: rms::Rms::new(ring_buffer), + } + } +} + +/// An adaptor that yields the RMS of the signal. +/// +/// The window size of the RMS detector is equal to the given ring buffer length. +#[derive(Clone)] +pub struct Rms +where + S: Signal, + D: ring_buffer::Slice::Float>, +{ + signal: S, + rms: rms::Rms, +} + +impl Rms +where + S: Signal, + D: ring_buffer::Slice::Float> + ring_buffer::SliceMut, +{ + /// The same as `Signal::next` but does not calculate the final square root required to + /// determine the RMS. + pub fn next_squared(&mut self) -> ::Frame { + self.rms.next_squared(self.signal.next()) + } + + /// Consumes the `Rms` signal and returns its inner signal `S` and `Rms` detector. + pub fn into_parts(self) -> (S, rms::Rms) { + let Rms { + signal, + rms, + } = self; + (signal, rms) + } +} + +impl Signal for Rms +where + S: Signal, + D: ring_buffer::Slice::Float> + + ring_buffer::SliceMut, +{ + type Frame = ::Float; + fn next(&mut self) -> Self::Frame { + self.rms.next(self.signal.next()) + } + + fn is_exhausted(&self) -> bool { + self.signal.is_exhausted() + } +} + +impl SignalRms for T where T: Signal {} diff --git a/dasp_signal/src/window/hanning.rs b/dasp_signal/src/window/hanning.rs new file mode 100644 index 00000000..c5db0f00 --- /dev/null +++ b/dasp_signal/src/window/hanning.rs @@ -0,0 +1,21 @@ +use dasp_frame::Frame; +use dasp_window::hanning::Hanning; +use super::{Window, Windower}; + +impl<'a, F> Windower<'a, F, Hanning> +where + F: 'a + Frame, +{ + /// Constructor for a `Windower` using the `Hanning` window function. + pub fn hanning(frames: &'a [F], bin: usize, hop: usize) -> Self { + Windower::new(frames, bin, hop) + } +} + +/// A helper function for constructing a `Window` that uses a `Hanning` `Type` function. +pub fn hanning(num_frames: usize) -> Window +where + F: Frame, +{ + Window::new(num_frames) +} diff --git a/src/window.rs b/dasp_signal/src/window/mod.rs similarity index 56% rename from src/window.rs rename to dasp_signal/src/window/mod.rs index 87040257..76c7cf16 100644 --- a/src/window.rs +++ b/dasp_signal/src/window/mod.rs @@ -1,28 +1,18 @@ -//! Module for windowing over a batch of Frames. Includes default Hanning and Rectangle window -//! types. - -use {FloatSample, Sample}; -use core; +use crate::{ConstHz, FromIterator, Phase, Signal}; use core::marker::PhantomData; -use frame::Frame; -use signal::{self, Signal}; +use dasp_frame::Frame; +use dasp_sample::Sample; +use dasp_window::Window as WindowType; -/// The window function used within a `Window`. -pub trait Type { - /// Returns the amplitude for the given phase, given as some `Sample` type. - fn at_phase(phase: S) -> S; -} +#[cfg(feature = "window-hanning")] +pub use hanning::hanning; +#[cfg(feature = "window-rectangle")] +pub use rectangle::rectangle; -/// A type of window function, also known as the "raised cosine window". -/// -/// [Wiki entry](https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Hanning; - -/// The simplest window type, equivalent to replacing all but *N* values of data sequence by -/// zeroes, making it appear as though the waveform suddenly turns on and off. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct Rectangle; +#[cfg(feature = "window-hanning")] +mod hanning; +#[cfg(feature = "window-rectangle")] +mod rectangle; /// A `Signal` type that for every yielded `phase`, yields the amplitude across the `window::Type` /// for that phase. @@ -30,10 +20,10 @@ pub struct Rectangle; pub struct Window where F: Frame, - W: Type, + W: WindowType, { /// Yields phase stepped at a constant rate to be passed to the window function `W`. - pub phase: signal::Phase, + pub phase: Phase, marker: PhantomData<(F, W)>, } @@ -42,7 +32,7 @@ where pub struct Windower<'a, F, W> where F: 'a + Frame, - W: Type, + W: WindowType, { /// The size of each `Windowed` chunk to be yielded. pub bin: usize, @@ -60,50 +50,31 @@ where pub struct Windowed where S: Signal, - W: Type, + W: WindowType, { signal: S, window: Window<::Float, W>, } - -impl Type for Hanning { - fn at_phase(phase: S) -> S { - const PI_2: f64 = core::f64::consts::PI * 2.0; - let v = phase.to_float_sample().to_sample() * PI_2; - (0.5 * (1.0 - super::ops::f64::cos(v))) - .to_sample::() - .to_sample::() - } -} - -impl Type for Rectangle { - fn at_phase(_phase: S) -> S { - ::identity().to_sample::() - } -} - - impl Window where F: Frame, - W: Type, + W: WindowType, { /// Construct a new `Window` with the given length as a number of frames. pub fn new(len: usize) -> Self { - let step = signal::rate(len as f64 - 1.0).const_hz(1.0); + let step = crate::rate(len as f64 - 1.0).const_hz(1.0); Window { - phase: signal::phase(step), + phase: crate::phase(step), marker: PhantomData, } } } - impl<'a, F, W> Windower<'a, F, W> where F: 'a + Frame, - W: Type, + W: WindowType, { /// Constructor for a new `Windower` iterator. pub fn new(frames: &'a [F], bin: usize, hop: usize) -> Self { @@ -116,31 +87,10 @@ where } } -impl<'a, F> Windower<'a, F, Rectangle> -where - F: 'a + Frame, -{ - /// Constructor for a `Windower` using the `Rectangle` window function. - pub fn rectangle(frames: &'a [F], bin: usize, hop: usize) -> Self { - Windower::new(frames, bin, hop) - } -} - -impl<'a, F> Windower<'a, F, Hanning> -where - F: 'a + Frame, -{ - /// Constructor for a `Windower` using the `Hanning` window function. - pub fn hanning(frames: &'a [F], bin: usize, hop: usize) -> Self { - Windower::new(frames, bin, hop) - } -} - - impl Iterator for Window where F: Frame, - W: Type, + W: WindowType, { type Item = F; @@ -154,9 +104,9 @@ where impl<'a, F, W> Iterator for Windower<'a, F, W> where F: 'a + Frame, - W: Type, + W: WindowType, { - type Item = Windowed>>, W>; + type Item = Windowed>>, W>; fn next(&mut self) -> Option { let num_frames = self.frames.len(); @@ -169,7 +119,7 @@ where &[] }; Some(Windowed { - signal: signal::from_iter(frames.iter().cloned()), + signal: crate::from_iter(frames.iter().cloned()), window: window, }) } else { @@ -198,7 +148,7 @@ where impl Iterator for Windowed where S: Signal, - W: Type, + W: WindowType, { type Item = S::Frame; fn next(&mut self) -> Option { @@ -208,19 +158,3 @@ where }) } } - -/// A helper function for constructing a `Window` that uses a `Hanning` `Type` function. -pub fn hanning(num_frames: usize) -> Window -where - F: Frame, -{ - Window::new(num_frames) -} - -/// A helper function for constructing a `Window` that uses a `Rectangle` `Type` function. -pub fn rectangle(num_frames: usize) -> Window -where - F: Frame, -{ - Window::new(num_frames) -} diff --git a/dasp_signal/src/window/rectangle.rs b/dasp_signal/src/window/rectangle.rs new file mode 100644 index 00000000..c2e9c486 --- /dev/null +++ b/dasp_signal/src/window/rectangle.rs @@ -0,0 +1,21 @@ +use dasp_frame::Frame; +use dasp_window::rectangle::Rectangle; +use super::{Window, Windower}; + +impl<'a, F> Windower<'a, F, Rectangle> +where + F: 'a + Frame, +{ + /// Constructor for a `Windower` using the `Rectangle` window function. + pub fn rectangle(frames: &'a [F], bin: usize, hop: usize) -> Self { + Windower::new(frames, bin, hop) + } +} + +/// A helper function for constructing a `Window` that uses a `Rectangle` `Type` function. +pub fn rectangle(num_frames: usize) -> Window +where + F: Frame, +{ + Window::new(num_frames) +} diff --git a/tests/interpolate.rs b/dasp_signal/tests/interpolate.rs similarity index 79% rename from tests/interpolate.rs rename to dasp_signal/tests/interpolate.rs index 539ad427..1f62b2c9 100644 --- a/tests/interpolate.rs +++ b/dasp_signal/tests/interpolate.rs @@ -1,20 +1,14 @@ //! Tests for the `Converter` and `Interpolator` traits -extern crate sample; +use dasp_interpolate::{floor::Floor, linear::Linear, sinc::Sinc}; +use dasp_ring_buffer as ring_buffer; +use dasp_signal::{self as signal, Signal, interpolate::Converter}; -#[cfg(feature="interpolate")] -use sample::interpolate::{Converter, Floor, Linear, Sinc}; -#[cfg(feature="ring_buffer")] -use sample::ring_buffer; -#[cfg(feature="signal")] -use sample::{signal, Signal}; - -#[cfg(feature="interpolate")] #[test] fn test_floor_converter() { let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]]; let mut source = signal::from_iter(frames.iter().cloned()); - let interp = Floor::from_source(&mut source); + let interp = Floor::new(source.next()); let mut conv = Converter::scale_playback_hz(source, interp, 0.5); assert_eq!(conv.next(), [0.0]); @@ -29,12 +23,13 @@ fn test_floor_converter() { assert_eq!(conv.next(), [2.0]); } -#[cfg(all(feature="interpolate", feature = "signal"))] #[test] fn test_linear_converter() { let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]]; let mut source = signal::from_iter(frames.iter().cloned()); - let interp = Linear::from_source(&mut source); + let a = source.next(); + let b = source.next(); + let interp = Linear::new(a, b); let mut conv = Converter::scale_playback_hz(source, interp, 0.5); assert_eq!(conv.next(), [0.0]); @@ -47,13 +42,14 @@ fn test_linear_converter() { assert_eq!(conv.next(), [1.0]); } -#[cfg(all(feature="interpolate", feature = "signal"))] #[test] fn test_scale_playback_rate() { // Scale the playback rate by `0.5` let foo = [[0.0], [1.0], [0.0], [-1.0]]; let mut source = signal::from_iter(foo.iter().cloned()); - let interp = Linear::from_source(&mut source); + let a = source.next(); + let b = source.next(); + let interp = Linear::new(a, b); let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); assert_eq!( &frames[..], @@ -61,7 +57,6 @@ fn test_scale_playback_rate() { ); } -#[cfg(all(feature="interpolate", feature = "signal"))] #[test] fn test_sinc() { let foo = [[0.0f64], [1.0], [0.0], [-1.0]]; diff --git a/tests/signal.rs b/dasp_signal/tests/signal.rs similarity index 74% rename from tests/signal.rs rename to dasp_signal/tests/signal.rs index d323edf5..c6a5eeb1 100644 --- a/tests/signal.rs +++ b/dasp_signal/tests/signal.rs @@ -1,18 +1,13 @@ //! Tests for the `Signal` trait. -extern crate sample; +use dasp_signal::{self as signal, Signal}; -#[cfg(feature = "signal")] -use sample::{signal, Signal}; - -#[cfg(feature = "signal")] #[test] fn test_equilibrium() { - let equilibrium: Vec<[i8; 1]> = sample::signal::equilibrium().take(4).collect(); + let equilibrium: Vec<[i8; 1]> = signal::equilibrium().take(4).collect(); assert_eq!(equilibrium, vec![[0], [0], [0], [0]]); } -#[cfg(feature = "signal")] #[test] fn test_scale_amp() { let foo = [[0.5], [0.8], [-0.4], [-0.2]]; @@ -24,7 +19,6 @@ fn test_scale_amp() { assert_eq!(amp_scaled, vec![[0.25], [0.4], [-0.2], [-0.1]]); } -#[cfg(feature = "signal")] #[test] fn test_offset_amp() { let foo = [[0.5], [0.9], [-0.4], [-0.2]]; diff --git a/tests/window.rs b/dasp_signal/tests/window.rs similarity index 83% rename from tests/window.rs rename to dasp_signal/tests/window.rs index b5e6815e..1258f222 100644 --- a/tests/window.rs +++ b/dasp_signal/tests/window.rs @@ -1,14 +1,12 @@ -extern crate sample; +#![cfg(feature = "window")] -#[cfg(feature = "frame")] -use sample::frame::Frame; -#[cfg(feature = "window")] -use sample::window::Windower; +use dasp_frame::Frame; +use dasp_signal::window::{self, Windower}; -#[cfg(feature = "window")] +#[cfg(feature = "window-hanning")] #[test] fn test_window_at_phase() { - let window = sample::window::hanning::<[f64; 1]>(9); + let window = window::hanning::<[f64; 1]>(9); let expected = [ 0.0, 0.1464, @@ -25,9 +23,8 @@ fn test_window_at_phase() { println!("Expected: {}\t\tFound: {}", e, r[0]); assert!((r[0] - e).abs() < 0.001); } - } -#[cfg(feature = "window")] + #[test] fn test_windower() { let data = [[0.1f64], [0.1], [0.2], [0.2], [0.3], [0.3], [0.4], [0.4]]; @@ -51,7 +48,7 @@ fn test_windower() { } } -#[cfg(feature = "window")] +#[cfg(feature = "window-hanning")] #[test] fn test_window_size() { let v = [[1f32; 1]; 16]; diff --git a/dasp_slice/Cargo.toml b/dasp_slice/Cargo.toml new file mode 100644 index 00000000..9faf9825 --- /dev/null +++ b/dasp_slice/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dasp_slice" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_frame = { path = "../dasp_frame", default-features = false } + +[features] +default = ["std"] +boxed = [] +std = ["dasp_sample/std", "dasp_frame/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_slice/src/boxed.rs b/dasp_slice/src/boxed.rs new file mode 100644 index 00000000..24df83dc --- /dev/null +++ b/dasp_slice/src/boxed.rs @@ -0,0 +1,235 @@ +//! Items related to boxed-slice conversions. + +#[cfg(not(feature = "std"))] +extern crate alloc; + +use dasp_frame::Frame; +use dasp_sample::Sample; + +#[cfg(not(feature = "std"))] +pub type Box = alloc::boxed::Box; +#[cfg(feature = "std")] +pub type Box = std::boxed::Box; + +// Traits +// ---------------------------------------------------------------------------- + +/// For converting a boxed slice of `Sample`s to a boxed slice of `Frame`s. +pub trait FromBoxedSampleSlice: Sized +where + S: Sample, +{ + fn from_boxed_sample_slice(slice: Box<[S]>) -> Option; +} + +/// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. +pub trait FromBoxedFrameSlice +where + F: Frame, +{ + fn from_boxed_frame_slice(slice: Box<[F]>) -> Self; +} + +/// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. +pub trait ToBoxedSampleSlice +where + S: Sample, +{ + fn to_boxed_sample_slice(self) -> Box<[S]>; +} + +/// For converting from a boxed slice of `Sample`s to a boxed slice of `Frame`s. +pub trait ToBoxedFrameSlice +where + F: Frame, +{ + fn to_boxed_frame_slice(self) -> Option>; +} + +/// For converting to and from a boxed slice of `Sample`s. +pub trait DuplexBoxedSampleSlice + : FromBoxedSampleSlice + ToBoxedSampleSlice +where + S: Sample +{ +} + +/// For converting to and from a boxed slice of `Frame`s. +pub trait DuplexBoxedFrameSlice + : FromBoxedFrameSlice + ToBoxedFrameSlice +where + F: Frame +{ +} + +/// For converting to and from a boxed slice of `Sample`s of type `S` and a slice of `Frame`s of +/// type `F`. +pub trait DuplexBoxedSlice + : DuplexBoxedSampleSlice + DuplexBoxedFrameSlice +where + S: Sample, + F: Frame +{ +} + +// Implementations +// ---------------------------------------------------------------------------- + +impl FromBoxedSampleSlice for Box<[S]> +where + S: Sample, +{ + #[inline] + fn from_boxed_sample_slice(slice: Box<[S]>) -> Option { + Some(slice) + } +} + +impl FromBoxedFrameSlice for Box<[F]> +where + F: Frame, +{ + #[inline] + fn from_boxed_frame_slice(slice: Box<[F]>) -> Self { + slice + } +} + +impl ToBoxedSampleSlice for Box<[S]> +where + S: Sample, +{ + #[inline] + fn to_boxed_sample_slice(self) -> Box<[S]> { + self + } +} + +impl ToBoxedFrameSlice for Box<[F]> +where + F: Frame, +{ + #[inline] + fn to_boxed_frame_slice(self) -> Option> { + Some(self) + } +} + +impl DuplexBoxedSampleSlice for T +where + S: Sample, + T: FromBoxedSampleSlice + ToBoxedSampleSlice, +{ +} + +impl DuplexBoxedFrameSlice for T +where + F: Frame, + T: FromBoxedFrameSlice + ToBoxedFrameSlice, +{ +} + +impl DuplexBoxedSlice for T +where + S: Sample, + F: Frame, + T: DuplexBoxedSampleSlice + DuplexBoxedFrameSlice, +{ +} + +// Free Functions +// ---------------------------------------------------------------------------- + +/// Converts the given boxed slice into a boxed slice of `Sample`s. +/// +/// This is a convenience function that wraps the `ToBoxedSampleSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = vec![[0.0, 0.5], [0.0, -0.5]].into_boxed_slice(); +/// let bar = dasp_slice::to_boxed_sample_slice(foo); +/// assert_eq!(bar.into_vec(), vec![0.0, 0.5, 0.0, -0.5]); +/// } +/// ``` +pub fn to_boxed_sample_slice(slice: T) -> Box<[S]> +where + S: Sample, + T: ToBoxedSampleSlice, +{ + slice.to_boxed_sample_slice() +} + +/// Converts the given boxed slice into a boxed slice of `Frame`s. +/// +/// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number +/// of samples in the given slice. +/// +/// This is a convenience function that wraps the `ToBoxedFrameSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = vec![0.0, 0.5, 0.0, -0.5].into_boxed_slice(); +/// let bar: Box<[[f32; 2]]> = dasp_slice::to_boxed_frame_slice(foo).unwrap(); +/// assert_eq!(bar.into_vec(), vec![[0.0, 0.5], [0.0, -0.5]]); +/// +/// let foo = vec![0.0, 0.5, 0.0].into_boxed_slice(); +/// let bar = dasp_slice::to_boxed_frame_slice(foo); +/// assert_eq!(bar, None::>); +/// } +/// ``` +pub fn to_boxed_frame_slice(slice: T) -> Option> +where + F: Frame, + T: ToBoxedFrameSlice, +{ + slice.to_boxed_frame_slice() +} + +/// Converts the given boxed slice of `Sample`s into some slice `T`. +/// +/// Returns `None` if the number of channels in a single frame is not a multiple of the number of +/// samples in the given slice. +/// +/// This is a convenience function that wraps the `FromBoxedSampleSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = vec![0.0, 0.5, 0.0, -0.5].into_boxed_slice(); +/// let bar: Box<[[f32; 2]]> = dasp_slice::from_boxed_sample_slice(foo).unwrap(); +/// assert_eq!(bar.into_vec(), vec![[0.0, 0.5], [0.0, -0.5]]); +/// } +/// ``` +pub fn from_boxed_sample_slice(slice: Box<[S]>) -> Option +where + S: Sample, + T: FromBoxedSampleSlice, +{ + T::from_boxed_sample_slice(slice) +} + +/// Converts the given boxed slice of `Frame`s into some slice `T`. +/// +/// This is a convenience function that wraps the `FromBoxedFrameSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = vec![[0.0, 0.5], [0.0, -0.5]].into_boxed_slice(); +/// let bar: Box<[f32]> = dasp_slice::from_boxed_frame_slice(foo); +/// assert_eq!(bar.into_vec(), vec![0.0, 0.5, 0.0, -0.5]); +/// } +/// ``` +pub fn from_boxed_frame_slice(slice: Box<[F]>) -> T +where + F: Frame, + T: FromBoxedFrameSlice, +{ + T::from_boxed_frame_slice(slice) +} diff --git a/dasp_slice/src/frame/fixed_size_array.rs b/dasp_slice/src/frame/fixed_size_array.rs new file mode 100644 index 00000000..d5a82b72 --- /dev/null +++ b/dasp_slice/src/frame/fixed_size_array.rs @@ -0,0 +1,210 @@ +//! Implementations of the slice conversion traits for slices of fixed-size-array frames. + +use crate::{ + FromFrameSlice, FromFrameSliceMut, FromSampleSlice, FromSampleSliceMut, ToFrameSlice, + ToFrameSliceMut, ToSampleSlice, ToSampleSliceMut, +}; +#[cfg(feature = "boxed")] +use crate::boxed::{ + Box, FromBoxedFrameSlice, FromBoxedSampleSlice, ToBoxedFrameSlice, ToBoxedSampleSlice, +}; +use dasp_frame::Frame; +use dasp_sample::Sample; + +/// A macro for implementing all audio slice conversion traits for each fixed-size array. +macro_rules! impl_from_slice_conversions { + ($($N:expr)*) => { + $( + impl<'a, S> FromSampleSlice<'a, S> for &'a [[S; $N]] + where + S: Sample, + [S; $N]: Frame, + { + #[inline] + fn from_sample_slice(slice: &'a [S]) -> Option { + let len = slice.len(); + if len % $N == 0 { + let new_len = len / $N; + let ptr = slice.as_ptr() as *const _; + let new_slice = unsafe { + core::slice::from_raw_parts(ptr, new_len) + }; + Some(new_slice) + } else { + None + } + } + } + + impl<'a, S> FromSampleSliceMut<'a, S> for &'a mut [[S; $N]] + where + S: Sample, + [S; $N]: Frame, + { + #[inline] + fn from_sample_slice_mut(slice: &'a mut [S]) -> Option { + let len = slice.len(); + if len % $N == 0 { + let new_len = len / $N; + let ptr = slice.as_ptr() as *mut _; + let new_slice = unsafe { + core::slice::from_raw_parts_mut(ptr, new_len) + }; + Some(new_slice) + } else { + None + } + } + } + + impl<'a, S> FromFrameSlice<'a, [S; $N]> for &'a [S] + where + [S; $N]: Frame, + { + #[inline] + fn from_frame_slice(slice: &'a [[S; $N]]) -> Self { + let new_len = slice.len() * $N; + let ptr = slice.as_ptr() as *const _; + unsafe { + core::slice::from_raw_parts(ptr, new_len) + } + } + } + + impl<'a, S> FromFrameSliceMut<'a, [S; $N]> for &'a mut [S] + where + [S; $N]: Frame, + { + #[inline] + fn from_frame_slice_mut(slice: &'a mut [[S; $N]]) -> Self { + let new_len = slice.len() * $N; + let ptr = slice.as_ptr() as *mut _; + unsafe { + core::slice::from_raw_parts_mut(ptr, new_len) + } + } + } + + impl<'a, S> ToSampleSlice<'a, S> for &'a [[S; $N]] + where + S: Sample, + { + #[inline] + fn to_sample_slice(self) -> &'a [S] { + FromFrameSlice::from_frame_slice(self) + } + } + + impl<'a, S> ToSampleSliceMut<'a, S> for &'a mut [[S; $N]] + where + S: Sample, + { + #[inline] + fn to_sample_slice_mut(self) -> &'a mut [S] { + FromFrameSliceMut::from_frame_slice_mut(self) + } + } + + impl<'a, S> ToFrameSlice<'a, [S; $N]> for &'a [S] + where + S: Sample, + [S; $N]: Frame, + { + #[inline] + fn to_frame_slice(self) -> Option<&'a [[S; $N]]> { + FromSampleSlice::from_sample_slice(self) + } + } + + impl<'a, S> ToFrameSliceMut<'a, [S; $N]> for &'a mut [S] + where + S: Sample, + [S; $N]: Frame, + { + #[inline] + fn to_frame_slice_mut(self) -> Option<&'a mut [[S; $N]]> { + FromSampleSliceMut::from_sample_slice_mut(self) + } + } + + #[cfg(feature = "boxed")] + impl FromBoxedSampleSlice for Box<[[S; $N]]> + where + S: Sample, + [S; $N]: Frame, + { + #[inline] + fn from_boxed_sample_slice(mut slice: Box<[S]>) -> Option { + // First, we need a raw pointer to the slice and to make sure that the `Box` is + // forgotten so that our slice does not get deallocated. + let len = slice.len(); + let slice_ptr = &mut slice as &mut [S] as *mut [S]; + core::mem::forget(slice); + let sample_slice = unsafe { + core::slice::from_raw_parts_mut((*slice_ptr).as_mut_ptr(), len) + }; + + // Convert to our frame slice if possible. + let frame_slice = match <&mut [[S; $N]]>::from_sample_slice_mut(sample_slice) { + Some(slice) => slice, + None => return None, + }; + let ptr = frame_slice as *mut [[S; $N]]; + + // Take ownership over the slice again before returning it. + let new_slice = unsafe { + Box::from_raw(ptr) + }; + + Some(new_slice) + } + } + + #[cfg(feature = "boxed")] + impl FromBoxedFrameSlice<[S; $N]> for Box<[S]> + where + [S; $N]: Frame, + { + #[inline] + fn from_boxed_frame_slice(mut slice: Box<[[S; $N]]>) -> Self { + let new_len = slice.len() * $N; + let frame_slice_ptr = &mut slice as &mut [[S; $N]] as *mut [[S; $N]]; + core::mem::forget(slice); + let sample_slice_ptr = frame_slice_ptr as *mut [S]; + unsafe { + let ptr = (*sample_slice_ptr).as_mut_ptr(); + let sample_slice = core::slice::from_raw_parts_mut(ptr, new_len); + Box::from_raw(sample_slice as *mut _) + } + } + } + + #[cfg(feature = "boxed")] + impl ToBoxedSampleSlice for Box<[[S; $N]]> + where + S: Sample, + { + #[inline] + fn to_boxed_sample_slice(self) -> Box<[S]> { + FromBoxedFrameSlice::from_boxed_frame_slice(self) + } + } + + #[cfg(feature = "boxed")] + impl ToBoxedFrameSlice<[S; $N]> for Box<[S]> + where + S: Sample, + [S; $N]: Frame, + { + #[inline] + fn to_boxed_frame_slice(self) -> Option> { + FromBoxedSampleSlice::from_boxed_sample_slice(self) + } + } + )* + }; +} + +impl_from_slice_conversions! { + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 +} diff --git a/dasp_slice/src/frame/mod.rs b/dasp_slice/src/frame/mod.rs new file mode 100644 index 00000000..d6578fed --- /dev/null +++ b/dasp_slice/src/frame/mod.rs @@ -0,0 +1,204 @@ +use dasp_frame::Frame; + +mod fixed_size_array; + +/// For converting from a slice of `Frame`s to a slice of `Sample`s. +pub trait FromFrameSlice<'a, F> +where + F: Frame, +{ + fn from_frame_slice(slice: &'a [F]) -> Self; +} + +/// For converting from a slice of `Frame`s to a slice of `Sample`s. +pub trait FromFrameSliceMut<'a, F> +where + F: Frame, +{ + fn from_frame_slice_mut(slice: &'a mut [F]) -> Self; +} + +/// For converting from a slice of `Sample`s to a slice of `Frame`s. +pub trait ToFrameSlice<'a, F> +where + F: Frame, +{ + fn to_frame_slice(self) -> Option<&'a [F]>; +} + +/// For converting from a mutable slice of `Sample`s to a mutable slice of `Frame`s. +pub trait ToFrameSliceMut<'a, F> +where + F: Frame, +{ + fn to_frame_slice_mut(self) -> Option<&'a mut [F]>; +} + +/// For converting to and from a slice of `Frame`s. +pub trait DuplexFrameSlice<'a, F>: FromFrameSlice<'a, F> + ToFrameSlice<'a, F> +where + F: Frame +{ +} + +/// For converting to and from a mutable slice of `Frame`s. +pub trait DuplexFrameSliceMut<'a, F> + : FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F> +where + F: Frame +{ +} + + +impl<'a, F> FromFrameSlice<'a, F> for &'a [F] +where + F: Frame, +{ + #[inline] + fn from_frame_slice(slice: &'a [F]) -> Self { + slice + } +} + +impl<'a, F> FromFrameSliceMut<'a, F> for &'a mut [F] +where + F: Frame, +{ + #[inline] + fn from_frame_slice_mut(slice: &'a mut [F]) -> Self { + slice + } +} + +impl<'a, F> ToFrameSlice<'a, F> for &'a [F] +where + F: Frame, +{ + #[inline] + fn to_frame_slice(self) -> Option<&'a [F]> { + Some(self) + } +} + +impl<'a, F> ToFrameSliceMut<'a, F> for &'a mut [F] +where + F: Frame, +{ + #[inline] + fn to_frame_slice_mut(self) -> Option<&'a mut [F]> { + Some(self) + } +} + +impl<'a, F, T> DuplexFrameSlice<'a, F> for T +where + F: Frame, + T: FromFrameSlice<'a, F> + ToFrameSlice<'a, F>, +{ +} + +impl<'a, F, T> DuplexFrameSliceMut<'a, F> for T +where + F: Frame, + T: FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F>, +{ +} + +/// Converts the given slice into a slice of `Frame`s. +/// +/// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number +/// of samples in the given slice. +/// +/// This is a convenience function that wraps the `ToFrameSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &[0.0, 0.5, 0.0, -0.5][..]; +/// let bar = dasp_slice::to_frame_slice(foo); +/// assert_eq!(bar, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); +/// +/// let foo = &[0.0, 0.5, 0.0][..]; +/// let bar = dasp_slice::to_frame_slice(foo); +/// assert_eq!(bar, None::<&[[f32; 2]]>); +/// } +/// ``` +pub fn to_frame_slice<'a, T, F>(slice: T) -> Option<&'a [F]> +where + F: Frame, + T: ToFrameSlice<'a, F>, +{ + slice.to_frame_slice() +} + + +/// Converts the given mutable slice into a mutable slice of `Frame`s. +/// +/// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number +/// of samples in the given slice. +/// +/// This is a convenience function that wraps the `ToFrameSliceMut` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &mut [0.0, 0.5, 0.0, -0.5][..]; +/// let bar = dasp_slice::to_frame_slice_mut(foo); +/// assert_eq!(bar, Some(&mut [[0.0, 0.5], [0.0, -0.5]][..])); +/// +/// let foo = &mut [0.0, 0.5, 0.0][..]; +/// let bar = dasp_slice::to_frame_slice_mut(foo); +/// assert_eq!(bar, None::<&mut [[f32; 2]]>); +/// } +/// ``` +pub fn to_frame_slice_mut<'a, T, F>(slice: T) -> Option<&'a mut [F]> +where + F: Frame, + T: ToFrameSliceMut<'a, F>, +{ + slice.to_frame_slice_mut() +} + +/// Converts the given slice of `Frame`s into some slice `T`. +/// +/// This is a convenience function that wraps the `FromFrameSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &[[0.0, 0.5], [0.0, -0.5]][..]; +/// let bar: &[f32] = dasp_slice::from_frame_slice(foo); +/// assert_eq!(bar, &[0.0, 0.5, 0.0, -0.5][..]); +/// } +/// ``` +pub fn from_frame_slice<'a, T, F>(slice: &'a [F]) -> T +where + F: Frame, + T: FromFrameSlice<'a, F>, +{ + T::from_frame_slice(slice) +} + +/// Converts the given slice of mutable `Frame`s into some mutable slice `T`. +/// +/// This is a convenience function that wraps the `FromFrameSliceMut` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &mut [[0.0, 0.5], [0.0, -0.5]][..]; +/// let bar: &mut [f32] = dasp_slice::from_frame_slice_mut(foo); +/// assert_eq!(bar, &mut [0.0, 0.5, 0.0, -0.5][..]); +/// } +/// ``` +pub fn from_frame_slice_mut<'a, T, F>(slice: &'a mut [F]) -> T +where + F: Frame, + T: FromFrameSliceMut<'a, F>, +{ + T::from_frame_slice_mut(slice) +} diff --git a/dasp_slice/src/lib.rs b/dasp_slice/src/lib.rs new file mode 100644 index 00000000..91d1180c --- /dev/null +++ b/dasp_slice/src/lib.rs @@ -0,0 +1,351 @@ +//! For working with slices of PCM audio data. +//! +//! Items related to conversion between slices of frames and slices of samples, particularly useful +//! for working with interleaved data. + +#![cfg_attr(not(feature = "std"), no_std)] + +use dasp_frame::Frame; +use dasp_sample::Sample; + +#[cfg(feature = "boxed")] +pub use boxed::{ + FromBoxedSampleSlice, FromBoxedFrameSlice, ToBoxedSampleSlice, ToBoxedFrameSlice, + DuplexBoxedSampleSlice, DuplexBoxedFrameSlice, DuplexBoxedSlice, to_boxed_sample_slice, + to_boxed_frame_slice, from_boxed_sample_slice, from_boxed_frame_slice, +}; + +pub use frame::{ + from_frame_slice, from_frame_slice_mut, to_frame_slice, to_frame_slice_mut, DuplexFrameSlice, + DuplexFrameSliceMut, FromFrameSlice, FromFrameSliceMut, ToFrameSlice, ToFrameSliceMut, +}; + +#[cfg(feature = "boxed")] +pub mod boxed; + +mod frame; + +// Slice Conversion Traits +// ---------------------------------------------------------------------------- + +/// For converting from a slice of `Sample`s to a slice of `Frame`s. +pub trait FromSampleSlice<'a, S>: Sized +where + S: Sample, +{ + fn from_sample_slice(slice: &'a [S]) -> Option; +} + +/// For converting from a mutable slice of `Sample`s to a mutable slice of `Frame`s. +pub trait FromSampleSliceMut<'a, S>: Sized +where + S: Sample, +{ + fn from_sample_slice_mut(slice: &'a mut [S]) -> Option; +} + +/// For converting from a slice of `Frame`s to a slice of `Sample`s. +pub trait ToSampleSlice<'a, S> +where + S: Sample, +{ + fn to_sample_slice(self) -> &'a [S]; +} + +/// For converting from a mutable slice of `Frame`s to a mutable slice of `Sample`s. +pub trait ToSampleSliceMut<'a, S> +where + S: Sample, +{ + fn to_sample_slice_mut(self) -> &'a mut [S]; +} + +/// For converting to and from a slice of `Sample`s. +pub trait DuplexSampleSlice<'a, S>: FromSampleSlice<'a, S> + ToSampleSlice<'a, S> +where + S: Sample, +{ +} + +/// For converting to and from a mutable slice of `Sample`s. +pub trait DuplexSampleSliceMut<'a, S>: FromSampleSliceMut<'a, S> + ToSampleSliceMut<'a, S> +where + S: Sample, +{ +} + +/// For converting to and from a slice of `Sample`s of type `S` and a slice of `Frame`s of type +/// `F`. +pub trait DuplexSlice<'a, S, F>: DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F> +where + S: Sample, + F: Frame, +{ +} + +/// For converting to and from a mutable slice of `Sample`s of type `S` and a slice of `Frame`s of +/// type `F`. +pub trait DuplexSliceMut<'a, S, F>: + DuplexSampleSliceMut<'a, S> + DuplexFrameSliceMut<'a, F> +where + S: Sample, + F: Frame, +{ +} + +// Slice Conversion Trait Implementations +// ---------------------------------------------------------------------------- + +impl<'a, S> FromSampleSlice<'a, S> for &'a [S] +where + S: Sample, +{ + #[inline] + fn from_sample_slice(slice: &'a [S]) -> Option { + Some(slice) + } +} + +impl<'a, S> FromSampleSliceMut<'a, S> for &'a mut [S] +where + S: Sample, +{ + #[inline] + fn from_sample_slice_mut(slice: &'a mut [S]) -> Option { + Some(slice) + } +} + +impl<'a, S> ToSampleSlice<'a, S> for &'a [S] +where + S: Sample, +{ + #[inline] + fn to_sample_slice(self) -> &'a [S] { + self + } +} + +impl<'a, S> ToSampleSliceMut<'a, S> for &'a mut [S] +where + S: Sample, +{ + #[inline] + fn to_sample_slice_mut(self) -> &'a mut [S] { + self + } +} + +impl<'a, S, T> DuplexSampleSlice<'a, S> for T +where + S: Sample, + T: FromSampleSlice<'a, S> + ToSampleSlice<'a, S>, +{ +} + +impl<'a, S, T> DuplexSampleSliceMut<'a, S> for T +where + S: Sample, + T: FromSampleSliceMut<'a, S> + ToSampleSliceMut<'a, S>, +{ +} + +impl<'a, S, F, T> DuplexSlice<'a, S, F> for T +where + S: Sample, + F: Frame, + T: DuplexSampleSlice<'a, S> + DuplexFrameSlice<'a, F>, +{ +} + +impl<'a, S, F, T> DuplexSliceMut<'a, S, F> for T +where + S: Sample, + F: Frame, + T: DuplexSampleSliceMut<'a, S> + DuplexFrameSliceMut<'a, F>, +{ +} + +// Conversion Functions +// ---------------------------------------------------------------------------- + +/// Converts the given slice into a slice of `Sample`s. +/// +/// This is a convenience function that wraps the `ToSampleSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &[[0.0, 0.5], [0.0, -0.5]][..]; +/// let bar = dasp_slice::to_sample_slice(foo); +/// assert_eq!(bar, &[0.0, 0.5, 0.0, -0.5][..]); +/// } +/// ``` +pub fn to_sample_slice<'a, T, S>(slice: T) -> &'a [S] +where + S: Sample, + T: ToSampleSlice<'a, S>, +{ + slice.to_sample_slice() +} + +/// Converts the given mutable slice of `Frame`s into a mutable slice of `Sample`s. +/// +/// This is a convenience function that wraps the `ToSampleSliceMut` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &mut [[0.0, 0.5], [0.0, -0.5]][..]; +/// let bar = dasp_slice::to_sample_slice_mut(foo); +/// assert_eq!(bar, &mut [0.0, 0.5, 0.0, -0.5][..]); +/// } +/// ``` +pub fn to_sample_slice_mut<'a, T, S>(slice: T) -> &'a mut [S] +where + S: Sample, + T: ToSampleSliceMut<'a, S>, +{ + slice.to_sample_slice_mut() +} + +/// Converts the given slice of `Sample`s into some slice `T`. +/// +/// Returns `None` if the number of channels in a single frame is not a multiple of the number of +/// samples in the given slice. +/// +/// This is a convenience function that wraps the `FromSampleSlice` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &[0.0, 0.5, 0.0, -0.5][..]; +/// let bar: Option<&_> = dasp_slice::from_sample_slice(foo); +/// assert_eq!(bar, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); +/// } +/// ``` +pub fn from_sample_slice<'a, T, S>(slice: &'a [S]) -> Option +where + S: Sample, + T: FromSampleSlice<'a, S>, +{ + T::from_sample_slice(slice) +} + +/// Converts the given mutable slice of `Sample`s into some mutable slice `T`. +/// +/// Returns `None` if the number of channels in a single frame is not a multiple of the number of +/// samples in the given slice. +/// +/// This is a convenience function that wraps the `FromSampleSliceMut` trait. +/// +/// # Examples +/// +/// ``` +/// fn main() { +/// let foo = &mut [0.0, 0.5, 0.0, -0.5][..]; +/// let bar: Option<&mut _> = dasp_slice::from_sample_slice_mut(foo); +/// assert_eq!(bar, Some(&mut [[0.0, 0.5], [0.0, -0.5]][..])); +/// } +/// ``` +pub fn from_sample_slice_mut<'a, T, S>(slice: &'a mut [S]) -> Option +where + S: Sample, + T: FromSampleSliceMut<'a, S>, +{ + T::from_sample_slice_mut(slice) +} + +///// Utility Functions + +/// Mutate every element in the slice with the given function. +#[inline] +pub fn map_in_place(a: &mut [F], mut map: M) +where + M: FnMut(F) -> F, + F: Frame, +{ + for f in a { + *f = map(*f); + } +} + +/// Sets the slice of frames at the associated `Sample`'s equilibrium value. +#[inline] +pub fn equilibrium(a: &mut [F]) +where + F: Frame, +{ + map_in_place(a, |_| F::equilibrium()) +} + +/// Mutate every frame in slice `a` while reading from each frame in slice `b` in lock-step using +/// the given function. +/// +/// **Panics** if the length of `b` is not equal to the length of `a`. +#[inline] +pub fn zip_map_in_place(a: &mut [FA], b: &[FB], zip_map: M) +where + FA: Frame, + FB: Frame, + M: FnMut(FA, FB) -> FA, +{ + assert_eq!(a.len(), b.len()); + + // We've asserted that the lengths are equal so we don't need bounds checking. + unsafe { + zip_map_in_place_unchecked(a, b, zip_map); + } +} + +/// Writes every sample in slice `b` to slice `a`. +/// +/// **Panics** if the slice lengths differ. +#[inline] +pub fn write(a: &mut [F], b: &[F]) +where + F: Frame, +{ + zip_map_in_place(a, b, |_, b| b); +} + +/// Adds every sample in slice `b` to every sample in slice `a` respectively. +#[inline] +pub fn add_in_place(a: &mut [FA], b: &[FB]) +where + FA: Frame, + FB: Frame::Signed, NumChannels = FA::NumChannels>, +{ + zip_map_in_place(a, b, |a, b| a.add_amp(b)); +} + +/// Scale the amplitude of each frame in `b` by `amp_per_channel` before summing it onto `a`. +#[inline] +pub fn add_in_place_with_amp_per_channel(a: &mut [FA], b: &[FB], amp_per_channel: A) +where + FA: Frame, + FB: Frame::Signed, NumChannels = FA::NumChannels>, + A: Frame::Float, NumChannels = FB::NumChannels>, +{ + zip_map_in_place(a, b, |af, bf| af.add_amp(bf.mul_amp(amp_per_channel))); +} + +/// Mutate every element in slice `a` while reading from each element from slice `b` in lock-step +/// using the given function. +/// +/// This function does not check that the slices are the same length and will crash horrifically on +/// index-out-of-bounds. +#[inline] +unsafe fn zip_map_in_place_unchecked(a: &mut [FA], b: &[FB], mut zip_map: M) +where + FA: Frame, + FB: Frame, + M: FnMut(FA, FB) -> FA, +{ + for i in 0..a.len() { + *a.get_unchecked_mut(i) = zip_map(*a.get_unchecked(i), *b.get_unchecked(i)); + } +} diff --git a/tests/slice.rs b/dasp_slice/tests/slice.rs similarity index 61% rename from tests/slice.rs rename to dasp_slice/tests/slice.rs index 1980a4b2..e9427087 100644 --- a/tests/slice.rs +++ b/dasp_slice/tests/slice.rs @@ -1,57 +1,49 @@ -extern crate sample; - -#[cfg(feature="slice")] #[test] fn test_add_slice() { let mut a = [[-0.5]; 32]; let b = [[0.5]; 32]; - sample::slice::add_in_place(&mut a, &b); + dasp_slice::add_in_place(&mut a, &b); assert_eq!([[0.0]; 32], a); } -#[cfg(feature="slice")] #[test] #[should_panic] fn test_add_slice_panic() { let mut a = [[0.5]; 31]; let b = [[0.5]; 32]; - sample::slice::add_in_place(&mut a, &b); + dasp_slice::add_in_place(&mut a, &b); } -#[cfg(feature="slice")] #[test] fn test_write_slice() { let mut a = [[0.0]; 32]; let b = [[1.0]; 32]; - sample::slice::write(&mut a, &b); + dasp_slice::write(&mut a, &b); assert_eq!([[1.0]; 32], a); } -#[cfg(feature="slice")] #[test] #[should_panic] fn test_write_slice_panic() { let mut a = [[0.0]; 31]; let b = [[1.0]; 32]; - sample::slice::write(&mut a, &b); + dasp_slice::write(&mut a, &b); } -#[cfg(feature="slice")] #[test] fn test_add_slice_with_amp_per_channel() { let mut a = [[0.5]; 32]; let b = [[1.0]; 32]; let amp = [0.5]; - sample::slice::add_in_place_with_amp_per_channel(&mut a, &b, amp); + dasp_slice::add_in_place_with_amp_per_channel(&mut a, &b, amp); assert_eq!([[1.0]; 32], a); } -#[cfg(feature="slice")] #[test] #[should_panic] fn test_add_slice_with_amp_per_channel_panic() { let mut a = [[0.5]; 31]; let b = [[1.0]; 32]; let amp = [0.5]; - sample::slice::add_in_place_with_amp_per_channel(&mut a, &b, amp); + dasp_slice::add_in_place_with_amp_per_channel(&mut a, &b, amp); } diff --git a/dasp_window/Cargo.toml b/dasp_window/Cargo.toml new file mode 100644 index 00000000..3be32054 --- /dev/null +++ b/dasp_window/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "dasp_window" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp_sample = { path = "../dasp_sample", default-features = false } + +[features] +default = ["std"] +hanning = [] +rectangle = [] +std = ["dasp_sample/std"] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_window/src/hanning/mod.rs b/dasp_window/src/hanning/mod.rs new file mode 100644 index 00000000..73620899 --- /dev/null +++ b/dasp_window/src/hanning/mod.rs @@ -0,0 +1,21 @@ +use crate::Window; +use dasp_sample::Sample; +use ops::f64::cos; + +mod ops; + +/// A type of window function, also known as the "raised cosine window". +/// +/// [Wiki entry](https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Hanning; + +impl Window for Hanning { + fn at_phase(phase: S) -> S { + const PI_2: f64 = core::f64::consts::PI * 2.0; + let v = phase.to_float_sample().to_sample() * PI_2; + (0.5 * (1.0 - cos(v))) + .to_sample::() + .to_sample::() + } +} diff --git a/dasp_window/src/hanning/ops.rs b/dasp_window/src/hanning/ops.rs new file mode 100644 index 00000000..55cdbb57 --- /dev/null +++ b/dasp_window/src/hanning/ops.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(dead_code)] + +pub mod f64 { + #[cfg(not(feature = "std"))] + pub fn cos(x: f64) -> f64 { + unsafe { core::intrinsics::cosf64(x) } + } + #[cfg(feature = "std")] + pub fn cos(x: f64) -> f64 { + x.cos() + } +} diff --git a/dasp_window/src/lib.rs b/dasp_window/src/lib.rs new file mode 100644 index 00000000..a8c17571 --- /dev/null +++ b/dasp_window/src/lib.rs @@ -0,0 +1,15 @@ +//! Module for windowing over a batch of Frames. Includes default Hanning and Rectangle window +//! types. + +use dasp_sample::Sample; + +#[cfg(feature = "hanning")] +pub mod hanning; +#[cfg(feature = "rectangle")] +pub mod rectangle; + +/// The window function used within a `Window`. +pub trait Window { + /// Returns the amplitude for the given phase, given as some `Sample` type. + fn at_phase(phase: S) -> S; +} diff --git a/dasp_window/src/rectangle.rs b/dasp_window/src/rectangle.rs new file mode 100644 index 00000000..68ac4c37 --- /dev/null +++ b/dasp_window/src/rectangle.rs @@ -0,0 +1,14 @@ +use crate::Window; + +use dasp_sample::{FloatSample, Sample}; + +/// The simplest window type, equivalent to replacing all but *N* values of data sequence by +/// zeroes, making it appear as though the waveform suddenly turns on and off. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Rectangle; + +impl Window for Rectangle { + fn at_phase(_phase: S) -> S { + ::identity().to_sample::() + } +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 00000000..1140f96d --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "examples" +version = "0.1.0" +authors = ["mitchmindtree "] +edition = "2018" + +[dependencies] +dasp = { path = "../dasp", features = ["slice", "interpolate", "interpolate-sinc", "ring_buffer", "signal"] } +find_folder = "0.3" +hound = "3" +portaudio = "0.7" + +[[example]] +name = "play_wav" +path = "play_wav.rs" +[[example]] +name = "resample" +path = "resample.rs" +[[example]] +name = "synth" +path = "synth.rs" +[[example]] +name = "test" +path = "test.rs" diff --git a/examples/play_wav.rs b/examples/play_wav.rs index dccd000a..d4461d7e 100644 --- a/examples/play_wav.rs +++ b/examples/play_wav.rs @@ -1,12 +1,7 @@ -extern crate find_folder; -extern crate hound; -extern crate portaudio as pa; -extern crate sample; +use dasp::signal::{self, Signal}; +use dasp::slice::ToFrameSliceMut; +use portaudio as pa; -#[cfg(feature = "signal")] -use sample::{signal, Signal, ToFrameSliceMut}; - -#[cfg(feature = "signal")] fn main() { // Find and load the wav. let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); @@ -52,8 +47,3 @@ fn main() { stream.stop().unwrap(); stream.close().unwrap(); } - -#[cfg(not(feature = "signal"))] -fn main() { - panic!("This example only works when compiled with the 'signal' feature."); -} diff --git a/examples/resample.rs b/examples/resample.rs index 500ec723..15c9ed37 100644 --- a/examples/resample.rs +++ b/examples/resample.rs @@ -1,15 +1,9 @@ // An example of using `sample` to efficiently perform decent quality sample rate conversion on a // WAV file entirely on the stack. -extern crate find_folder; -extern crate hound; -extern crate sample; - use hound::{WavReader, WavWriter}; -#[cfg(all(feature = "interpolate", feature = "ring_buffer", feature = "signal"))] -use sample::{interpolate, ring_buffer, signal, Sample, Signal}; +use dasp::{interpolate::sinc::Sinc, ring_buffer, signal, Sample, Signal}; -#[cfg(all(feature = "interpolate", feature = "ring_buffer", feature = "signal"))] fn main() { // Find and load the wav. let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); @@ -26,7 +20,7 @@ fn main() { // Convert the signal's sample rate using `Sinc` interpolation. let ring_buffer = ring_buffer::Fixed::from([[0.0]; 100]); - let sinc = interpolate::Sinc::new(ring_buffer); + let sinc = Sinc::new(ring_buffer); let new_signal = signal.from_hz_to_hz(sinc, spec.sample_rate as f64, target.sample_rate as f64); // Write the result to a new file. @@ -35,8 +29,3 @@ fn main() { writer.write_sample(frame[0].to_sample::()).unwrap(); } } - -#[cfg(not(all(feature = "interpolate", feature = "ring_buffer", feature = "signal")))] -fn main() { - panic!("This example only works when compiled with the features 'interpolate', 'ring_buffer' and 'signal'."); -} diff --git a/examples/synth.rs b/examples/synth.rs index 27dd27d7..f786604b 100644 --- a/examples/synth.rs +++ b/examples/synth.rs @@ -1,8 +1,7 @@ -extern crate portaudio as pa; -extern crate sample; +use portaudio as pa; -#[cfg(feature = "signal")] -use sample::{signal, Frame, Sample, Signal, ToFrameSliceMut}; +use dasp::{signal, Frame, Sample, Signal}; +use dasp::slice::ToFrameSliceMut; const FRAMES_PER_BUFFER: u32 = 512; const NUM_CHANNELS: i32 = 1; @@ -12,9 +11,7 @@ fn main() { run().unwrap(); } -#[cfg(feature = "signal")] fn run() -> Result<(), pa::Error> { - // Create a signal chain to play back 1 second of each oscillator at A4. let hz = signal::rate(SAMPLE_RATE).const_hz(440.0); let one_sec = SAMPLE_RATE as usize; @@ -28,12 +25,12 @@ fn run() -> Result<(), pa::Error> { .map(|f| f.map(|s| s.to_sample::() * 0.2)); // Initialise PortAudio. - let pa = try!(pa::PortAudio::new()); - let settings = try!(pa.default_output_stream_settings::( + let pa = pa::PortAudio::new()?; + let settings = pa.default_output_stream_settings::( NUM_CHANNELS, SAMPLE_RATE, FRAMES_PER_BUFFER, - )); + )?; // Define the callback which provides PortAudio the audio. let callback = move |pa::OutputStreamCallbackArgs { buffer, .. }| { @@ -47,20 +44,15 @@ fn run() -> Result<(), pa::Error> { pa::Continue }; - let mut stream = try!(pa.open_non_blocking_stream(settings, callback)); - try!(stream.start()); + let mut stream = pa.open_non_blocking_stream(settings, callback)?; + stream.start()?; while let Ok(true) = stream.is_active() { std::thread::sleep(std::time::Duration::from_millis(100)); } - try!(stream.stop()); - try!(stream.close()); + stream.stop()?; + stream.close()?; Ok(()) } - -#[cfg(not(feature = "signal"))] -fn run() -> Result<(), pa::Error> { - panic!("This example only works when compiled with the 'signal' feature."); -} diff --git a/examples/test.rs b/examples/test.rs index a879266d..34f140b8 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -1,9 +1,7 @@ //! A short example that converts an f64 sine wave to a few of the sample types available within //! the `Sample` crate, prints their values, and then converts them back to the original f64. -extern crate sample; - -use sample::Sample; +use dasp::Sample; /// An iterator that continually steps forward the phase for a signal by `0.03`. struct Iter { diff --git a/src/envelope/detect.rs b/src/envelope/detect.rs deleted file mode 100644 index ee643a94..00000000 --- a/src/envelope/detect.rs +++ /dev/null @@ -1,195 +0,0 @@ -use {Frame, Sample}; -use core; -use peak; -use ring_buffer; -use rms; - -/// A type that can be used to detect the envelope of a signal. -#[derive(Clone, Debug)] -pub struct Detector -where - F: Frame, - D: Detect, -{ - last_env_frame: D::Output, - attack_gain: f32, - release_gain: f32, - detect: D, -} - -/// Types that may be used to detect an envelope over a signal. -pub trait Detect -where - F: Frame, -{ - /// The result of detection. - type Output: Frame; - /// Given some frame, return the detected envelope over each channel. - fn detect(&mut self, frame: F) -> Self::Output; -} - -/// A `Peak` detector, generic over the `FullWave`, `PositiveHalfWave`, `NegativeHalfWave` -/// rectifiers. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Peak { - rectifier: R, -} - -impl From for Peak { - fn from(rectifier: R) -> Self { - Peak { rectifier: rectifier } - } -} - -impl Peak { - /// A signal rectifier that produces the absolute amplitude from samples. - pub fn full_wave() -> Self { - peak::FullWave.into() - } -} - -impl Peak { - /// A signal rectifier that produces only the positive samples. - pub fn positive_half_wave() -> Self { - peak::PositiveHalfWave.into() - } -} - -impl Peak { - /// A signal rectifier that produces only the negative samples. - pub fn negative_half_wave() -> Self { - peak::NegativeHalfWave.into() - } -} - -impl Detect for Peak -where - F: Frame, - R: peak::Rectifier, -{ - type Output = R::Output; - fn detect(&mut self, frame: F) -> Self::Output { - self.rectifier.rectify(frame) - } -} - -impl Detect for rms::Rms -where - F: Frame, - S: ring_buffer::Slice - + ring_buffer::SliceMut, -{ - type Output = F::Float; - fn detect(&mut self, frame: F) -> Self::Output { - self.next(frame) - } -} - -fn calc_gain(n_frames: f32) -> f32 { - if n_frames == 0.0 { - 0.0 - } else { - ::ops::f32::powf32(core::f32::consts::E, -1.0 / n_frames) - } -} - -impl Detector> -where - F: Frame, - S: ring_buffer::Slice + ring_buffer::SliceMut, -{ -/// Construct a new **Rms** **Detector**. - pub fn rms(buffer: ring_buffer::Fixed, attack_frames: f32, release_frames: f32) -> Self { - let rms = rms::Rms::new(buffer); - Self::new(rms, attack_frames, release_frames) - } -} - -impl Detector> -where - F: Frame, - R: peak::Rectifier, -{ - /// Construct a new **Peak** **Detector** that uses the given rectifier. - pub fn peak_from_rectifier(rectifier: R, attack_frames: f32, release_frames: f32) -> Self { - let peak = rectifier.into(); - Self::new(peak, attack_frames, release_frames) - } -} - -impl Detector> -where - F: Frame, -{ - /// Construct a new full wave **Peak** **Detector**. - pub fn peak(attack_frames: f32, release_frames: f32) -> Self { - let peak = Peak::full_wave(); - Self::new(peak, attack_frames, release_frames) - } -} - -impl Detector> -where - F: Frame, -{ - /// Construct a new positive half wave **Peak** **Detector**. - pub fn peak_positive_half_wave(attack_frames: f32, release_frames: f32) -> Self { - let peak = Peak::positive_half_wave(); - Self::new(peak, attack_frames, release_frames) - } -} - -impl Detector> -where - F: Frame, -{ - /// Construct a new positive half wave **Peak** **Detector**. - pub fn peak_negative_half_wave(attack_frames: f32, release_frames: f32) -> Self { - let peak = Peak::negative_half_wave(); - Self::new(peak, attack_frames, release_frames) - } -} - -impl Detector -where - F: Frame, - D: Detect, -{ - fn new(detect: D, attack_frames: f32, release_frames: f32) -> Self { - Detector { - last_env_frame: D::Output::equilibrium(), - attack_gain: calc_gain(attack_frames), - release_gain: calc_gain(release_frames), - detect: detect, - } - } - - /// Set the **Detector**'s attack time as a number of frames. - pub fn set_attack_frames(&mut self, frames: f32) { - self.attack_gain = calc_gain(frames); - } - - /// Set the **Detector**'s release time as a number of frames. - pub fn set_release_frames(&mut self, frames: f32) { - self.release_gain = calc_gain(frames); - } - - /// Given the next input signal frame, detect and return the next envelope frame. - pub fn next(&mut self, frame: F) -> D::Output { - let Detector { - attack_gain, - release_gain, - ref mut detect, - ref mut last_env_frame, - } = *self; - - let detected_frame = detect.detect(frame); - let new_env_frame = last_env_frame.zip_map(detected_frame, |l, d| { - let gain = if l < d { attack_gain } else { release_gain }; - let diff = l.add_amp(-d.to_signed_sample()); - d.add_amp(diff.mul_amp(gain.to_sample()).to_sample()) - }); - *last_env_frame = new_env_frame; - new_env_frame - } -} diff --git a/src/envelope/mod.rs b/src/envelope/mod.rs deleted file mode 100644 index dd3cf9aa..00000000 --- a/src/envelope/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod detect; - -pub use self::detect::{Detect, Detector}; diff --git a/src/interpolate.rs b/src/interpolate.rs deleted file mode 100644 index 23d0efa9..00000000 --- a/src/interpolate.rs +++ /dev/null @@ -1,356 +0,0 @@ -//! The Interpolate module allows for conversion between various sample rates. - -use {Duplex, Frame, Sample, Signal}; -use core::f64::consts::PI; -use ring_buffer; -use ops::f64::{sin, cos}; - -/// An iterator that converts the rate at which frames are yielded from some given frame -/// Interpolator into a new type. -/// -/// Other names for `sample::interpolate::Converter` might include: -/// -/// - Sample rate converter -/// - {Up/Down}sampler -/// - Sample interpolater -/// - Sample decimator -/// -#[derive(Clone)] -pub struct Converter -where - S: Signal, - I: Interpolator, -{ - source: S, - interpolator: I, - interpolation_value: f64, - source_to_target_ratio: f64, -} - -/// Interpolator that just rounds off any values to the previous value from the source -pub struct Floor { - left: F, -} - -/// Interpolator that interpolates linearly between the previous value and the next value -pub struct Linear { - left: F, - right: F, -} - -/// Interpolator for sinc interpolation. -/// -/// Generally accepted as one of the better sample rate converters, although it uses significantly -/// more computation. -pub struct Sinc { - frames: ring_buffer::Fixed, - idx: usize, -} - -/// Types that can interpolate between two values. -/// -/// Implementations should keep track of the necessary data both before and after the current -/// frame. -pub trait Interpolator { - type Frame: Frame; - - /// Given a distance between [0.0 and 1.0) to the following sample, return the interpolated - /// value. - fn interpolate(&self, x: f64) -> Self::Frame; - - /// Called whenever the Interpolator value steps passed 1.0. - fn next_source_frame(&mut self, source_frame: Self::Frame); -} - -impl Converter -where - S: Signal, - I: Interpolator, -{ - /// Construct a new `Converter` from the source frames and the source and target sample rates - /// (in Hz). - #[inline] - pub fn from_hz_to_hz(source: S, interpolator: I, source_hz: f64, target_hz: f64) -> Self { - Self::scale_playback_hz(source, interpolator, source_hz / target_hz) - } - - /// Construct a new `Converter` from the source frames and the amount by which the current - /// ***playback*** **rate** (not sample rate) should be multiplied to reach the new playback - /// rate. - /// - /// For example, if our `source_frames` is a sine wave oscillating at a frequency of 2hz and - /// we wanted to convert it to a frequency of 3hz, the given `scale` should be `1.5`. - #[inline] - pub fn scale_playback_hz(source: S, interpolator: I, scale: f64) -> Self { - assert!( - scale > 0.0, - "We can't yield any frames at 0 times a second!" - ); - Converter { - source: source, - interpolator: interpolator, - interpolation_value: 0.0, - source_to_target_ratio: scale, - } - } - - /// Construct a new `Converter` from the source frames and the amount by which the current - /// ***sample*** **rate** (not playback rate) should be multiplied to reach the new sample - /// rate. - /// - /// If our `source_frames` are being sampled at a rate of 44_100hz and we want to - /// convert to a sample rate of 96_000hz, the given `scale` should be `96_000.0 / 44_100.0`. - /// - /// This is the same as calling `Converter::scale_playback_hz(source_frames, 1.0 / scale)`. - #[inline] - pub fn scale_sample_hz(source: S, interpolator: I, scale: f64) -> Self { - Self::scale_playback_hz(source, interpolator, 1.0 / scale) - } - - /// Update the `source_to_target_ratio` internally given the source and target hz. - /// - /// This method might be useful for changing the sample rate during playback. - #[inline] - pub fn set_hz_to_hz(&mut self, source_hz: f64, target_hz: f64) { - self.set_playback_hz_scale(source_hz / target_hz) - } - - /// Update the `source_to_target_ratio` internally given a new **playback rate** multiplier. - /// - /// This method is useful for dynamically changing rates. - #[inline] - pub fn set_playback_hz_scale(&mut self, scale: f64) { - self.source_to_target_ratio = scale; - } - - /// Update the `source_to_target_ratio` internally given a new **sample rate** multiplier. - /// - /// This method is useful for dynamically changing rates. - #[inline] - pub fn set_sample_hz_scale(&mut self, scale: f64) { - self.set_playback_hz_scale(1.0 / scale); - } - - /// Borrow the `source_frames` Interpolator from the `Converter`. - #[inline] - pub fn source(&self) -> &S { - &self.source - } - - /// Mutably borrow the `source_frames` Iterator from the `Converter`. - #[inline] - pub fn source_mut(&mut self) -> &mut S { - &mut self.source - } - - /// Drop `self` and return the internal `source_frames` Iterator. - #[inline] - pub fn into_source(self) -> S { - self.source - } -} - -impl Signal for Converter -where - S: Signal, - I: Interpolator, -{ - type Frame = S::Frame; - - fn next(&mut self) -> Self::Frame { - let Converter { - ref mut source, - ref mut interpolator, - ref mut interpolation_value, - source_to_target_ratio, - } = *self; - - // Advance frames - while *interpolation_value >= 1.0 { - interpolator.next_source_frame(source.next()); - *interpolation_value -= 1.0; - } - - let out = interpolator.interpolate(*interpolation_value); - *interpolation_value += source_to_target_ratio; - out - } - - fn is_exhausted(&self) -> bool { - self.source.is_exhausted() && self.interpolation_value >= 1.0 - } -} - -impl Floor { - /// Create a new Floor Interpolator. - pub fn new(left: F) -> Floor { - Floor { left: left } - } - - /// Consumes the first value from a given source in order to initialize itself. If the source - /// has no values at all, this will return None. - pub fn from_source(source: &mut S) -> Floor - where - F: Frame, - S: Signal, - { - let left = source.next(); - Floor { left: left } - } -} - -impl Linear { - /// Create a new Linear Interpolator. - pub fn new(left: F, right: F) -> Linear { - Linear { - left: left, - right: right, - } - } - - /// Consumes the first value from a given source to initialize itself. If the source has no - /// values, this will return None. - pub fn from_source(source: &mut S) -> Linear - where - F: Frame, - S: Signal, - { - let left = source.next(); - let right = source.next(); - Linear { - left: left, - right: right, - } - } -} - -impl Sinc { - /// Create a new **Sinc** interpolator with the given ring buffer. - /// - /// The given ring buffer should have a length twice that of the desired sinc interpolation - /// `depth`. - /// - /// The initial contents of the ring_buffer will act as padding for the interpolated signal. - /// - /// **panic!**s if the given ring buffer's length is not a multiple of `2`. - pub fn new(frames: ring_buffer::Fixed) -> Self - where - S: ring_buffer::SliceMut, - S::Element: Frame, - { - assert!(frames.len() % 2 == 0); - Sinc { - frames: frames, - idx: 0, - } - } - - fn depth(&self) -> usize - where - S: ring_buffer::Slice, - { - self.frames.len() / 2 - } -} - -impl Interpolator for Floor -where - F: Frame, - F::Sample: Duplex, -{ - type Frame = F; - - fn interpolate(&self, _x: f64) -> Self::Frame { - self.left - } - - fn next_source_frame(&mut self, source_frame: Self::Frame) { - self.left = source_frame; - } -} - -impl Interpolator for Linear -where - F: Frame, - F::Sample: Duplex, -{ - type Frame = F; - - /// Converts linearly from the previous value, using the next value to interpolate. It is - /// possible, although not advisable, to provide an x > 1.0 or < 0.0, but this will just - /// continue to be a linear ramp in one direction or another. - fn interpolate(&self, x: f64) -> Self::Frame { - self.left.zip_map(self.right, |l, r| { - let l_f = l.to_sample::(); - let r_f = r.to_sample::(); - let diff = r_f - l_f; - ((diff * x) + l_f).to_sample::<::Sample>() - }) - } - - fn next_source_frame(&mut self, source_frame: Self::Frame) { - self.left = self.right; - self.right = source_frame; - } -} - -impl Interpolator for Sinc -where - S: ring_buffer::SliceMut, - S::Element: Frame, - ::Sample: Duplex, -{ - type Frame = S::Element; - - /// Sinc interpolation - fn interpolate(&self, x: f64) -> Self::Frame { - let phil = x; - let phir = 1.0 - x; - let nl = self.idx; - let nr = self.idx + 1; - let depth = self.depth(); - - let rightmost = nl + depth; - let leftmost = nr as isize - depth as isize; - let max_depth = if rightmost >= self.frames.len() { - self.frames.len() - depth - } else if leftmost < 0 { - (depth as isize + leftmost) as usize - } else { - depth - }; - - (0..max_depth).fold(Self::Frame::equilibrium(), |mut v, n| { - v = { - let a = PI * (phil + n as f64); - let first = if a == 0.0 { 1.0 } else { sin(a) / a }; - let second = 0.5 + 0.5 * cos(a / (phil + max_depth as f64)); - v.zip_map(self.frames[nr - n], |vs, r_lag| { - vs.add_amp( - (first * second * r_lag.to_sample::()) - .to_sample::<::Sample>() - .to_signed_sample(), - ) - }) - }; - - let a = PI * (phir + n as f64); - let first = if a == 0.0 { 1.0 } else { sin(a) / a }; - let second = 0.5 + 0.5 * cos(a / (phir + max_depth as f64)); - v.zip_map(self.frames[nl + n], |vs, r_lag| { - vs.add_amp( - (first * second * r_lag.to_sample::()) - .to_sample::<::Sample>() - .to_signed_sample(), - ) - }) - }) - } - - fn next_source_frame(&mut self, source_frame: Self::Frame) { - let _old_frame = self.frames.push(source_frame); - if self.idx < self.depth() { - self.idx += 1; - } - } -} diff --git a/src/slice.rs b/src/slice.rs deleted file mode 100644 index 88f5baeb..00000000 --- a/src/slice.rs +++ /dev/null @@ -1,410 +0,0 @@ -//! This module provides various helper functions for performing operations on slices of frames. - -use {Box, Frame, Sample, ToSampleSlice, ToSampleSliceMut, ToBoxedSampleSlice, ToFrameSlice, - ToFrameSliceMut, ToBoxedFrameSlice, FromSampleSlice, FromSampleSliceMut, - FromBoxedSampleSlice, FromFrameSlice, FromFrameSliceMut, FromBoxedFrameSlice}; - - -///// Conversion Functions -///// -///// The following functions wrap the various DSP slice conversion traits for convenience. - - -/// Converts the given slice into a slice of `Frame`s. -/// -/// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number -/// of samples in the given slice. -/// -/// This is a convenience function that wraps the `ToFrameSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &[0.0, 0.5, 0.0, -0.5][..]; -/// let bar = sample::slice::to_frame_slice(foo); -/// assert_eq!(bar, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); -/// -/// let foo = &[0.0, 0.5, 0.0][..]; -/// let bar = sample::slice::to_frame_slice(foo); -/// assert_eq!(bar, None::<&[[f32; 2]]>); -/// } -/// ``` -pub fn to_frame_slice<'a, T, F>(slice: T) -> Option<&'a [F]> -where - F: Frame, - T: ToFrameSlice<'a, F>, -{ - slice.to_frame_slice() -} - -/// Converts the given mutable slice into a mutable slice of `Frame`s. -/// -/// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number -/// of samples in the given slice. -/// -/// This is a convenience function that wraps the `ToFrameSliceMut` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &mut [0.0, 0.5, 0.0, -0.5][..]; -/// let bar = sample::slice::to_frame_slice_mut(foo); -/// assert_eq!(bar, Some(&mut [[0.0, 0.5], [0.0, -0.5]][..])); -/// -/// let foo = &mut [0.0, 0.5, 0.0][..]; -/// let bar = sample::slice::to_frame_slice_mut(foo); -/// assert_eq!(bar, None::<&mut [[f32; 2]]>); -/// } -/// ``` -pub fn to_frame_slice_mut<'a, T, F>(slice: T) -> Option<&'a mut [F]> -where - F: Frame, - T: ToFrameSliceMut<'a, F>, -{ - slice.to_frame_slice_mut() -} - -/// Converts the given boxed slice into a boxed slice of `Frame`s. -/// -/// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number -/// of samples in the given slice. -/// -/// This is a convenience function that wraps the `ToBoxedFrameSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = vec![0.0, 0.5, 0.0, -0.5].into_boxed_slice(); -/// let bar: Box<[[f32; 2]]> = sample::slice::to_boxed_frame_slice(foo).unwrap(); -/// assert_eq!(bar.into_vec(), vec![[0.0, 0.5], [0.0, -0.5]]); -/// -/// let foo = vec![0.0, 0.5, 0.0].into_boxed_slice(); -/// let bar = sample::slice::to_boxed_frame_slice(foo); -/// assert_eq!(bar, None::>); -/// } -/// ``` -pub fn to_boxed_frame_slice(slice: T) -> Option> -where - F: Frame, - T: ToBoxedFrameSlice, -{ - slice.to_boxed_frame_slice() -} - -/// Converts the given slice into a slice of `Sample`s. -/// -/// This is a convenience function that wraps the `ToSampleSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &[[0.0, 0.5], [0.0, -0.5]][..]; -/// let bar = sample::slice::to_sample_slice(foo); -/// assert_eq!(bar, &[0.0, 0.5, 0.0, -0.5][..]); -/// } -/// ``` -pub fn to_sample_slice<'a, T, S>(slice: T) -> &'a [S] -where - S: Sample, - T: ToSampleSlice<'a, S>, -{ - slice.to_sample_slice() -} - -/// Converts the given mutable slice of `Frame`s into a mutable slice of `Sample`s. -/// -/// This is a convenience function that wraps the `ToSampleSliceMut` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &mut [[0.0, 0.5], [0.0, -0.5]][..]; -/// let bar = sample::slice::to_sample_slice_mut(foo); -/// assert_eq!(bar, &mut [0.0, 0.5, 0.0, -0.5][..]); -/// } -/// ``` -pub fn to_sample_slice_mut<'a, T, S>(slice: T) -> &'a mut [S] -where - S: Sample, - T: ToSampleSliceMut<'a, S>, -{ - slice.to_sample_slice_mut() -} - -/// Converts the given boxed slice into a boxed slice of `Sample`s. -/// -/// This is a convenience function that wraps the `ToBoxedSampleSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = vec![[0.0, 0.5], [0.0, -0.5]].into_boxed_slice(); -/// let bar = sample::slice::to_boxed_sample_slice(foo); -/// assert_eq!(bar.into_vec(), vec![0.0, 0.5, 0.0, -0.5]); -/// } -/// ``` -pub fn to_boxed_sample_slice(slice: T) -> Box<[S]> -where - S: Sample, - T: ToBoxedSampleSlice, -{ - slice.to_boxed_sample_slice() -} - -/// Converts the given slice of `Sample`s into some slice `T`. -/// -/// Returns `None` if the number of channels in a single frame is not a multiple of the number of -/// samples in the given slice. -/// -/// This is a convenience function that wraps the `FromSampleSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &[0.0, 0.5, 0.0, -0.5][..]; -/// let bar: Option<&_> = sample::slice::from_sample_slice(foo); -/// assert_eq!(bar, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); -/// } -/// ``` -pub fn from_sample_slice<'a, T, S>(slice: &'a [S]) -> Option -where - S: Sample, - T: FromSampleSlice<'a, S>, -{ - T::from_sample_slice(slice) -} - -/// Converts the given mutable slice of `Sample`s into some mutable slice `T`. -/// -/// Returns `None` if the number of channels in a single frame is not a multiple of the number of -/// samples in the given slice. -/// -/// This is a convenience function that wraps the `FromSampleSliceMut` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &mut [0.0, 0.5, 0.0, -0.5][..]; -/// let bar: Option<&mut _> = sample::slice::from_sample_slice_mut(foo); -/// assert_eq!(bar, Some(&mut [[0.0, 0.5], [0.0, -0.5]][..])); -/// } -/// ``` -pub fn from_sample_slice_mut<'a, T, S>(slice: &'a mut [S]) -> Option -where - S: Sample, - T: FromSampleSliceMut<'a, S>, -{ - T::from_sample_slice_mut(slice) -} - -/// Converts the given boxed slice of `Sample`s into some slice `T`. -/// -/// Returns `None` if the number of channels in a single frame is not a multiple of the number of -/// samples in the given slice. -/// -/// This is a convenience function that wraps the `FromBoxedSampleSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = vec![0.0, 0.5, 0.0, -0.5].into_boxed_slice(); -/// let bar: Box<[[f32; 2]]> = sample::slice::from_boxed_sample_slice(foo).unwrap(); -/// assert_eq!(bar.into_vec(), vec![[0.0, 0.5], [0.0, -0.5]]); -/// } -/// ``` -pub fn from_boxed_sample_slice(slice: Box<[S]>) -> Option -where - S: Sample, - T: FromBoxedSampleSlice, -{ - T::from_boxed_sample_slice(slice) -} - -/// Converts the given slice of `Frame`s into some slice `T`. -/// -/// This is a convenience function that wraps the `FromFrameSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &[[0.0, 0.5], [0.0, -0.5]][..]; -/// let bar: &[f32] = sample::slice::from_frame_slice(foo); -/// assert_eq!(bar, &[0.0, 0.5, 0.0, -0.5][..]); -/// } -/// ``` -pub fn from_frame_slice<'a, T, F>(slice: &'a [F]) -> T -where - F: Frame, - T: FromFrameSlice<'a, F>, -{ - T::from_frame_slice(slice) -} - -/// Converts the given slice of mutable `Frame`s into some mutable slice `T`. -/// -/// This is a convenience function that wraps the `FromFrameSliceMut` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = &mut [[0.0, 0.5], [0.0, -0.5]][..]; -/// let bar: &mut [f32] = sample::slice::from_frame_slice_mut(foo); -/// assert_eq!(bar, &mut [0.0, 0.5, 0.0, -0.5][..]); -/// } -/// ``` -pub fn from_frame_slice_mut<'a, T, F>(slice: &'a mut [F]) -> T -where - F: Frame, - T: FromFrameSliceMut<'a, F>, -{ - T::from_frame_slice_mut(slice) -} - -/// Converts the given boxed slice of `Frame`s into some slice `T`. -/// -/// This is a convenience function that wraps the `FromBoxedFrameSlice` trait. -/// -/// # Examples -/// -/// ``` -/// extern crate sample; -/// -/// fn main() { -/// let foo = vec![[0.0, 0.5], [0.0, -0.5]].into_boxed_slice(); -/// let bar: Box<[f32]> = sample::slice::from_boxed_frame_slice(foo); -/// assert_eq!(bar.into_vec(), vec![0.0, 0.5, 0.0, -0.5]); -/// } -/// ``` -pub fn from_boxed_frame_slice(slice: Box<[F]>) -> T -where - F: Frame, - T: FromBoxedFrameSlice, -{ - T::from_boxed_frame_slice(slice) -} - - -///// Utility Functions - - -/// Mutate every element in the slice with the given function. -#[inline] -pub fn map_in_place(a: &mut [F], mut map: M) -where - M: FnMut(F) -> F, - F: Frame, -{ - for f in a { - *f = map(*f); - } -} - -/// Sets the slice of frames at the associated `Sample`'s equilibrium value. -#[inline] -pub fn equilibrium(a: &mut [F]) -where - F: Frame, -{ - map_in_place(a, |_| F::equilibrium()) -} - -/// Mutate every frame in slice `a` while reading from each frame in slice `b` in lock-step using -/// the given function. -/// -/// **Panics** if the length of `b` is not equal to the length of `a`. -#[inline] -pub fn zip_map_in_place(a: &mut [FA], b: &[FB], zip_map: M) -where - FA: Frame, - FB: Frame, - M: FnMut(FA, FB) -> FA, -{ - assert_eq!(a.len(), b.len()); - - // We've asserted that the lengths are equal so we don't need bounds checking. - unsafe { - zip_map_in_place_unchecked(a, b, zip_map); - } -} - -/// Writes every sample in slice `b` to slice `a`. -/// -/// **Panics** if the slice lengths differ. -#[inline] -pub fn write(a: &mut [F], b: &[F]) -where - F: Frame, -{ - zip_map_in_place(a, b, |_, b| b); -} - -/// Adds every sample in slice `b` to every sample in slice `a` respectively. -#[inline] -pub fn add_in_place(a: &mut [FA], b: &[FB]) -where - FA: Frame, - FB: Frame::Signed, NumChannels = FA::NumChannels>, -{ - zip_map_in_place(a, b, |a, b| a.add_amp(b)); -} - -/// Scale the amplitude of each frame in `b` by `amp_per_channel` before summing it onto `a`. -#[inline] -pub fn add_in_place_with_amp_per_channel(a: &mut [FA], b: &[FB], amp_per_channel: A) -where - FA: Frame, - FB: Frame::Signed, NumChannels = FA::NumChannels>, - A: Frame::Float, NumChannels = FB::NumChannels>, -{ - zip_map_in_place(a, b, |af, bf| af.add_amp(bf.mul_amp(amp_per_channel))); -} - -/// Mutate every element in slice `a` while reading from each element from slice `b` in lock-step -/// using the given function. -/// -/// This function does not check that the slices are the same length and will crash horrifically on -/// index-out-of-bounds. -#[inline] -unsafe fn zip_map_in_place_unchecked(a: &mut [FA], b: &[FB], mut zip_map: M) -where - FA: Frame, - FB: Frame, - M: FnMut(FA, FB) -> FA, -{ - for i in 0..a.len() { - *a.get_unchecked_mut(i) = zip_map(*a.get_unchecked(i), *b.get_unchecked(i)); - } -} From f948eca0d58f53e9ce7ed712388fecee2721a7c6 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 18:01:40 +0200 Subject: [PATCH 06/29] Remove portaudio from examples in favour of cpal --- examples/Cargo.toml | 3 +- examples/play_wav.rs | 52 +++++++++++++--------- examples/synth.rs | 104 ++++++++++++++++++++++++++----------------- 3 files changed, 96 insertions(+), 63 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 1140f96d..99bb2b60 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -5,10 +5,11 @@ authors = ["mitchmindtree "] edition = "2018" [dependencies] +anyhow = "1" +cpal = { git = "https://github.com/rustaudio/cpal", branch = "master" } dasp = { path = "../dasp", features = ["slice", "interpolate", "interpolate-sinc", "ring_buffer", "signal"] } find_folder = "0.3" hound = "3" -portaudio = "0.7" [[example]] name = "play_wav" diff --git a/examples/play_wav.rs b/examples/play_wav.rs index d4461d7e..e04d21a9 100644 --- a/examples/play_wav.rs +++ b/examples/play_wav.rs @@ -1,8 +1,9 @@ +use cpal; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use dasp::signal::{self, Signal}; use dasp::slice::ToFrameSliceMut; -use portaudio as pa; -fn main() { +fn main() -> Result<(), anyhow::Error> { // Find and load the wav. let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); let reader = hound::WavReader::open(assets.join("thumbpiano A#3.wav")).unwrap(); @@ -12,38 +13,45 @@ fn main() { let samples = reader.into_samples::().filter_map(Result::ok); let mut frames = signal::from_interleaved_samples_iter(samples).until_exhausted(); - // Initialise PortAudio. - let pa = pa::PortAudio::new().unwrap(); - let ch = spec.channels as i32; - let sr = spec.sample_rate as f64; - let buffer_len = 0; // use default - let settings = pa.default_output_stream_settings::(ch, sr, buffer_len).unwrap(); + // Initialise CPAL. + let host = cpal::default_host(); + let device = host + .default_output_device() + .expect("failed to find a default output device"); + + // Create a stream config to match the wave format. + // + // NOTE: It's possible that the platform will not support the sample format, sample rate or + // channel layout of the WAV file. In these cases, you may need to convert the data read from + // the WAV file to a format compatible with one of the platform's supported stream + // configurations. + let config = cpal::StreamConfig { + channels: spec.channels, + sample_rate: cpal::SampleRate(spec.sample_rate), + }; // A channel for indicating when playback has completed. - let (complete_tx, complete_rx) = std::sync::mpsc::channel(); + let (complete_tx, complete_rx) = std::sync::mpsc::sync_channel(1); - // Define the callback which provides PortAudio the audio. - let callback = move |pa::OutputStreamCallbackArgs { buffer, .. }| { - let buffer: &mut [[i16; 2]] = buffer.to_frame_slice_mut().unwrap(); + // Create and run the CPAL stream. + let err_fn = |err| eprintln!("an error occurred on stream: {}", err); + let data_fn = move |data: &mut [i16], _info: &cpal::OutputCallbackInfo| { + let buffer: &mut [[i16; 2]] = data.to_frame_slice_mut().unwrap(); for out_frame in buffer { match frames.next() { Some(frame) => *out_frame = frame, None => { - complete_tx.send(()).unwrap(); - return pa::Complete; + complete_tx.try_send(()).ok(); + *out_frame = dasp::Frame::equilibrium(); }, } } - pa::Continue }; - - // Spawn and start the output stream. - let mut stream = pa.open_non_blocking_stream(settings, callback).unwrap(); - stream.start().unwrap(); + let stream = device.build_output_stream(&config, data_fn, err_fn)?; + stream.play().unwrap(); // Block until playback completes. complete_rx.recv().unwrap(); - - stream.stop().unwrap(); - stream.close().unwrap(); + stream.pause().ok(); + Ok(()) } diff --git a/examples/synth.rs b/examples/synth.rs index f786604b..96571892 100644 --- a/examples/synth.rs +++ b/examples/synth.rs @@ -1,58 +1,82 @@ -use portaudio as pa; +use cpal; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use dasp::{signal, Sample, Signal}; +use std::sync::mpsc; -use dasp::{signal, Frame, Sample, Signal}; -use dasp::slice::ToFrameSliceMut; +fn main() -> Result<(), anyhow::Error> { + let host = cpal::default_host(); + let device = host + .default_output_device() + .expect("failed to find a default output device"); + let config = device.default_output_config()?; -const FRAMES_PER_BUFFER: u32 = 512; -const NUM_CHANNELS: i32 = 1; -const SAMPLE_RATE: f64 = 44_100.0; + match config.sample_format() { + cpal::SampleFormat::F32 => run::(&device, &config.into())?, + cpal::SampleFormat::I16 => run::(&device, &config.into())?, + cpal::SampleFormat::U16 => run::(&device, &config.into())?, + } -fn main() { - run().unwrap(); + Ok(()) } -fn run() -> Result<(), pa::Error> { +fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error> +where + T: cpal::Sample, +{ // Create a signal chain to play back 1 second of each oscillator at A4. - let hz = signal::rate(SAMPLE_RATE).const_hz(440.0); - let one_sec = SAMPLE_RATE as usize; - let mut waves = hz.clone() + let hz = signal::rate(config.sample_rate.0 as f64).const_hz(440.0); + let one_sec = config.sample_rate.0 as usize; + let mut synth = hz + .clone() .sine() .take(one_sec) .chain(hz.clone().saw().take(one_sec)) .chain(hz.clone().square().take(one_sec)) .chain(hz.clone().noise_simplex().take(one_sec)) .chain(signal::noise(0).take(one_sec)) - .map(|f| f.map(|s| s.to_sample::() * 0.2)); - - // Initialise PortAudio. - let pa = pa::PortAudio::new()?; - let settings = pa.default_output_stream_settings::( - NUM_CHANNELS, - SAMPLE_RATE, - FRAMES_PER_BUFFER, - )?; + .map(|[s]| s.to_sample::() * 0.2); - // Define the callback which provides PortAudio the audio. - let callback = move |pa::OutputStreamCallbackArgs { buffer, .. }| { - let buffer: &mut [[f32; 1]] = buffer.to_frame_slice_mut().unwrap(); - for out_frame in buffer { - match waves.next() { - Some(frame) => *out_frame = frame, - None => return pa::Complete, - } - } - pa::Continue - }; - - let mut stream = pa.open_non_blocking_stream(settings, callback)?; - stream.start()?; + // A channel for indicating when playback has completed. + let (complete_tx, complete_rx) = mpsc::sync_channel(1); - while let Ok(true) = stream.is_active() { - std::thread::sleep(std::time::Duration::from_millis(100)); - } + // Create and run the stream. + let err_fn = |err| eprintln!("an error occurred on stream: {}", err); + let channels = config.channels as usize; + let stream = device.build_output_stream( + config, + move |data: &mut [T], _: &cpal::OutputCallbackInfo| { + write_data(data, channels, &complete_tx, &mut synth) + }, + err_fn, + )?; + stream.play()?; - stream.stop()?; - stream.close()?; + // Wait for playback to complete. + complete_rx.recv().unwrap(); + stream.pause()?; Ok(()) } + +fn write_data( + output: &mut [T], + channels: usize, + complete_tx: &mpsc::SyncSender<()>, + signal: &mut dyn Iterator, +) where + T: cpal::Sample, +{ + for frame in output.chunks_mut(channels) { + let sample = match signal.next() { + None => { + complete_tx.try_send(()).ok(); + 0.0 + } + Some(sample) => sample, + }; + let value: T = cpal::Sample::from::(&sample); + for sample in frame.iter_mut() { + *sample = value; + } + } +} From bd200767758661d77864ce89b4731c40e986b223 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 18:37:44 +0200 Subject: [PATCH 07/29] Add `all-features-no-std` features. Remove travis in favour of workflow Also adds auto-publishing when pushing to master and more thorough testing of different combinations of features and no_std. --- .github/workflows/dasp.yml | 195 +++++++++++++++++++++++++++++++ .travis.yml | 22 ---- dasp/Cargo.toml | 25 ++++ dasp_envelope/Cargo.toml | 12 +- dasp_interpolate/Cargo.toml | 11 +- dasp_interpolate/src/lib.rs | 3 + dasp_interpolate/src/sinc/ops.rs | 2 - dasp_signal/Cargo.toml | 17 ++- dasp_slice/Cargo.toml | 8 +- dasp_window/Cargo.toml | 8 +- dasp_window/src/hanning/ops.rs | 1 - dasp_window/src/lib.rs | 3 + 12 files changed, 274 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/dasp.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/dasp.yml b/.github/workflows/dasp.yml new file mode 100644 index 00000000..ae5bc665 --- /dev/null +++ b/.github/workflows/dasp.yml @@ -0,0 +1,195 @@ +name: dasp +on: [push, pull_request] +jobs: + cargo-fmt-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + - name: cargo fmt check + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + cargo-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Update apt + run: sudo apt update + - name: Install alsa + run: sudo apt-get install libasound2-dev + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --verbose + + cargo-test-no-default-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Update apt + run: sudo apt update + - name: Install alsa + run: sudo apt-get install libasound2-dev + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo test (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --no-default-features --verbose + + cargo-test-all-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Update apt + run: sudo apt update + - name: Install alsa + run: sudo apt-get install libasound2-dev + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo test (all features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --all-features --verbose + + cargo-test-all-features-no-std: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Update apt + run: sudo apt update + - name: Install alsa + run: sudo apt-get install libasound2-dev + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo test dasp (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + - name: cargo test dasp_signal (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_signal/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + - name: cargo test dasp_slice (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_slice/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + - name: cargo test dasp_interpolate (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_interpolate/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + - name: cargo test window (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_window/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + - name: cargo test dasp_envelope (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_envelope/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + + cargo-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Update apt + run: sudo apt update + - name: Install alsa + run: sudo apt-get install libasound2-dev + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo doc + uses: actions-rs/cargo@v1 + with: + command: doc + args: --all --all-features --verbose + + cargo-publish: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + env: + CRATESIO_TOKEN: ${{ secrets.CRATESIO_TOKEN }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Update apt + run: sudo apt update + - name: Install alsa dev tools + run: sudo apt-get install libasound2-dev + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: cargo publish dasp_sample + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_sample/Cargo.toml + - name: cargo publish dasp_frame + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_frame/Cargo.toml + - name: cargo publish dasp_slice + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_slice/Cargo.toml + - name: cargo publish dasp_ring_buffer + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_ring_buffer/Cargo.toml + - name: cargo publish dasp_peak + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_peak/Cargo.toml + - name: cargo publish dasp_rms + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_rms/Cargo.toml + - name: cargo publish dasp_interpolate + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_interpolate/Cargo.toml + - name: cargo publish dasp_window + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_window/Cargo.toml + - name: cargo publish dasp_envelope + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_envelope/Cargo.toml + - name: cargo publish dasp_signal + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_signal/Cargo.toml + - name: cargo publish dasp + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp/Cargo.toml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index afd0f1c3..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: rust -rust: -- stable -- beta -- nightly -script: -- | - ([ "$TRAVIS_RUST_VERSION" = "nightly" ] && cargo check -v --no-default-features) || [ "$TRAVIS_RUST_VERSION" != "nightly" ] -- cargo check -v -- cargo check --tests --examples --no-default-features --features std -- cargo check --tests --examples --no-default-features --features std,slice -- cargo check --tests --examples --no-default-features --features std,envelope -- cargo check --tests --examples --no-default-features --features std,frame -- cargo check --tests --examples --no-default-features --features std,peak -- cargo check --tests --examples --no-default-features --features std,ring_buffer -- cargo check --tests --examples --no-default-features --features std,rms -- cargo check --tests --examples --no-default-features --features std,signal -- cargo check --tests --examples --no-default-features --features std,window -- cargo check --tests --examples --no-default-features --features std,interpolate -- cargo test -v -- cargo test --release -v -- cargo doc -v diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 9623d4b5..6a1d283f 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -18,6 +18,31 @@ dasp_window = { path = "../dasp_window", default-features = false, optional = tr [features] default = ["std"] +all-features-no-std = [ + "envelope", + "envelope-peak", + "envelope-rms", + "interpolate", + "interpolate-floor", + "interpolate-linear", + "interpolate-sinc", + "peak", + "ring_buffer", + "rms", + "signal", + "signal-boxed", + "signal-bus", + "signal-envelope", + "signal-rms", + "signal-window", + "signal-window-hanning", + "signal-window-rectangle", + "slice", + "slice-boxed", + "window", + "window-hanning", + "window-rectangle", +] std = [ "dasp_envelope/std", "dasp_frame/std", diff --git a/dasp_envelope/Cargo.toml b/dasp_envelope/Cargo.toml index e783b13d..81fb737f 100644 --- a/dasp_envelope/Cargo.toml +++ b/dasp_envelope/Cargo.toml @@ -13,9 +13,19 @@ dasp_sample = { path = "../dasp_sample", default-features = false } [features] default = ["std"] +all-features-no-std = [ + "peak", + "rms", +] +std = [ + "dasp_frame/std", + "dasp_peak/std", + "dasp_ring_buffer/std", + "dasp_rms/std", + "dasp_sample/std", +] peak = ["dasp_peak"] rms = ["dasp_rms"] -std = ["dasp_frame/std", "dasp_peak/std", "dasp_ring_buffer/std", "dasp_rms/std", "dasp_sample/std"] [package.metadata.docs.rs] all-features = true diff --git a/dasp_interpolate/Cargo.toml b/dasp_interpolate/Cargo.toml index d79ce0fe..07bdfed7 100644 --- a/dasp_interpolate/Cargo.toml +++ b/dasp_interpolate/Cargo.toml @@ -11,10 +11,19 @@ dasp_sample = { path = "../dasp_sample", default-features = false } [features] default = ["std"] +all-features-no-std = [ + "floor", + "linear", + "sinc", +] +std = [ + "dasp_frame/std", + "dasp_ring_buffer/std", + "dasp_sample/std", +] floor = [] linear = [] sinc = [] -std = ["dasp_frame/std", "dasp_ring_buffer/std", "dasp_sample/std"] [package.metadata.docs.rs] all-features = true diff --git a/dasp_interpolate/src/lib.rs b/dasp_interpolate/src/lib.rs index e1ae551f..cf69d44a 100644 --- a/dasp_interpolate/src/lib.rs +++ b/dasp_interpolate/src/lib.rs @@ -1,5 +1,8 @@ //! The Interpolate module allows for conversion between various sample rates. +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] + use dasp_frame::Frame; #[cfg(feature = "floor")] diff --git a/dasp_interpolate/src/sinc/ops.rs b/dasp_interpolate/src/sinc/ops.rs index e2c15db4..edc9b7a0 100644 --- a/dasp_interpolate/src/sinc/ops.rs +++ b/dasp_interpolate/src/sinc/ops.rs @@ -1,5 +1,3 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] #![allow(dead_code)] pub mod f64 { diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml index 352acec2..13cccc87 100644 --- a/dasp_signal/Cargo.toml +++ b/dasp_signal/Cargo.toml @@ -21,10 +21,15 @@ dasp_window = { path = "../dasp_window", default-features = false, features = [" [features] default = ["std"] -boxed = [] -bus = [] -envelope = ["dasp_envelope"] -rms = ["dasp_rms"] +all-features-no-std = [ + "boxed", + "bus", + "envelope", + "rms", + "window", + "window-hanning", + "window-rectangle", +] std = [ "dasp_envelope/std", "dasp_frame/std", @@ -35,6 +40,10 @@ std = [ "dasp_sample/std", "dasp_window/std", ] +boxed = [] +bus = [] +envelope = ["dasp_envelope"] +rms = ["dasp_rms"] window = ["dasp_window"] window-hanning = ["dasp_window/hanning"] window-rectangle = ["dasp_window/rectangle"] diff --git a/dasp_slice/Cargo.toml b/dasp_slice/Cargo.toml index 9faf9825..6b908336 100644 --- a/dasp_slice/Cargo.toml +++ b/dasp_slice/Cargo.toml @@ -10,8 +10,14 @@ dasp_frame = { path = "../dasp_frame", default-features = false } [features] default = ["std"] +all-features-no-std = [ + "boxed", +] +std = [ + "dasp_sample/std", + "dasp_frame/std", +] boxed = [] -std = ["dasp_sample/std", "dasp_frame/std"] [package.metadata.docs.rs] all-features = true diff --git a/dasp_window/Cargo.toml b/dasp_window/Cargo.toml index 3be32054..3036f254 100644 --- a/dasp_window/Cargo.toml +++ b/dasp_window/Cargo.toml @@ -9,9 +9,15 @@ dasp_sample = { path = "../dasp_sample", default-features = false } [features] default = ["std"] +all-features-no-std = [ + "hanning", + "rectangle", +] +std = [ + "dasp_sample/std", +] hanning = [] rectangle = [] -std = ["dasp_sample/std"] [package.metadata.docs.rs] all-features = true diff --git a/dasp_window/src/hanning/ops.rs b/dasp_window/src/hanning/ops.rs index 55cdbb57..f37cda1f 100644 --- a/dasp_window/src/hanning/ops.rs +++ b/dasp_window/src/hanning/ops.rs @@ -1,4 +1,3 @@ -#![cfg_attr(not(feature = "std"), no_std)] #![allow(dead_code)] pub mod f64 { diff --git a/dasp_window/src/lib.rs b/dasp_window/src/lib.rs index a8c17571..740f2d3a 100644 --- a/dasp_window/src/lib.rs +++ b/dasp_window/src/lib.rs @@ -1,6 +1,9 @@ //! Module for windowing over a batch of Frames. Includes default Hanning and Rectangle window //! types. +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] + use dasp_sample::Sample; #[cfg(feature = "hanning")] From f35a13453a893a28033cae3a0576faeac30257ea Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:32:39 +0200 Subject: [PATCH 08/29] Add dependency graph image to assets directory --- assets/deps-graph.png | Bin 0 -> 117898 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/deps-graph.png diff --git a/assets/deps-graph.png b/assets/deps-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..b61436f0da9c43d7e6c92e3bfc0cd05375c6cb97 GIT binary patch literal 117898 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 Mrz)E%ZFv9x0R1q-@&Et; literal 0 HcmV?d00001 From 5e0a1df2c8cfdb35faf4e7a79af843c530de1a8f Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:32:46 +0200 Subject: [PATCH 09/29] Update top-level README with info on included crates and no_std --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ecf7307f..53eaae29 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,38 @@ for working with digital audio signals. set of tools required for their project. The following crates are included within this repository: -| Crate | Badges | Description | +| **Library** | **Links** | **Description** | | --- | --- | --- | -| dasp_sample | | | -| dasp | | | +| [**`dasp`**][dasp] | [![Crates.io][dasp-crates-io-svg]][dasp-crates-io] [![docs.rs][dasp-docs-rs-svg]][dasp-docs-rs] | Top-level API with features for all crates. | +| [**`dasp_sample`**][dasp_sample] | [![Crates.io][dasp_sample-crates-io-svg]][dasp_sample-crates-io] [![docs.rs][dasp_sample-docs-rs-svg]][dasp_sample-docs-rs] | Sample trait, types, conversions and operations. | +| [**`dasp_frame`**][dasp_frame] | [![Crates.io][dasp_frame-crates-io-svg]][dasp_frame-crates-io] [![docs.rs][dasp_frame-docs-rs-svg]][dasp_frame-docs-rs] | Frame trait, types, conversions and operations. | +| [**`dasp_slice`**][dasp_slice] | [![Crates.io][dasp_slice-crates-io-svg]][dasp_slice-crates-io] [![docs.rs][dasp_slice-docs-rs-svg]][dasp_slice-docs-rs] | Conversions and operations for slices of samples or frames. | +| [**`dasp_ring_buffer`**][dasp_ring_buffer] | [![Crates.io][dasp_ring_buffer-crates-io-svg]][dasp_ring_buffer-crates-io] [![docs.rs][dasp_ring_buffer-docs-rs-svg]][dasp_ring_buffer-docs-rs] | Simple fixed and bounded ring buffers. | +| [**`dasp_peak`**][dasp_peak] | [![Crates.io][dasp_peak-crates-io-svg]][dasp_peak-crates-io] [![docs.rs][dasp_peak-docs-rs-svg]][dasp_peak-docs-rs] | Peak detection with half/full positive/negative wave rectifiers. | +| [**`dasp_rms`**][dasp_rms] | [![Crates.io][dasp_rms-crates-io-svg]][dasp_rms-crates-io] [![docs.rs][dasp_rms-docs-rs-svg]][dasp_rms-docs-rs] | RMS detection with configurable window. | +| [**`dasp_envelope`**][dasp_envelope] | [![Crates.io][dasp_envelope-crates-io-svg]][dasp_envelope-crates-io] [![docs.rs][dasp_envelope-docs-rs-svg]][dasp_envelope-docs-rs] | Envelope detection abstraction with peak and RMS implementations. | +| [**`dasp_interpolate`**][dasp_interpolate] | [![Crates.io][dasp_interpolate-crates-io-svg]][dasp_interpolate-crates-io] [![docs.rs][dasp_interpolate-docs-rs-svg]][dasp_interpolate-docs-rs] | Abstraction for frame interpolation (provides linear, sinc and more). | +| [**`dasp_window`**][dasp_window] | [![Crates.io][dasp_window-crates-io-svg]][dasp_window-crates-io] [![docs.rs][dasp_window-docs-rs-svg]][dasp_window-docs-rs] | Windowing abstraction with provided hanning and rectangle functions. | +| [**`dasp_signal`**][dasp_signal] | [![Crates.io][dasp_signal-crates-io-svg]][dasp_signal-crates-io] [![docs.rs][dasp_signal-docs-rs-svg]][dasp_signal-docs-rs] | An iterator-like API for working with streams of audio frames. | + +[![deps-graph][deps-graph]][deps-graph] + +*Red dotted lines indicate optional use, while black lines indicate required +dependencies.* + + +### Features + +TODO + + +### `no_std` + +All crates may be compiled with and without the std library. The std library is +enabled by default, however it may be disabled via `--no-default-features`. + +To enable all of a crate's features *without* the std library, you may use +`--no-default-features --features "all-features-no-std"`. ## Contributing @@ -41,3 +69,62 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + + +[deps-graph]: https://github.com/RustAudio/sample/blob/master/assets/deps-graph.png + +[dasp]: ./dasp +[dasp-crates-io]: https://crates.io/crates/dasp +[dasp-crates-io-svg]: https://img.shields.io/crates/v/dasp.svg +[dasp-docs-rs]: https://docs.rs/dasp/ +[dasp-docs-rs-svg]: https://docs.rs/dasp/badge.svg +[dasp_envelope]: ./dasp_envelope +[dasp_envelope-crates-io]: https://crates.io/crates/dasp_envelope +[dasp_envelope-crates-io-svg]: https://img.shields.io/crates/v/dasp_envelope.svg +[dasp_envelope-docs-rs]: https://docs.rs/dasp_envelope/ +[dasp_envelope-docs-rs-svg]: https://docs.rs/dasp_envelope/badge.svg +[dasp_frame]: ./dasp_frame +[dasp_frame-crates-io]: https://crates.io/crates/dasp_frame +[dasp_frame-crates-io-svg]: https://img.shields.io/crates/v/dasp_frame.svg +[dasp_frame-docs-rs]: https://docs.rs/dasp_frame/ +[dasp_frame-docs-rs-svg]: https://docs.rs/dasp_frame/badge.svg +[dasp_interpolate]: ./dasp_interpolate +[dasp_interpolate-crates-io]: https://crates.io/crates/dasp_interpolate +[dasp_interpolate-crates-io-svg]: https://img.shields.io/crates/v/dasp_interpolate.svg +[dasp_interpolate-docs-rs]: https://docs.rs/dasp_interpolate/ +[dasp_interpolate-docs-rs-svg]: https://docs.rs/dasp_interpolate/badge.svg +[dasp_peak]: ./dasp_peak +[dasp_peak-crates-io]: https://crates.io/crates/dasp_peak +[dasp_peak-crates-io-svg]: https://img.shields.io/crates/v/dasp_peak.svg +[dasp_peak-docs-rs]: https://docs.rs/dasp_peak/ +[dasp_peak-docs-rs-svg]: https://docs.rs/dasp_peak/badge.svg +[dasp_ring_buffer]: ./dasp_ring_buffer +[dasp_ring_buffer-crates-io]: https://crates.io/crates/dasp_ring_buffer +[dasp_ring_buffer-crates-io-svg]: https://img.shields.io/crates/v/dasp_ring_buffer.svg +[dasp_ring_buffer-docs-rs]: https://docs.rs/dasp_ring_buffer/ +[dasp_ring_buffer-docs-rs-svg]: https://docs.rs/dasp_ring_buffer/badge.svg +[dasp_rms]: ./dasp_rms +[dasp_rms-crates-io]: https://crates.io/crates/dasp_rms +[dasp_rms-crates-io-svg]: https://img.shields.io/crates/v/dasp_rms.svg +[dasp_rms-docs-rs]: https://docs.rs/dasp_rms/ +[dasp_rms-docs-rs-svg]: https://docs.rs/dasp_rms/badge.svg +[dasp_sample]: ./dasp_sample +[dasp_sample-crates-io]: https://crates.io/crates/dasp_sample +[dasp_sample-crates-io-svg]: https://img.shields.io/crates/v/dasp_sample.svg +[dasp_sample-docs-rs]: https://docs.rs/dasp_sample/ +[dasp_sample-docs-rs-svg]: https://docs.rs/dasp_sample/badge.svg +[dasp_signal]: ./dasp_signal +[dasp_signal-crates-io]: https://crates.io/crates/dasp_signal +[dasp_signal-crates-io-svg]: https://img.shields.io/crates/v/dasp_signal.svg +[dasp_signal-docs-rs]: https://docs.rs/dasp_signal/ +[dasp_signal-docs-rs-svg]: https://docs.rs/dasp_signal/badge.svg +[dasp_slice]: ./dasp_slice +[dasp_slice-crates-io]: https://crates.io/crates/dasp_slice +[dasp_slice-crates-io-svg]: https://img.shields.io/crates/v/dasp_slice.svg +[dasp_slice-docs-rs]: https://docs.rs/dasp_slice/ +[dasp_slice-docs-rs-svg]: https://docs.rs/dasp_slice/badge.svg +[dasp_window]: ./dasp_window +[dasp_window-crates-io]: https://crates.io/crates/dasp_window +[dasp_window-crates-io-svg]: https://img.shields.io/crates/v/dasp_window.svg +[dasp_window-docs-rs]: https://docs.rs/dasp_window/ +[dasp_window-docs-rs-svg]: https://docs.rs/dasp_window/badge.svg From 4252cfa3730e6cd6f6ac601cdc14654c2f426444 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:34:28 +0200 Subject: [PATCH 10/29] Run cargo fmt on whole repository --- dasp_envelope/src/detect/peak.rs | 4 ++- dasp_envelope/src/detect/rms.rs | 5 ++- dasp_interpolate/src/sinc/mod.rs | 2 +- dasp_peak/src/lib.rs | 20 +++++++----- dasp_ring_buffer/src/lib.rs | 13 +++----- dasp_rms/src/lib.rs | 28 +++++++++-------- dasp_sample/src/conv.rs | 39 ++++++++++-------------- dasp_sample/tests/conv.rs | 6 ++-- dasp_sample/tests/types.rs | 35 ++++++++++++++++----- dasp_signal/src/bus.rs | 21 +++++++------ dasp_signal/src/rms.rs | 8 ++--- dasp_signal/src/window/hanning.rs | 2 +- dasp_signal/src/window/rectangle.rs | 2 +- dasp_signal/tests/interpolate.rs | 6 ++-- dasp_signal/tests/window.rs | 11 +------ dasp_slice/src/boxed.rs | 15 ++++----- dasp_slice/src/frame/fixed_size_array.rs | 8 ++--- dasp_slice/src/frame/mod.rs | 9 ++---- dasp_slice/src/lib.rs | 6 ++-- examples/play_wav.rs | 6 ++-- examples/resample.rs | 11 +++++-- examples/test.rs | 1 - 22 files changed, 134 insertions(+), 124 deletions(-) diff --git a/dasp_envelope/src/detect/peak.rs b/dasp_envelope/src/detect/peak.rs index caa0aca1..a0c53385 100644 --- a/dasp_envelope/src/detect/peak.rs +++ b/dasp_envelope/src/detect/peak.rs @@ -88,6 +88,8 @@ where impl From for Peak { fn from(rectifier: R) -> Self { - Peak { rectifier: rectifier } + Peak { + rectifier: rectifier, + } } } diff --git a/dasp_envelope/src/detect/rms.rs b/dasp_envelope/src/detect/rms.rs index 27542b77..3e42312f 100644 --- a/dasp_envelope/src/detect/rms.rs +++ b/dasp_envelope/src/detect/rms.rs @@ -1,13 +1,12 @@ use crate::{Detect, Detector}; use dasp_frame::Frame; -use dasp_rms as rms; use dasp_ring_buffer as ring_buffer; +use dasp_rms as rms; impl Detect for rms::Rms where F: Frame, - S: ring_buffer::Slice - + ring_buffer::SliceMut, + S: ring_buffer::Slice + ring_buffer::SliceMut, { type Output = F::Float; fn detect(&mut self, frame: F) -> Self::Output { diff --git a/dasp_interpolate/src/sinc/mod.rs b/dasp_interpolate/src/sinc/mod.rs index 429b6ed3..1f3567d4 100644 --- a/dasp_interpolate/src/sinc/mod.rs +++ b/dasp_interpolate/src/sinc/mod.rs @@ -1,5 +1,5 @@ -use core::f64::consts::PI; use crate::Interpolator; +use core::f64::consts::PI; use dasp_frame::Frame; use dasp_ring_buffer as ring_buffer; use dasp_sample::{Duplex, Sample}; diff --git a/dasp_peak/src/lib.rs b/dasp_peak/src/lib.rs index 86768df4..05b8e005 100644 --- a/dasp_peak/src/lib.rs +++ b/dasp_peak/src/lib.rs @@ -76,10 +76,12 @@ pub fn positive_half_wave(frame: F) -> F where F: Frame, { - frame.map(|s| if s < Sample::equilibrium() { - Sample::equilibrium() - } else { - s + frame.map(|s| { + if s < Sample::equilibrium() { + Sample::equilibrium() + } else { + s + } }) } @@ -88,9 +90,11 @@ pub fn negative_half_wave(frame: F) -> F where F: Frame, { - frame.map(|s| if s > Sample::equilibrium() { - Sample::equilibrium() - } else { - s + frame.map(|s| { + if s > Sample::equilibrium() { + Sample::equilibrium() + } else { + s + } }) } diff --git a/dasp_ring_buffer/src/lib.rs b/dasp_ring_buffer/src/lib.rs index cf38d86a..48d21999 100644 --- a/dasp_ring_buffer/src/lib.rs +++ b/dasp_ring_buffer/src/lib.rs @@ -12,10 +12,10 @@ #[cfg(not(feature = "std"))] extern crate alloc; -use core::mem; use core::iter::{Chain, Cycle, FromIterator, Skip, Take}; -use core::ptr; +use core::mem; use core::ops::{Index, IndexMut}; +use core::ptr; use core::slice; #[cfg(not(feature = "std"))] @@ -723,10 +723,7 @@ where return None; } let wrapped_index = index % self.max_len(); - unsafe { - Some(self.data.slice_mut().get_unchecked_mut(wrapped_index) as - &mut _) - } + unsafe { Some(self.data.slice_mut().get_unchecked_mut(wrapped_index) as &mut _) } } /// Pushes the given element to the back of the buffer. @@ -832,9 +829,7 @@ where /// } /// ``` pub fn drain(&mut self) -> DrainBounded { - DrainBounded { - bounded: self, - } + DrainBounded { bounded: self } } /// Creates a `Bounded` ring buffer from its start index, length and data slice. diff --git a/dasp_rms/src/lib.rs b/dasp_rms/src/lib.rs index 45e7f84b..ebb7114e 100644 --- a/dasp_rms/src/lib.rs +++ b/dasp_rms/src/lib.rs @@ -143,25 +143,27 @@ where // 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.square_sum = self.square_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.square_sum = + self.square_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_squared() } /// 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, square_sum, .. } = self; + let Rms { + window, square_sum, .. + } = self; (window, square_sum) } diff --git a/dasp_sample/src/conv.rs b/dasp_sample/src/conv.rs index 500b0355..f1ed3f90 100644 --- a/dasp_sample/src/conv.rs +++ b/dasp_sample/src/conv.rs @@ -13,7 +13,7 @@ //! Note that floating point conversions use the range -1.0 <= v < 1.0: //! `(1.0 as f64).to_sample::()` will overflow! -use crate::types::{I24, U24, I48, U48}; +use crate::types::{I24, I48, U24, U48}; macro_rules! conversion_fn { ($Rep:ty, $s:ident to_i8 { $body:expr }) => { @@ -113,7 +113,6 @@ macro_rules! conversion_fn { $body } }; - } macro_rules! conversion_fns { @@ -133,7 +132,6 @@ macro_rules! conversions { }; } - conversions!(i8, i8 { s to_i16 { (s as i16) << 8 } s to_i24 { I24::new_unchecked((s as i32) << 16) } @@ -566,7 +564,6 @@ conversions!(f64, f64 { s to_f32 { s as f32 } }); - /// Similar to the std `From` trait, but specifically for converting between sample types. /// /// We use this trait to be generic over the `Sample::to_sample` and `Sample::from_sample` methods. @@ -595,85 +592,85 @@ macro_rules! impl_from_sample { }; } -impl_from_sample!{i8, to_i8 from +impl_from_sample! {i8, to_i8 from {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{i16, to_i16 from +impl_from_sample! {i16, to_i16 from {i8:i8} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{I24, to_i24 from +impl_from_sample! {I24, to_i24 from {i8:i8} {i16:i16} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{i32, to_i32 from +impl_from_sample! {i32, to_i32 from {i8:i8} {i16:i16} {I24:i24} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{I48, to_i48 from +impl_from_sample! {I48, to_i48 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{i64, to_i64 from +impl_from_sample! {i64, to_i64 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{u8, to_u8 from +impl_from_sample! {u8, to_u8 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{u16, to_u16 from +impl_from_sample! {u16, to_u16 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{U24, to_u24 from +impl_from_sample! {U24, to_u24 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {u32:u32} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{u32, to_u32 from +impl_from_sample! {u32, to_u32 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {U48:u48} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{U48, to_u48 from +impl_from_sample! {U48, to_u48 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {u64:u64} {f32:f32} {f64:f64} } -impl_from_sample!{u64, to_u64 from +impl_from_sample! {u64, to_u64 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {f32:f32} {f64:f64} } -impl_from_sample!{f32, to_f32 from +impl_from_sample! {f32, to_f32 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f64:f64} } -impl_from_sample!{f64, to_f64 from +impl_from_sample! {f64, to_f64 from {i8:i8} {i16:i16} {I24:i24} {i32:i32} {I48:i48} {i64:i64} {u8:u8} {u16:u16} {U24:u24} {u32:u32} {U48:u48} {u64:u64} {f32:f32} @@ -699,8 +696,4 @@ where /// Sample types which may be converted to and from some type `S`. pub trait Duplex: FromSample + ToSample {} -impl Duplex for T -where - T: FromSample + ToSample, -{ -} +impl Duplex for T where T: FromSample + ToSample {} diff --git a/dasp_sample/tests/conv.rs b/dasp_sample/tests/conv.rs index 3fb0bbc9..b442d246 100644 --- a/dasp_sample/tests/conv.rs +++ b/dasp_sample/tests/conv.rs @@ -18,7 +18,10 @@ macro_rules! conv_cmp { assert_eq!($fn_name($T::new_unchecked($pre_conv)), $post_conv); }; ($T:ident; $fn_name:ident: $U:ident, $pre_conv:expr, $post_conv:expr) => { - assert_eq!($fn_name($T::new_unchecked($pre_conv)), $U::new_unchecked($post_conv)); + assert_eq!( + $fn_name($T::new_unchecked($pre_conv)), + $U::new_unchecked($post_conv) + ); }; } @@ -285,7 +288,6 @@ macro_rules! tests { }; } - tests!(i8 { to_i16 { -128, -32_768; 0, 0; 127, 32_512; } to_i24 { -128, -8_388_608; 0, 0; 127, 8_323_072; } diff --git a/dasp_sample/tests/types.rs b/dasp_sample/tests/types.rs index 9c199095..c8a9a642 100644 --- a/dasp_sample/tests/types.rs +++ b/dasp_sample/tests/types.rs @@ -7,13 +7,34 @@ macro_rules! test_type { #[test] fn ops() { use dasp_sample::types::$mod_name::$T; - assert_eq!($T::new(8).unwrap() + $T::new(12).unwrap(), $T::new(20).unwrap()); - assert_eq!($T::new(12).unwrap() - $T::new(4).unwrap(), $T::new(8).unwrap()); - assert_eq!($T::new(2).unwrap() * $T::new(2).unwrap(), $T::new(4).unwrap()); - assert_eq!($T::new(3).unwrap() * $T::new(3).unwrap(), $T::new(9).unwrap()); - assert_eq!($T::new(5).unwrap() * $T::new(10).unwrap(), $T::new(50).unwrap()); - assert_eq!($T::new(16).unwrap() / $T::new(8).unwrap(), $T::new(2).unwrap()); - assert_eq!($T::new(8).unwrap() % $T::new(3).unwrap(), $T::new(2).unwrap()); + assert_eq!( + $T::new(8).unwrap() + $T::new(12).unwrap(), + $T::new(20).unwrap() + ); + assert_eq!( + $T::new(12).unwrap() - $T::new(4).unwrap(), + $T::new(8).unwrap() + ); + assert_eq!( + $T::new(2).unwrap() * $T::new(2).unwrap(), + $T::new(4).unwrap() + ); + assert_eq!( + $T::new(3).unwrap() * $T::new(3).unwrap(), + $T::new(9).unwrap() + ); + assert_eq!( + $T::new(5).unwrap() * $T::new(10).unwrap(), + $T::new(50).unwrap() + ); + assert_eq!( + $T::new(16).unwrap() / $T::new(8).unwrap(), + $T::new(2).unwrap() + ); + assert_eq!( + $T::new(8).unwrap() % $T::new(3).unwrap(), + $T::new(2).unwrap() + ); } #[cfg(debug_assertions)] diff --git a/dasp_signal/src/bus.rs b/dasp_signal/src/bus.rs index 1549c7ee..778ccdf9 100644 --- a/dasp_signal/src/bus.rs +++ b/dasp_signal/src/bus.rs @@ -132,9 +132,10 @@ where // signal and appended to the ring buffer to be received by the other outputs. fn next_frame(&mut self, key: usize) -> S::Frame { let num_frames = self.buffer.len(); - let frames_read = self.frames_read.remove(&key).expect( - "no frames_read for Output", - ); + let frames_read = self + .frames_read + .remove(&key) + .expect("no frames_read for Output"); let frame = if frames_read < num_frames { self.buffer[frames_read] @@ -146,9 +147,10 @@ where // If the number of frames read by this output is the lowest, then we can pop the frame // from the front. - let least_frames_read = !self.frames_read.values().any(|&other_frames_read| { - other_frames_read <= frames_read - }); + let least_frames_read = !self + .frames_read + .values() + .any(|&other_frames_read| other_frames_read <= frames_read); // If this output had read the least number of frames, pop the front frame and decrement // the frames read counters for each of the other outputs. @@ -177,9 +179,10 @@ where // Called by the `Output::drop` implementation. fn drop_output(&mut self, key: usize) { self.frames_read.remove(&key); - let least_frames_read = self.frames_read.values().fold(self.buffer.len(), |a, &b| { - core::cmp::min(a, b) - }); + let least_frames_read = self + .frames_read + .values() + .fold(self.buffer.len(), |a, &b| core::cmp::min(a, b)); if least_frames_read > 0 { for frames_read in self.frames_read.values_mut() { *frames_read -= least_frames_read; diff --git a/dasp_signal/src/rms.rs b/dasp_signal/src/rms.rs index 343fd2aa..c4b43f40 100644 --- a/dasp_signal/src/rms.rs +++ b/dasp_signal/src/rms.rs @@ -64,10 +64,7 @@ where /// Consumes the `Rms` signal and returns its inner signal `S` and `Rms` detector. pub fn into_parts(self) -> (S, rms::Rms) { - let Rms { - signal, - rms, - } = self; + let Rms { signal, rms } = self; (signal, rms) } } @@ -75,8 +72,7 @@ where impl Signal for Rms where S: Signal, - D: ring_buffer::Slice::Float> - + ring_buffer::SliceMut, + D: ring_buffer::Slice::Float> + ring_buffer::SliceMut, { type Frame = ::Float; fn next(&mut self) -> Self::Frame { diff --git a/dasp_signal/src/window/hanning.rs b/dasp_signal/src/window/hanning.rs index c5db0f00..0d524cd8 100644 --- a/dasp_signal/src/window/hanning.rs +++ b/dasp_signal/src/window/hanning.rs @@ -1,6 +1,6 @@ +use super::{Window, Windower}; use dasp_frame::Frame; use dasp_window::hanning::Hanning; -use super::{Window, Windower}; impl<'a, F> Windower<'a, F, Hanning> where diff --git a/dasp_signal/src/window/rectangle.rs b/dasp_signal/src/window/rectangle.rs index c2e9c486..bd6c31a0 100644 --- a/dasp_signal/src/window/rectangle.rs +++ b/dasp_signal/src/window/rectangle.rs @@ -1,6 +1,6 @@ +use super::{Window, Windower}; use dasp_frame::Frame; use dasp_window::rectangle::Rectangle; -use super::{Window, Windower}; impl<'a, F> Windower<'a, F, Rectangle> where diff --git a/dasp_signal/tests/interpolate.rs b/dasp_signal/tests/interpolate.rs index 1f62b2c9..67ca9605 100644 --- a/dasp_signal/tests/interpolate.rs +++ b/dasp_signal/tests/interpolate.rs @@ -2,7 +2,7 @@ use dasp_interpolate::{floor::Floor, linear::Linear, sinc::Sinc}; use dasp_ring_buffer as ring_buffer; -use dasp_signal::{self as signal, Signal, interpolate::Converter}; +use dasp_signal::{self as signal, interpolate::Converter, Signal}; #[test] fn test_floor_converter() { @@ -67,7 +67,9 @@ fn test_sinc() { let resampled = source.from_hz_to_hz(interp, 44100.0, 11025.0); assert_eq!( - resampled.until_exhausted().find(|sample| sample[0].is_nan()), + resampled + .until_exhausted() + .find(|sample| sample[0].is_nan()), None ); } diff --git a/dasp_signal/tests/window.rs b/dasp_signal/tests/window.rs index 1258f222..ad761430 100644 --- a/dasp_signal/tests/window.rs +++ b/dasp_signal/tests/window.rs @@ -8,16 +8,7 @@ use dasp_signal::window::{self, Windower}; fn test_window_at_phase() { let window = window::hanning::<[f64; 1]>(9); let expected = [ - 0.0, - 0.1464, - 0.5000, - 0.8536, - 1., - 0.8536, - 0.5000, - 0.1464, - 0., - 0.1464, + 0.0, 0.1464, 0.5000, 0.8536, 1., 0.8536, 0.5000, 0.1464, 0., 0.1464, ]; for (r, e) in window.zip(&expected) { println!("Expected: {}\t\tFound: {}", e, r[0]); diff --git a/dasp_slice/src/boxed.rs b/dasp_slice/src/boxed.rs index 24df83dc..6f007bda 100644 --- a/dasp_slice/src/boxed.rs +++ b/dasp_slice/src/boxed.rs @@ -47,28 +47,25 @@ where } /// For converting to and from a boxed slice of `Sample`s. -pub trait DuplexBoxedSampleSlice - : FromBoxedSampleSlice + ToBoxedSampleSlice +pub trait DuplexBoxedSampleSlice: FromBoxedSampleSlice + ToBoxedSampleSlice where - S: Sample + S: Sample, { } /// For converting to and from a boxed slice of `Frame`s. -pub trait DuplexBoxedFrameSlice - : FromBoxedFrameSlice + ToBoxedFrameSlice +pub trait DuplexBoxedFrameSlice: FromBoxedFrameSlice + ToBoxedFrameSlice where - F: Frame + F: Frame, { } /// For converting to and from a boxed slice of `Sample`s of type `S` and a slice of `Frame`s of /// type `F`. -pub trait DuplexBoxedSlice - : DuplexBoxedSampleSlice + DuplexBoxedFrameSlice +pub trait DuplexBoxedSlice: DuplexBoxedSampleSlice + DuplexBoxedFrameSlice where S: Sample, - F: Frame + F: Frame, { } diff --git a/dasp_slice/src/frame/fixed_size_array.rs b/dasp_slice/src/frame/fixed_size_array.rs index d5a82b72..3724066a 100644 --- a/dasp_slice/src/frame/fixed_size_array.rs +++ b/dasp_slice/src/frame/fixed_size_array.rs @@ -1,13 +1,13 @@ //! Implementations of the slice conversion traits for slices of fixed-size-array frames. -use crate::{ - FromFrameSlice, FromFrameSliceMut, FromSampleSlice, FromSampleSliceMut, ToFrameSlice, - ToFrameSliceMut, ToSampleSlice, ToSampleSliceMut, -}; #[cfg(feature = "boxed")] use crate::boxed::{ Box, FromBoxedFrameSlice, FromBoxedSampleSlice, ToBoxedFrameSlice, ToBoxedSampleSlice, }; +use crate::{ + FromFrameSlice, FromFrameSliceMut, FromSampleSlice, FromSampleSliceMut, ToFrameSlice, + ToFrameSliceMut, ToSampleSlice, ToSampleSliceMut, +}; use dasp_frame::Frame; use dasp_sample::Sample; diff --git a/dasp_slice/src/frame/mod.rs b/dasp_slice/src/frame/mod.rs index d6578fed..99f90f58 100644 --- a/dasp_slice/src/frame/mod.rs +++ b/dasp_slice/src/frame/mod.rs @@ -37,19 +37,17 @@ where /// For converting to and from a slice of `Frame`s. pub trait DuplexFrameSlice<'a, F>: FromFrameSlice<'a, F> + ToFrameSlice<'a, F> where - F: Frame + F: Frame, { } /// For converting to and from a mutable slice of `Frame`s. -pub trait DuplexFrameSliceMut<'a, F> - : FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F> +pub trait DuplexFrameSliceMut<'a, F>: FromFrameSliceMut<'a, F> + ToFrameSliceMut<'a, F> where - F: Frame + F: Frame, { } - impl<'a, F> FromFrameSlice<'a, F> for &'a [F] where F: Frame, @@ -132,7 +130,6 @@ where slice.to_frame_slice() } - /// Converts the given mutable slice into a mutable slice of `Frame`s. /// /// Returns `None` if the number of channels in a single frame `F` is not a multiple of the number diff --git a/dasp_slice/src/lib.rs b/dasp_slice/src/lib.rs index 91d1180c..1fa71c7a 100644 --- a/dasp_slice/src/lib.rs +++ b/dasp_slice/src/lib.rs @@ -10,9 +10,9 @@ use dasp_sample::Sample; #[cfg(feature = "boxed")] pub use boxed::{ - FromBoxedSampleSlice, FromBoxedFrameSlice, ToBoxedSampleSlice, ToBoxedFrameSlice, - DuplexBoxedSampleSlice, DuplexBoxedFrameSlice, DuplexBoxedSlice, to_boxed_sample_slice, - to_boxed_frame_slice, from_boxed_sample_slice, from_boxed_frame_slice, + from_boxed_frame_slice, from_boxed_sample_slice, to_boxed_frame_slice, to_boxed_sample_slice, + DuplexBoxedFrameSlice, DuplexBoxedSampleSlice, DuplexBoxedSlice, FromBoxedFrameSlice, + FromBoxedSampleSlice, ToBoxedFrameSlice, ToBoxedSampleSlice, }; pub use frame::{ diff --git a/examples/play_wav.rs b/examples/play_wav.rs index e04d21a9..29e08392 100644 --- a/examples/play_wav.rs +++ b/examples/play_wav.rs @@ -5,7 +5,9 @@ use dasp::slice::ToFrameSliceMut; fn main() -> Result<(), anyhow::Error> { // Find and load the wav. - let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); + let assets = find_folder::Search::ParentsThenKids(5, 5) + .for_folder("assets") + .unwrap(); let reader = hound::WavReader::open(assets.join("thumbpiano A#3.wav")).unwrap(); let spec = reader.spec(); @@ -43,7 +45,7 @@ fn main() -> Result<(), anyhow::Error> { None => { complete_tx.try_send(()).ok(); *out_frame = dasp::Frame::equilibrium(); - }, + } } } }; diff --git a/examples/resample.rs b/examples/resample.rs index 15c9ed37..083d16b9 100644 --- a/examples/resample.rs +++ b/examples/resample.rs @@ -1,12 +1,14 @@ // An example of using `sample` to efficiently perform decent quality sample rate conversion on a // WAV file entirely on the stack. -use hound::{WavReader, WavWriter}; use dasp::{interpolate::sinc::Sinc, ring_buffer, signal, Sample, Signal}; +use hound::{WavReader, WavWriter}; fn main() { // Find and load the wav. - let assets = find_folder::Search::ParentsThenKids(5, 5).for_folder("assets").unwrap(); + let assets = find_folder::Search::ParentsThenKids(5, 5) + .for_folder("assets") + .unwrap(); let reader = WavReader::open(assets.join("two_vowels.wav")).unwrap(); // Get the wav spec and create a target with the new desired sample rate. @@ -15,7 +17,10 @@ fn main() { target.sample_rate = 10_000; // Read the interleaved samples and convert them to a signal. - let samples = reader.into_samples().filter_map(Result::ok).map(i16::to_sample::); + let samples = reader + .into_samples() + .filter_map(Result::ok) + .map(i16::to_sample::); let signal = signal::from_interleaved_samples_iter(samples); // Convert the signal's sample rate using `Sinc` interpolation. diff --git a/examples/test.rs b/examples/test.rs index 34f140b8..1c893e4c 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -47,7 +47,6 @@ fn main() { sample_i32 ); - let wave_f32 = sample_f32.to_sample::(); let wave_u8 = sample_u8.to_sample::(); let wave_u16 = sample_u16.to_sample::(); From bb73ff66069add3aaf0f0c0e3c981408c02f73ad Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:41:43 +0200 Subject: [PATCH 11/29] Use nightly for no_std CI builds. Fix no-default-features tests. --- .github/workflows/dasp.yml | 60 +++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dasp.yml b/.github/workflows/dasp.yml index ae5bc665..fc3ba357 100644 --- a/.github/workflows/dasp.yml +++ b/.github/workflows/dasp.yml @@ -50,13 +50,64 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: nightly override: true - - name: cargo test (no default features) + # Can't do `--no-default-features` for all pkgs, so do them one by one. + - name: cargo test dasp (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_envelope (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_envelope/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_frame (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_frame/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_interpolate (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_interpolate/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_peak (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_peak/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_ring_buffer (no default features) uses: actions-rs/cargo@v1 with: command: test - args: --all --no-default-features --verbose + args: --manifest-path dasp_ring_buffer/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_rms (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_rms/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_sample (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_sample/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_signal (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_signal/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_slice (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_slice/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_window (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_window/Cargo.toml --no-default-features --verbose cargo-test-all-features: runs-on: ubuntu-latest @@ -90,8 +141,9 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: nightly override: true + # Can't do `--no-default-features` or `--features` for all pkgs, so do them one by one. - name: cargo test dasp (all features no std) uses: actions-rs/cargo@v1 with: From d5b5f2376f9ff7a25e1a95387f4a64e0153fc52d Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:44:25 +0200 Subject: [PATCH 12/29] Remove resampled wav from repo. Add to gitignore. --- .gitignore | 1 + assets/two_vowels_10k.wav | Bin 56704 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 assets/two_vowels_10k.wav diff --git a/.gitignore b/.gitignore index 2c96eb1b..fd896f39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +assets/two_vowels_10k.wav target/ Cargo.lock diff --git a/assets/two_vowels_10k.wav b/assets/two_vowels_10k.wav deleted file mode 100644 index 97dbcc6c656274456c56743b5c3f3155b354dde5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56704 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Ð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 From 6090e7f9f14721a1553da73899e1efae6b3acd88 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:50:02 +0200 Subject: [PATCH 13/29] Formatting and link fixes for README --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 53eaae29..0be2bc01 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,14 @@ within this repository: | [**`dasp`**][dasp] | [![Crates.io][dasp-crates-io-svg]][dasp-crates-io] [![docs.rs][dasp-docs-rs-svg]][dasp-docs-rs] | Top-level API with features for all crates. | | [**`dasp_sample`**][dasp_sample] | [![Crates.io][dasp_sample-crates-io-svg]][dasp_sample-crates-io] [![docs.rs][dasp_sample-docs-rs-svg]][dasp_sample-docs-rs] | Sample trait, types, conversions and operations. | | [**`dasp_frame`**][dasp_frame] | [![Crates.io][dasp_frame-crates-io-svg]][dasp_frame-crates-io] [![docs.rs][dasp_frame-docs-rs-svg]][dasp_frame-docs-rs] | Frame trait, types, conversions and operations. | -| [**`dasp_slice`**][dasp_slice] | [![Crates.io][dasp_slice-crates-io-svg]][dasp_slice-crates-io] [![docs.rs][dasp_slice-docs-rs-svg]][dasp_slice-docs-rs] | Conversions and operations for slices of samples or frames. | +| [**`dasp_slice`**][dasp_slice] | [![Crates.io][dasp_slice-crates-io-svg]][dasp_slice-crates-io] [![docs.rs][dasp_slice-docs-rs-svg]][dasp_slice-docs-rs] | Conversions and ops for slices of samples/frames. | | [**`dasp_ring_buffer`**][dasp_ring_buffer] | [![Crates.io][dasp_ring_buffer-crates-io-svg]][dasp_ring_buffer-crates-io] [![docs.rs][dasp_ring_buffer-docs-rs-svg]][dasp_ring_buffer-docs-rs] | Simple fixed and bounded ring buffers. | -| [**`dasp_peak`**][dasp_peak] | [![Crates.io][dasp_peak-crates-io-svg]][dasp_peak-crates-io] [![docs.rs][dasp_peak-docs-rs-svg]][dasp_peak-docs-rs] | Peak detection with half/full positive/negative wave rectifiers. | +| [**`dasp_peak`**][dasp_peak] | [![Crates.io][dasp_peak-crates-io-svg]][dasp_peak-crates-io] [![docs.rs][dasp_peak-docs-rs-svg]][dasp_peak-docs-rs] | Peak detection with half/full pos/neg wave rectifiers. | | [**`dasp_rms`**][dasp_rms] | [![Crates.io][dasp_rms-crates-io-svg]][dasp_rms-crates-io] [![docs.rs][dasp_rms-docs-rs-svg]][dasp_rms-docs-rs] | RMS detection with configurable window. | -| [**`dasp_envelope`**][dasp_envelope] | [![Crates.io][dasp_envelope-crates-io-svg]][dasp_envelope-crates-io] [![docs.rs][dasp_envelope-docs-rs-svg]][dasp_envelope-docs-rs] | Envelope detection abstraction with peak and RMS implementations. | -| [**`dasp_interpolate`**][dasp_interpolate] | [![Crates.io][dasp_interpolate-crates-io-svg]][dasp_interpolate-crates-io] [![docs.rs][dasp_interpolate-docs-rs-svg]][dasp_interpolate-docs-rs] | Abstraction for frame interpolation (provides linear, sinc and more). | -| [**`dasp_window`**][dasp_window] | [![Crates.io][dasp_window-crates-io-svg]][dasp_window-crates-io] [![docs.rs][dasp_window-docs-rs-svg]][dasp_window-docs-rs] | Windowing abstraction with provided hanning and rectangle functions. | -| [**`dasp_signal`**][dasp_signal] | [![Crates.io][dasp_signal-crates-io-svg]][dasp_signal-crates-io] [![docs.rs][dasp_signal-docs-rs-svg]][dasp_signal-docs-rs] | An iterator-like API for working with streams of audio frames. | +| [**`dasp_envelope`**][dasp_envelope] | [![Crates.io][dasp_envelope-crates-io-svg]][dasp_envelope-crates-io] [![docs.rs][dasp_envelope-docs-rs-svg]][dasp_envelope-docs-rs] | Envelope detection with peak and RMS impls. | +| [**`dasp_interpolate`**][dasp_interpolate] | [![Crates.io][dasp_interpolate-crates-io-svg]][dasp_interpolate-crates-io] [![docs.rs][dasp_interpolate-docs-rs-svg]][dasp_interpolate-docs-rs] | Inter-frame rate interpolation (linear, sinc, etc). | +| [**`dasp_window`**][dasp_window] | [![Crates.io][dasp_window-crates-io-svg]][dasp_window-crates-io] [![docs.rs][dasp_window-docs-rs-svg]][dasp_window-docs-rs] | Windowing function abstraction (hanning, rectangle). | +| [**`dasp_signal`**][dasp_signal] | [![Crates.io][dasp_signal-crates-io-svg]][dasp_signal-crates-io] [![docs.rs][dasp_signal-docs-rs-svg]][dasp_signal-docs-rs] | Iterator-like API for streams of audio frames. | [![deps-graph][deps-graph]][deps-graph] @@ -71,7 +71,7 @@ for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -[deps-graph]: https://github.com/RustAudio/sample/blob/master/assets/deps-graph.png +[deps-graph]: ./assets/deps-graph.png [dasp]: ./dasp [dasp-crates-io]: https://crates.io/crates/dasp From 5f38099f07ca4709e60057665ad1f598870aa462 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 19:56:26 +0200 Subject: [PATCH 14/29] Add action status badge to top of top-level README --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0be2bc01..8c524ea4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# dasp +# dasp [![Actions Status][dasp-actions-svg]][dasp-actions] **Digital Audio Signal Processing in Rust.** @@ -30,8 +30,8 @@ within this repository: [![deps-graph][deps-graph]][deps-graph] -*Red dotted lines indicate optional use, while black lines indicate required -dependencies.* +*Red dotted lines indicate optional dependencies, while black lines indicate +required dependencies.* ### Features @@ -50,9 +50,9 @@ To enable all of a crate's features *without* the std library, you may use ## Contributing -If the **sample** crate is missing types, conversions or other fundamental -functionality that you wish it had, feel free to open an issue or pull request! -The more hands on deck, the merrier :) +If **dasp** is missing types, conversions or other fundamental functionality +that you wish it had, feel free to open an issue or pull request! The more +hands on deck, the merrier :) ## License @@ -71,8 +71,9 @@ for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +[dasp-actions]: https://github.com/nannou-org/dasp/actions +[dasp-actions-svg]: https://github.com/rustaudio/dasp/workflows/dasp/badge.svg [deps-graph]: ./assets/deps-graph.png - [dasp]: ./dasp [dasp-crates-io]: https://crates.io/crates/dasp [dasp-crates-io-svg]: https://img.shields.io/crates/v/dasp.svg From 32776adda61c9a9241f0ddef9b69656a0fbb7deb Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 20:14:01 +0200 Subject: [PATCH 15/29] Remove remaining readme content from dasp_sample to top-level --- README.md | 156 +++++++++++++++++++++++++++++++- dasp_sample/README.md | 201 ------------------------------------------ 2 files changed, 152 insertions(+), 205 deletions(-) delete mode 100644 dasp_sample/README.md diff --git a/README.md b/README.md index 8c524ea4..5a87e76f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# dasp [![Actions Status][dasp-actions-svg]][dasp-actions] +# dasp [![Actions Status][dasp-actions-svg]][dasp-actions] [![docs.rs][dasp-docs-rs-svg]][dasp-docs-rs] **Digital Audio Signal Processing in Rust.** @@ -7,6 +7,15 @@ modulation) DSP (digital signal processing). In other words, `dasp` provides a suite of low-level, high-performance tools including types, traits and functions for working with digital audio signals. +The `dasp` libraries require **no dynamic allocations**1 and have +**no dependencies**. The goal is to design a library akin to the **std, but for +audio DSP**; keeping the focus on portable and fast fundamentals. + +1: Besides the feature-gated `SignalBus` trait, which is occasionally +useful when converting a `Signal` tree into a directed acyclic graph. + +Find the [API documentation here][dasp-docs-rs]. + ## Crates @@ -34,12 +43,146 @@ within this repository: required dependencies.* -### Features +## Features + +Use the **Sample** trait to convert between and remain generic over any +bit-depth in an optimal, performance-sensitive manner. Implementations are +provided for all signed integer, unsigned integer and floating point primitive +types along with some custom types including 11, 20, 24 and 48-bit signed and +unsigned unpacked integers. For example: + +```rust +assert_eq!((-1.0).to_sample::(), 0); +assert_eq!(0.0.to_sample::(), 128); +assert_eq!(0i32.to_sample::(), 2_147_483_648); +assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); +assert_eq!(0.0, Sample::equilibrium()); +``` + +Use the **Frame** trait to remain generic over the number of channels at a +discrete moment in time. Implementations are provided for all fixed-size arrays +up to 32 elements in length. + +```rust +let foo = [0.1, 0.2, -0.1, -0.2]; +let bar = foo.scale_amp(2.0); +assert_eq!(bar, [0.2, 0.4, -0.2, -0.4]); + +assert_eq!(Mono::::equilibrium(), [0.0]); +assert_eq!(Stereo::::equilibrium(), [0.0, 0.0]); +assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); + +let foo = [0i16, 0]; +let bar: [u8; 2] = foo.map(Sample::to_sample); +assert_eq!(bar, [128u8, 128]); +``` + +Use the **Signal** trait (enabled by the "signal" feature) for working with +infinite-iterator-like types that yield `Frame`s. **Signal** provides methods +for adding, scaling, offsetting, multiplying, clipping, generating, monitoring +and buffering streams of `Frame`s. Working with **Signal**s allows for easy, +readable creation of rich and complex DSP graphs with a simple and familiar API. + +```rust +// Clip to an amplitude of 0.9. +let frames = [[1.2, 0.8], [-0.7, -1.4]]; +let clipped: Vec<_> = signal::from_iter(frames.iter().cloned()).clip_amp(0.9).take(2).collect(); +assert_eq!(clipped, vec![[0.9, 0.8], [-0.7, -0.9]]); + +// Add `a` with `b` and yield the result. +let a = [[0.2], [-0.6], [0.5]]; +let b = [[0.2], [0.1], [-0.8]]; +let a_signal = signal::from_iter(a.iter().cloned()); +let b_signal = signal::from_iter(b.iter().cloned()); +let added: Vec<[f32; 1]> = a_signal.add_amp(b_signal).take(3).collect(); +assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); + +// Scale the playback rate by `0.5`. +let foo = [[0.0], [1.0], [0.0], [-1.0]]; +let mut source = signal::from_iter(foo.iter().cloned()); +let a = source.next(); +let b = source.next(); +let interp = Linear::new(a, b); +let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); +assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); -TODO +// Convert a signal to its RMS. +let signal = signal::rate(44_100.0).const_hz(440.0).sine();; +let ring_buffer = ring_buffer::Fixed::from([[0.0]; WINDOW_SIZE]); +let mut rms_signal = signal.rms(ring_buffer); +``` +The **signal** module also provides a series of **Signal** source types, +including: -### `no_std` +- `FromIterator` +- `FromInterleavedSamplesIterator` +- `Equilibrium` (silent signal) +- `Phase` +- `Sine` +- `Saw` +- `Square` +- `Noise` +- `NoiseSimplex` +- `Gen` (generate frames from a Fn() -> F) +- `GenMut` (generate frames from a FnMut() -> F) + +Use the **slice** module functions (enabled via the "slice" feature) for +processing chunks of `Frame`s. Conversion functions are provided for safely +converting between slices of interleaved `Sample`s and slices of `Frame`s +without requiring any allocation. For example: + +```rust +let frames = &[[0.0, 0.5], [0.0, -0.5]][..]; +let samples = slice::to_sample_slice(frames); +assert_eq!(samples, &[0.0, 0.5, 0.0, -0.5][..]); + +let samples = &[0.0, 0.5, 0.0, -0.5][..]; +let frames = slice::to_frame_slice(samples); +assert_eq!(frames, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); + +let samples = &[0.0, 0.5, 0.0][..]; +let frames = slice::to_frame_slice(samples); +assert_eq!(frames, None::<&[[f32; 2]]>); +``` + +The **signal::interpolate** module provides a **Converter** type, for converting +and interpolating the rate of **Signal**s. This can be useful for both sample +rate conversion and playback rate multiplication. **Converter**s can use a range +of interpolation methods, with Floor, Linear, and Sinc interpolation provided in +the library. + +The **ring_buffer** module provides generic **Fixed** and **Bounded** ring +buffer types, both of which may be used with owned, borrowed, stack and +allocated buffers. + +The **peak** module can be used for monitoring the peak of a signal. Provided +peak rectifiers include `full_wave`, `positive_half_wave` and +`negative_half_wave`. + +The **rms** module provides a flexible **Rms** type that can be used for RMS +(root mean square) detection. Any **Fixed** ring buffer can be used as the +window for the RMS detection. + +The **envelope** module provides a **Detector** type (also known as a +*Follower*) that allows for detecting the envelope of a signal. **Detector** is +generic over the type of **Detect**ion - **Rms** and **Peak** detection are +provided. For example: + +```rust +let signal = signal::rate(4.0).const_hz(1.0).sine(); +let attack = 1.0; +let release = 1.0; +let detector = envelope::Detector::peak(attack, release); +let mut envelope = signal.detect_envelope(detector); +assert_eq!( + envelope.take(4).collect::>(), + vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] +); +``` + + +## `no_std` All crates may be compiled with and without the std library. The std library is enabled by default, however it may be disabled via `--no-default-features`. @@ -47,6 +190,11 @@ enabled by default, however it may be disabled via `--no-default-features`. To enable all of a crate's features *without* the std library, you may use `--no-default-features --features "all-features-no-std"`. +Please note that some of the crates require the `core_intrinsics` feature in +order to be able to perform operations like `sin`, `cos` and `powf32` in a +`no_std` context. This means that these crates require the nightly toolchain in +order to build in a `no_std` context. + ## Contributing diff --git a/dasp_sample/README.md b/dasp_sample/README.md deleted file mode 100644 index 7781c079..00000000 --- a/dasp_sample/README.md +++ /dev/null @@ -1,201 +0,0 @@ -# sample [![Build Status](https://travis-ci.org/RustAudio/sample.svg?branch=master)](https://travis-ci.org/RustAudio/sample) [![Crates.io](https://img.shields.io/crates/v/sample.svg)](https://crates.io/crates/sample) [![Crates.io](https://img.shields.io/crates/l/sample.svg)](https://github.com/RustAudio/sample/blob/master/LICENSE-MIT) [![docs.rs](https://docs.rs/sample/badge.svg)](https://docs.rs/sample/) - -A crate providing the fundamentals for working with PCM (pulse-code modulation) -DSP (digital signal processing). In other words, `sample` provides a suite of -low-level, high-performance tools including types, traits and functions for -working with digital audio signals. - -The `sample` crate requires **no dynamic allocations**1 and has **no -dependencies**. The goal is to design a library akin to the **std, but for audio -DSP**; keeping the focus on portable and fast fundamentals. - -1: Besides the `Signal::bus` method, which is only necessary when -converting a `Signal` tree into a directed acyclic graph. - -Find the [API documentation here](https://docs.rs/sample/). - - -Features --------- - -Use the **Sample** trait to convert between and remain generic over any -bit-depth in an optimal, performance-sensitive manner. Implementations are -provided for all signed integer, unsigned integer and floating point primitive -types along with some custom types including 11, 20, 24 and 48-bit signed and -unsigned unpacked integers. For example: - -```rust -assert_eq!((-1.0).to_sample::(), 0); -assert_eq!(0.0.to_sample::(), 128); -assert_eq!(0i32.to_sample::(), 2_147_483_648); -assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); -assert_eq!(0.0, Sample::equilibrium()); -``` - -Use the **Frame** trait to remain generic over the number of channels at a -discrete moment in time. Implementations are provided for all fixed-size arrays -up to 32 elements in length. - -```rust -let foo = [0.1, 0.2, -0.1, -0.2]; -let bar = foo.scale_amp(2.0); -assert_eq!(bar, [0.2, 0.4, -0.2, -0.4]); - -assert_eq!(Mono::::equilibrium(), [0.0]); -assert_eq!(Stereo::::equilibrium(), [0.0, 0.0]); -assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); - -let foo = [0i16, 0]; -let bar: [u8; 2] = foo.map(Sample::to_sample); -assert_eq!(bar, [128u8, 128]); -``` - -Use the **Signal** trait for working with infinite-iterator-like types that -yield `Frame`s. **Signal** provides methods for adding, scaling, offsetting, -multiplying, clipping, generating, monitoring and buffering streams of `Frame`s. -Working with **Signal**s allows for easy, readable creation of rich and complex -DSP graphs with a simple and familiar API. - -```rust -// Clip to an amplitude of 0.9. -let frames = [[1.2, 0.8], [-0.7, -1.4]]; -let clipped: Vec<_> = signal::from_iter(frames.iter().cloned()).clip_amp(0.9).take(2).collect(); -assert_eq!(clipped, vec![[0.9, 0.8], [-0.7, -0.9]]); - -// Add `a` with `b` and yield the result. -let a = [[0.2], [-0.6], [0.5]]; -let b = [[0.2], [0.1], [-0.8]]; -let a_signal = signal::from_iter(a.iter().cloned()); -let b_signal = signal::from_iter(b.iter().cloned()); -let added: Vec<[f32; 1]> = a_signal.add_amp(b_signal).take(3).collect(); -assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); - -// Scale the playback rate by `0.5`. -let foo = [[0.0], [1.0], [0.0], [-1.0]]; -let mut source = signal::from_iter(foo.iter().cloned()); -let interp = Linear::from_source(&mut source); -let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); -assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); - -// Convert a signal to its RMS. -let signal = signal::rate(44_100.0).const_hz(440.0).sine();; -let ring_buffer = ring_buffer::Fixed::from([[0.0]; WINDOW_SIZE]); -let mut rms_signal = signal.rms(ring_buffer); -``` - -The **signal** module also provides a series of **Signal** source types, -including: - -- `FromIterator` -- `FromInterleavedSamplesIterator` -- `Equilibrium` (silent signal) -- `Phase` -- `Sine` -- `Saw` -- `Square` -- `Noise` -- `NoiseSimplex` -- `Gen` (generate frames from a Fn() -> F) -- `GenMut` (generate frames from a FnMut() -> F) - -Use the **slice** module functions for processing chunks of `Frame`s. -Conversion functions are provided for safely converting between slices of -interleaved `Sample`s and slices of `Frame`s without requiring any allocation. -For example: - -```rust -let frames = &[[0.0, 0.5], [0.0, -0.5]][..]; -let samples = sample::slice::to_sample_slice(frames); -assert_eq!(samples, &[0.0, 0.5, 0.0, -0.5][..]); - -let samples = &[0.0, 0.5, 0.0, -0.5][..]; -let frames = sample::slice::to_frame_slice(samples); -assert_eq!(frames, Some(&[[0.0, 0.5], [0.0, -0.5]][..])); - -let samples = &[0.0, 0.5, 0.0][..]; -let frames = sample::slice::to_frame_slice(samples); -assert_eq!(frames, None::<&[[f32; 2]]>); -``` - -The **conv** module provides pure functions and traits for more specific -conversions. A function is provided for converting between every possible pair -of sample types. Traits include: - -- `FromSample`, `ToSample`, `Duplex`, -- `FromSampleSlice`, `ToSampleSlice`, `DuplexSampleSlice`, -- `FromSampleSliceMut`, `ToSampleSliceMut`, `DuplexSampleSliceMut`, -- `FromFrameSlice`, `ToFrameSlice`, `DuplexFrameSlice`, -- `FromFrameSliceMut`, `ToFrameSliceMut`, `DuplexFrameSliceMut`, -- `DuplexSlice`, `DuplexSliceMut`, - -The **interpolate** module provides a **Converter** type, for converting and -interpolating the rate of **Signal**s. This can be useful for both sample rate -conversion and playback rate multiplication. **Converter**s can use a range of -interpolation methods, with Floor, Linear, and Sinc interpolation provided in -the library. (NB: Sinc interpolation currently requires heap allocation, as it -uses VecDeque.) - -The **ring_buffer** module provides generic **Fixed** and **Bounded** ring -buffer types, both of which may be used with owned, borrowed, stack and -allocated buffers. - -The **peak** module can be used for monitoring the peak of a signal. Provided -peak rectifiers include `full_wave`, `positive_half_wave` and -`negative_half_wave`. - -The **rms** module provides a flexible **Rms** type that can be used for RMS -(root mean square) detection. Any **Fixed** ring buffer can be used as the -window for the RMS detection. - -The **envelope** module provides a **Detector** type (also known as a -*Follower*) that allows for detecting the envelope of a signal. **Detector** is -generic over the type of **Detect**ion - **Rms** and **Peak** detection are -provided. For example: - -```rust -let signal = signal::rate(4.0).const_hz(1.0).sine(); -let attack = 1.0; -let release = 1.0; -let detector = envelope::Detector::peak(attack, release); -let mut envelope = signal.detect_envelope(detector); -assert_eq!( - envelope.take(4).collect::>(), - vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] -); -``` - - -Using in a `no_std` environment -------------------------------- - -This crate is largely dependency free, even of things outside `core`. The -`no_std` cargo feature will enable using `sample` in these environments. -Currently, only nightly is supported, because it explicitly depends on the -`alloc` and `collections` for datastructures and `core_intrinsics` for some of -the math. If this restriction is onerous for you, it can be lifted with minor -loss of functionality (the `Signal::bus` method), so open an issue! - - -Contributions -------------- - -If the **sample** crate is missing types, conversions or other fundamental -functionality that you wish it had, feel free to open an issue or pull request! -The more hands on deck, the merrier :) - - -License -------- - -Licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -**Contributions** - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. From 895d25fdfb48787f1b9979119432aedcf0c19743 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 20:19:38 +0200 Subject: [PATCH 16/29] Fix dasp_frame mul_amp example --- dasp_frame/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dasp_frame/src/lib.rs b/dasp_frame/src/lib.rs index 5c878c03..b8cac513 100644 --- a/dasp_frame/src/lib.rs +++ b/dasp_frame/src/lib.rs @@ -211,8 +211,8 @@ pub trait Frame: Copy + Clone + PartialEq { /// let foo = [0.25, 0.4].mul_amp([0.2, 0.5]); /// assert_eq!(foo, [0.05, 0.2]); /// - /// let bar = [192u8, 64].mul_amp([0.0, -2.0]); - /// assert_eq!(bar, [128, 0]); + /// let bar = [192u8, 64].mul_amp([0.0, -1.0]); + /// assert_eq!(bar, [128, 192]); /// } /// ``` #[inline] From 27590daf71bd95fc447deec9694ea6f11648af1b Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 20:36:11 +0200 Subject: [PATCH 17/29] Prepare crates for publishing under version 0.11.0. Rather than starting from version 0.1.0, I thought it would make sense for versioning to continue on from where the `sample` crate left off. --- dasp/Cargo.toml | 22 +++++++++++----------- dasp_envelope/Cargo.toml | 12 ++++++------ dasp_frame/Cargo.toml | 4 ++-- dasp_interpolate/Cargo.toml | 8 ++++---- dasp_peak/Cargo.toml | 6 +++--- dasp_ring_buffer/Cargo.toml | 2 +- dasp_rms/Cargo.toml | 8 ++++---- dasp_sample/Cargo.toml | 2 +- dasp_signal/Cargo.toml | 24 ++++++++++++------------ dasp_slice/Cargo.toml | 6 +++--- dasp_window/Cargo.toml | 4 ++-- examples/Cargo.toml | 2 +- 12 files changed, 50 insertions(+), 50 deletions(-) diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 6a1d283f..6d808a4f 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "dasp" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_envelope = { path = "../dasp_envelope", default-features = false, optional = true } -dasp_frame = { path = "../dasp_frame", default-features = false } -dasp_interpolate = { path = "../dasp_interpolate", default-features = false, optional = true } -dasp_peak = { path = "../dasp_peak", default-features = false, optional = true } -dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false, optional = true } -dasp_rms = { path = "../dasp_rms", default-features = false, optional = true } -dasp_sample = { path = "../dasp_sample", default-features = false } -dasp_signal = { path = "../dasp_signal", default-features = false, optional = true } -dasp_slice = { path = "../dasp_slice", default-features = false, optional = true } -dasp_window = { path = "../dasp_window", default-features = false, optional = true } +dasp_envelope = { version = "0.11", path = "../dasp_envelope", default-features = false, optional = true } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } +dasp_interpolate = { version = "0.11", path = "../dasp_interpolate", default-features = false, optional = true } +dasp_peak = { version = "0.11", path = "../dasp_peak", default-features = false, optional = true } +dasp_ring_buffer = { version = "0.11", path = "../dasp_ring_buffer", default-features = false, optional = true } +dasp_rms = { version = "0.11", path = "../dasp_rms", default-features = false, optional = true } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } +dasp_signal = { version = "0.11", path = "../dasp_signal", default-features = false, optional = true } +dasp_slice = { version = "0.11", path = "../dasp_slice", default-features = false, optional = true } +dasp_window = { version = "0.11", path = "../dasp_window", default-features = false, optional = true } [features] default = ["std"] diff --git a/dasp_envelope/Cargo.toml b/dasp_envelope/Cargo.toml index 81fb737f..90c0f9bc 100644 --- a/dasp_envelope/Cargo.toml +++ b/dasp_envelope/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "dasp_envelope" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_frame = { path = "../dasp_frame", default-features = false } -dasp_peak = { path = "../dasp_peak", default-features = false, optional = true } -dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } -dasp_rms = { path = "../dasp_rms", default-features = false, optional = true } -dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } +dasp_peak = { version = "0.11", path = "../dasp_peak", default-features = false, optional = true } +dasp_ring_buffer = { version = "0.11", path = "../dasp_ring_buffer", default-features = false } +dasp_rms = { version = "0.11", path = "../dasp_rms", default-features = false, optional = true } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } [features] default = ["std"] diff --git a/dasp_frame/Cargo.toml b/dasp_frame/Cargo.toml index 321b758a..34994d06 100644 --- a/dasp_frame/Cargo.toml +++ b/dasp_frame/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "dasp_frame" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } [features] default = ["std"] diff --git a/dasp_interpolate/Cargo.toml b/dasp_interpolate/Cargo.toml index 07bdfed7..86307a43 100644 --- a/dasp_interpolate/Cargo.toml +++ b/dasp_interpolate/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "dasp_interpolate" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_frame = { path = "../dasp_frame", default-features = false } -dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } -dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } +dasp_ring_buffer = { version = "0.11", path = "../dasp_ring_buffer", default-features = false } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } [features] default = ["std"] diff --git a/dasp_peak/Cargo.toml b/dasp_peak/Cargo.toml index 5dbd6c03..5a74b5ca 100644 --- a/dasp_peak/Cargo.toml +++ b/dasp_peak/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "dasp_peak" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_frame = { path = "../dasp_frame", default-features = false } -dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } [features] default = ["std"] diff --git a/dasp_ring_buffer/Cargo.toml b/dasp_ring_buffer/Cargo.toml index deb04f17..efbec4b0 100644 --- a/dasp_ring_buffer/Cargo.toml +++ b/dasp_ring_buffer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dasp_ring_buffer" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" diff --git a/dasp_rms/Cargo.toml b/dasp_rms/Cargo.toml index 3602c4da..adbef0c5 100644 --- a/dasp_rms/Cargo.toml +++ b/dasp_rms/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "dasp_rms" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_frame = { path = "../dasp_frame", default-features = false } -dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } -dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } +dasp_ring_buffer = { version = "0.11", path = "../dasp_ring_buffer", default-features = false } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } [features] default = ["std"] diff --git a/dasp_sample/Cargo.toml b/dasp_sample/Cargo.toml index 466a7bbc..c475b727 100644 --- a/dasp_sample/Cargo.toml +++ b/dasp_sample/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dasp_sample" description = "A crate providing the fundamentals for working with audio PCM DSP." -version = "0.10.0" +version = "0.11.0" authors = ["mitchmindtree "] readme = "README.md" keywords = ["dsp", "bit-depth", "rate", "pcm", "audio"] diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml index 13cccc87..3925010a 100644 --- a/dasp_signal/Cargo.toml +++ b/dasp_signal/Cargo.toml @@ -1,23 +1,23 @@ [package] name = "dasp_signal" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_envelope = { path = "../dasp_envelope", default-features = false, optional = true } -dasp_frame = { path = "../dasp_frame", default-features = false } -dasp_interpolate = { path = "../dasp_interpolate", default-features = false } -dasp_peak = { path = "../dasp_peak", default-features = false } -dasp_ring_buffer = { path = "../dasp_ring_buffer", default-features = false } -dasp_rms = { path = "../dasp_rms", default-features = false, optional = true } -dasp_sample = { path = "../dasp_sample", default-features = false } -dasp_window = { path = "../dasp_window", default-features = false, optional = true } +dasp_envelope = { version = "0.11", path = "../dasp_envelope", default-features = false, optional = true } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } +dasp_interpolate = { version = "0.11", path = "../dasp_interpolate", default-features = false } +dasp_peak = { version = "0.11", path = "../dasp_peak", default-features = false } +dasp_ring_buffer = { version = "0.11", path = "../dasp_ring_buffer", default-features = false } +dasp_rms = { version = "0.11", path = "../dasp_rms", default-features = false, optional = true } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } +dasp_window = { version = "0.11", path = "../dasp_window", default-features = false, optional = true } [dev-dependencies] -dasp_envelope = { path = "../dasp_envelope", default-features = false, features = ["peak"] } -dasp_interpolate = { path = "../dasp_interpolate", default-features = false, features = ["floor", "linear", "sinc"] } -dasp_window = { path = "../dasp_window", default-features = false, features = ["hanning"] } +dasp_envelope = { version = "0.11", path = "../dasp_envelope", default-features = false, features = ["peak"] } +dasp_interpolate = { version = "0.11", path = "../dasp_interpolate", default-features = false, features = ["floor", "linear", "sinc"] } +dasp_window = { version = "0.11", path = "../dasp_window", default-features = false, features = ["hanning"] } [features] default = ["std"] diff --git a/dasp_slice/Cargo.toml b/dasp_slice/Cargo.toml index 6b908336..f6f462a2 100644 --- a/dasp_slice/Cargo.toml +++ b/dasp_slice/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "dasp_slice" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_sample = { path = "../dasp_sample", default-features = false } -dasp_frame = { path = "../dasp_frame", default-features = false } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } +dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } [features] default = ["std"] diff --git a/dasp_window/Cargo.toml b/dasp_window/Cargo.toml index 3036f254..b45995c4 100644 --- a/dasp_window/Cargo.toml +++ b/dasp_window/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "dasp_window" -version = "0.1.0" +version = "0.11.0" authors = ["mitchmindtree "] edition = "2018" [dependencies] -dasp_sample = { path = "../dasp_sample", default-features = false } +dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = false } [features] default = ["std"] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 99bb2b60..db5190c8 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] anyhow = "1" cpal = { git = "https://github.com/rustaudio/cpal", branch = "master" } -dasp = { path = "../dasp", features = ["slice", "interpolate", "interpolate-sinc", "ring_buffer", "signal"] } +dasp = { version = "0.11", path = "../dasp", features = ["slice", "interpolate", "interpolate-sinc", "ring_buffer", "signal"] } find_folder = "0.3" hound = "3" From 8a62a9844b66e3ce8456153929a052e304bf5aca Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 20:53:17 +0200 Subject: [PATCH 18/29] Complete missing entries from new dasp cargo manifests --- dasp/Cargo.toml | 6 ++++++ dasp_envelope/Cargo.toml | 6 ++++++ dasp_frame/Cargo.toml | 6 ++++++ dasp_interpolate/Cargo.toml | 6 ++++++ dasp_peak/Cargo.toml | 6 ++++++ dasp_ring_buffer/Cargo.toml | 6 ++++++ dasp_rms/Cargo.toml | 6 ++++++ dasp_sample/Cargo.toml | 10 +++++----- dasp_signal/Cargo.toml | 6 ++++++ dasp_slice/Cargo.toml | 6 ++++++ dasp_window/Cargo.toml | 6 ++++++ 11 files changed, 65 insertions(+), 5 deletions(-) diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 6d808a4f..4d2865b1 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp" +description = "A crate providing the fundamentals for working with audio PCM DSP." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "bit-depth", "rate", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_envelope/Cargo.toml b/dasp_envelope/Cargo.toml index 90c0f9bc..20d2f65c 100644 --- a/dasp_envelope/Cargo.toml +++ b/dasp_envelope/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_envelope" version = "0.11.0" +description = "Audio PCM DSP envelope detection with peak and RMS implementations." authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["envelope", "detector", "follower", "peak", "rms"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_frame/Cargo.toml b/dasp_frame/Cargo.toml index 34994d06..ce3ff5a7 100644 --- a/dasp_frame/Cargo.toml +++ b/dasp_frame/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_frame" +description = "An abstraction for audio PCM DSP frames, along with useful conversions and operations." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "frame", "channel", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_interpolate/Cargo.toml b/dasp_interpolate/Cargo.toml index 86307a43..c7a052f5 100644 --- a/dasp_interpolate/Cargo.toml +++ b/dasp_interpolate/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_interpolate" +description = "An abstraction for audio PCM DSP rate interpolation, including floor, linear and sinc." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "interpolate", "sample", "rate", "pcm"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_peak/Cargo.toml b/dasp_peak/Cargo.toml index 5a74b5ca..90560ea9 100644 --- a/dasp_peak/Cargo.toml +++ b/dasp_peak/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_peak" +description = "A DSP peak detection library generic over the rectifier." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["peak", "rectifier", "full", "half", "wave"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_ring_buffer/Cargo.toml b/dasp_ring_buffer/Cargo.toml index efbec4b0..58f000f7 100644 --- a/dasp_ring_buffer/Cargo.toml +++ b/dasp_ring_buffer/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_ring_buffer" +description = "Simple fixed and bounded ring buffers for audio PCM DSP." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["ring", "buffer", "dsp", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [features] diff --git a/dasp_rms/Cargo.toml b/dasp_rms/Cargo.toml index adbef0c5..218a6cb0 100644 --- a/dasp_rms/Cargo.toml +++ b/dasp_rms/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_rms" +description = "RMS detection with configurable window for audio PCM DSP." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "rms", "envelope", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_sample/Cargo.toml b/dasp_sample/Cargo.toml index c475b727..1849abe7 100644 --- a/dasp_sample/Cargo.toml +++ b/dasp_sample/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "dasp_sample" -description = "A crate providing the fundamentals for working with audio PCM DSP." +description = "An abstraction for audio PCM DSP samples, along with useful conversions and operations." version = "0.11.0" authors = ["mitchmindtree "] -readme = "README.md" -keywords = ["dsp", "bit-depth", "rate", "pcm", "audio"] +readme = "../README.md" +keywords = ["dsp", "bit-depth", "sample", "pcm", "audio"] license = "MIT OR Apache-2.0" -repository = "https://github.com/RustAudio/sample.git" -homepage = "https://github.com/RustAudio/sample" +repository = "https://github.com/rustaudio/sample.git" +homepage = "https://github.com/rustaudio/sample" edition = "2018" [features] diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml index 3925010a..2592612a 100644 --- a/dasp_signal/Cargo.toml +++ b/dasp_signal/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_signal" +description = "An iterator-like API for audio PCM DSP streams." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "signal", "rate", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_slice/Cargo.toml b/dasp_slice/Cargo.toml index f6f462a2..9f2d63ba 100644 --- a/dasp_slice/Cargo.toml +++ b/dasp_slice/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_slice" +description = "Conversions and operations for slices of audio PCM DSP samples and frames." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "bit-depth", "rate", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] diff --git a/dasp_window/Cargo.toml b/dasp_window/Cargo.toml index b45995c4..908956a5 100644 --- a/dasp_window/Cargo.toml +++ b/dasp_window/Cargo.toml @@ -1,7 +1,13 @@ [package] name = "dasp_window" +description = "Windowing function abstractions (e.g. hanning, rectangle) for audio PCM DSP." version = "0.11.0" authors = ["mitchmindtree "] +readme = "../README.md" +keywords = ["dsp", "window", "hanning", "pcm", "audio"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" edition = "2018" [dependencies] From e2d1b15f4a1cc43fe2d0258642dbcf382353aad8 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 22:17:14 +0200 Subject: [PATCH 19/29] Add missing no_std attr to dasp --- dasp/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index 9f1aab00..d38dcab1 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -9,6 +9,8 @@ //! - See the [**interpolate** module](./interpolate/index.html) for sample rate conversion and scaling. //! - See the [**ring_buffer** module](./ring_buffer/index.html) for fast FIFO queue options. +#![cfg_attr(not(feature = "std"), no_std)] + #[cfg(feature = "envelope")] #[doc(inline)] pub use dasp_envelope as envelope; From 06d8822b94870de3943c85e29dccf037f25dac70 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 22:17:33 +0200 Subject: [PATCH 20/29] Enable all features for dasp_ring_buffer docs.rs metadata --- dasp_ring_buffer/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dasp_ring_buffer/Cargo.toml b/dasp_ring_buffer/Cargo.toml index 58f000f7..18d80e7a 100644 --- a/dasp_ring_buffer/Cargo.toml +++ b/dasp_ring_buffer/Cargo.toml @@ -13,3 +13,6 @@ edition = "2018" [features] default = ["std"] std = [] + +[package.metadata.docs.rs] +all-features = true From 52b60e44a894f3eb08bcf9f190257b451f2afd57 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 28 May 2020 22:17:41 +0200 Subject: [PATCH 21/29] Add a CHANGELOG --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..450c0c2e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Unreleased + +*No unreleased changes as of yet.* + +--- + +# 0.11.0 (2020-05-29) + +- Refactor the `sample` crate into a modular collection of crates under `dasp`. +- Rename repository from `sample` to `dasp`, where `dasp` stands for digital + audio signal processing. +- Add a suite of useful feature gates: + - Add `std` to all crates. Can be disabled in order to use `no_std`. + - Add a `all-features-no-std` feature to `dasp`, `dasp_envelope`, + `dasp_interpolate`, `dasp_signal`, `dasp_slice` and `dasp_window`. Enables + all features within a `no_std` context. + - `dasp_envelope` crate: + - `peak` - enables peak detector implementation. + - `rms` - enables RMS detector implementation. + - `dasp_interpolate` crate: + - `floor` - enables `Floor` `Interpolate` implementation. + - `linear` - enables `Linear` `Interpolate` implementation. + - `sinc` - enables `Sinc` `Interpolate` implementation. + - `dasp_signal` crate: + - `boxed` - enables `Signal` implementation for `Box`. + - `bus` - enables `SignalBus` trait. + - `envelope` - enables `SignalEnvelope` trait. + - `rms` - enables `SignalRms` trait. + - `window` - enables `signal::window` module. + - `window-hanning` - enables *hanning* window constructor. + - `window-rectangle` - enables *rectangle* window constructor. + - `dasp_slice` crate: + - `boxed` - enables conversions between boxed slices. + - The `dasp` crate has a feature for each of the above. + +--- + +*CHANGELOG begins...* From e09f785ad2b64266c5b8596b1f49868c8dca9d55 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 16:48:59 +0200 Subject: [PATCH 22/29] Update and improve docs for all crates. Add optional feature docs. --- dasp/src/lib.rs | 75 ++++++++++++++++++++++++++--- dasp_envelope/src/detect/mod.rs | 7 ++- dasp_envelope/src/detect/peak.rs | 47 ++++++++++++++++++ dasp_envelope/src/detect/rms.rs | 5 ++ dasp_envelope/src/lib.rs | 17 +++++++ dasp_frame/src/lib.rs | 2 +- dasp_interpolate/src/floor.rs | 17 +++++++ dasp_interpolate/src/lib.rs | 17 ++++++- dasp_interpolate/src/linear.rs | 20 +++++++- dasp_interpolate/src/sinc/mod.rs | 17 +++++++ dasp_signal/src/bus.rs | 38 ++++++++++++++- dasp_signal/src/envelope.rs | 38 +++++++++++++++ dasp_signal/src/interpolate.rs | 15 +++--- dasp_signal/src/lib.rs | 21 ++++++-- dasp_signal/src/rms.rs | 33 +++++++++++++ dasp_signal/src/window/hanning.rs | 12 ++++- dasp_signal/src/window/mod.rs | 27 +++++++++++ dasp_signal/src/window/rectangle.rs | 12 ++++- dasp_slice/src/boxed.rs | 62 ++++++++++++++++++++++++ dasp_slice/src/lib.rs | 5 ++ dasp_window/src/hanning/mod.rs | 5 ++ dasp_window/src/lib.rs | 16 +++++- dasp_window/src/rectangle.rs | 5 ++ 23 files changed, 486 insertions(+), 27 deletions(-) diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index d38dcab1..a198f759 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -1,13 +1,73 @@ -//! A crate of fundamentals for audio PCM DSP. +//! **dasp** is a suite of crates, providing the fundamentals for working with pulse-code +//! modulation digital signal processing. In other words, `dasp` provides a suite of low-level, +//! high-performance tools including types, traits and functions for working with digital audio +//! signals. +//! +//! Each of the **dasp** crates are re-exported under their respective +//! [modules](file:///home/mindtree/programming/rust/dasp/target/doc/dasp/index.html#modules). +//! +//! ## Highlights +//! +//! The following are some of the more interesting items within the dasp collection: //! //! - Use the [**Sample** trait](./trait.Sample.html) to remain generic across bit-depth. //! - Use the [**Frame** trait](./frame/trait.Frame.html) to remain generic over channel layout. -//! - Use the [**Signal** trait](./signal/trait.Signal.html) for working with **Iterators** that yield **Frames**. -//! - Use the [**slice** module](./slice/index.html) for working with slices of **Samples** and **Frames**. -//! - See the [**conv** module](./conv/index.html) for fast conversions between slices, frames and samples. -//! - See the [**types** module](./types/index.html) for provided custom sample types. -//! - See the [**interpolate** module](./interpolate/index.html) for sample rate conversion and scaling. +//! - Use the [**Signal** trait](./signal/trait.Signal.html) for working with **Iterators** that +//! yield **Frames**. +//! - See the [**signal** module](./signal/index.html) for a collection of interesting signal +//! constructors (e.g. `sine`, `noise`, `from_iter`, etc). +//! - Use the [**slice** module](./slice/index.html) for working with slices of **Samples** and +//! **Frames**. +//! - See the [**sample::types** module](./sample/types/index.html) for provided custom sample +//! types. +//! - See the [**Converter** type](./signal/interpolate/struct.Converter.html) for sample rate +//! conversion and scaling. //! - See the [**ring_buffer** module](./ring_buffer/index.html) for fast FIFO queue options. +//! +//! ## Optional Features +//! +//! By default, only the **sample** and **frame** modules and their respective traits are included +//! within this crate. You may pick and choose between the following features for additional +//! functionality. +//! +//! - The **envelope** feature enables the `dasp_envelope` crate via the +//! [envelope](./envelope/index.html) module. +//! - The **envelope-peak** feature enables peak envelope detection. +//! - The **envelope-rms** feature enables RMS envelope detection. +//! - The **interpolate** feature enables the `dasp_interpolate` crate via the +//! [interpolate](./interpolate/index.html) module. +//! - The **interpolate-floor** feature enables a floor interpolation implementation. +//! - The **interpolate-linear** feature enables a linear interpolation implementation. +//! - The **interpolate-sinc** feature enables a sinc interpolation implementation. +//! - The **peak** feature enables the `dasp_peak` crate via the [peak](./peak/index.html) module. +//! - The **ring_buffer** feature enables the `dasp_ring_buffer` crate via the +//! [ring_buffer](./peak/index.html) module. +//! - The **rms** feature enables the `dasp_rms` crate via the [rms](./rms/index.html) module. +//! - The **signal** feature enables the `dasp_signal` crate via the [signal](./signal/index.html) +//! module. +//! - The **signal-boxed** feature enables an implementation of **Signal** for `Box`. +//! - The **signal-bus** feature enables the [**SignalBus**](./signal/bus/trait.SignalBus.html) +//! trait. +//! - The **signal-envelope** feature enables the +//! [**SignalEnvelope**](./signal/envelope/trait.SignalEnvelope.html) trait. +//! - The **signal-rms** feature enables the [**SignalRms**](./signal/rms/trait.SignalRms.html) +//! trait. +//! - The **signal-window** feature enables the +//! [**signal::window**](./signal/window/index.html) module. +//! - The **signal-window-hanning** enables the +//! [**signal::window::hanning**](./signal/window/fn.hanning.html) window constructor. +//! - The **signal-window-rectangle** enables the +//! [**signal::window::rectangle**](./signal/window/fn.rectangle.html) window constructor. +//! - The **slice** feature enables the `dasp_slice` crate via the [slice](./slice/index.html) +//! module. +//! - The **slice-boxed** feature enables boxed slice conversion traits and functions. +//! - The **window** feature enables the `dasp_window` crate via the [window](./window/index.html) +//! module. +//! - The **window-hanning** feature enables the [**Hanning**](./window/struct.Hanning.html) +//! window implementation. +//! - The **window-rectangle** feature enables the +//! [**Rectangle**](./window/struct.Rectangle.html) window implementation. #![cfg_attr(not(feature = "std"), no_std)] @@ -25,6 +85,9 @@ pub use dasp_peak as peak; #[cfg(feature = "ring_buffer")] #[doc(inline)] pub use dasp_ring_buffer as ring_buffer; +#[cfg(feature = "rms")] +#[doc(inline)] +pub use dasp_rms as rms; #[doc(inline)] pub use dasp_sample::{self as sample, Sample}; #[cfg(feature = "signal")] diff --git a/dasp_envelope/src/detect/mod.rs b/dasp_envelope/src/detect/mod.rs index 5728110d..e10632ba 100644 --- a/dasp_envelope/src/detect/mod.rs +++ b/dasp_envelope/src/detect/mod.rs @@ -2,6 +2,9 @@ use dasp_frame::Frame; use dasp_sample::Sample; use ops::f32::powf32; +#[cfg(feature = "peak")] +pub use self::peak::Peak; + mod ops; #[cfg(feature = "peak")] mod peak; @@ -45,8 +48,8 @@ where F: Frame, D: Detect, { - #[cfg(any(feature = "peak", feature = "rms"))] - fn new(detect: D, attack_frames: f32, release_frames: f32) -> Self { + /// Construct a **Detector** with the given **Detect** implementation. + pub fn new(detect: D, attack_frames: f32, release_frames: f32) -> Self { Detector { last_env_frame: D::Output::equilibrium(), attack_gain: calc_gain(attack_frames), diff --git a/dasp_envelope/src/detect/peak.rs b/dasp_envelope/src/detect/peak.rs index a0c53385..24f8efd7 100644 --- a/dasp_envelope/src/detect/peak.rs +++ b/dasp_envelope/src/detect/peak.rs @@ -1,9 +1,21 @@ +//! Peak detector implementations. +//! +//! ### Required Features +//! +//! - When using `dasp_envelope`, this module requires the **peak** feature to be enabled. +//! - When using `dasp`, this module requires the **envelope-peak** feature to be enabled. + use crate::{Detect, Detector}; use dasp_frame::Frame; use dasp_peak as peak; /// A `Peak` detector, generic over the `FullWave`, `PositiveHalfWave`, `NegativeHalfWave` /// rectifiers. +/// +/// ### Required Features +/// +/// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. +/// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Peak { rectifier: R, @@ -11,6 +23,11 @@ pub struct Peak { impl Peak { /// A signal rectifier that produces the absolute amplitude from samples. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn full_wave() -> Self { peak::FullWave.into() } @@ -18,6 +35,11 @@ impl Peak { impl Peak { /// A signal rectifier that produces only the positive samples. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn positive_half_wave() -> Self { peak::PositiveHalfWave.into() } @@ -25,6 +47,11 @@ impl Peak { impl Peak { /// A signal rectifier that produces only the negative samples. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn negative_half_wave() -> Self { peak::NegativeHalfWave.into() } @@ -36,6 +63,11 @@ where R: peak::Rectifier, { /// Construct a new **Peak** **Detector** that uses the given rectifier. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn peak_from_rectifier(rectifier: R, attack_frames: f32, release_frames: f32) -> Self { let peak = rectifier.into(); Self::new(peak, attack_frames, release_frames) @@ -47,6 +79,11 @@ where F: Frame, { /// Construct a new full wave **Peak** **Detector**. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn peak(attack_frames: f32, release_frames: f32) -> Self { let peak = Peak::full_wave(); Self::new(peak, attack_frames, release_frames) @@ -58,6 +95,11 @@ where F: Frame, { /// Construct a new positive half wave **Peak** **Detector**. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn peak_positive_half_wave(attack_frames: f32, release_frames: f32) -> Self { let peak = Peak::positive_half_wave(); Self::new(peak, attack_frames, release_frames) @@ -69,6 +111,11 @@ where F: Frame, { /// Construct a new positive half wave **Peak** **Detector**. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **peak** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-peak** feature to be enabled. pub fn peak_negative_half_wave(attack_frames: f32, release_frames: f32) -> Self { let peak = Peak::negative_half_wave(); Self::new(peak, attack_frames, release_frames) diff --git a/dasp_envelope/src/detect/rms.rs b/dasp_envelope/src/detect/rms.rs index 3e42312f..ede849ca 100644 --- a/dasp_envelope/src/detect/rms.rs +++ b/dasp_envelope/src/detect/rms.rs @@ -20,6 +20,11 @@ where S: ring_buffer::Slice + ring_buffer::SliceMut, { /// Construct a new **Rms** **Detector**. + /// + /// ### Required Features + /// + /// - When using `dasp_envelope`, this item requires the **rms** feature to be enabled. + /// - When using `dasp`, this item requires the **envelope-rms** feature to be enabled. pub fn rms(buffer: ring_buffer::Fixed, attack_frames: f32, release_frames: f32) -> Self { let rms = rms::Rms::new(buffer); Self::new(rms, attack_frames, release_frames) diff --git a/dasp_envelope/src/lib.rs b/dasp_envelope/src/lib.rs index 051d721a..3eac1732 100644 --- a/dasp_envelope/src/lib.rs +++ b/dasp_envelope/src/lib.rs @@ -1,3 +1,20 @@ +//! An abstraction supporting different kinds of envelope detection. +//! +//! - The [**Detect**](./trait.Detect.html) trait provides an abstraction for generalising over +//! types of envelope detection. +//! - The [**Detector**](./struct.Detector.html) type allows for applying a **Detect** +//! implementation in order to detect the envelope of a signal. +//! +//! See the `dasp_signal` crate (or `dasp::signal` module) **SignalWindow** trait for a convenient +//! way to detect envelopes over arbitrary signals. +//! +//! ### Optional Features +//! +//! - The **peak** feature (or **envelope-peak** feature if using `dasp`) provides a peak envelope +//! detection implementation. +//! - The **rms** feature (or **envelope-rms** feature if using `dasp`) provides an RMS envelope +//! detection implementation. + #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_frame/src/lib.rs b/dasp_frame/src/lib.rs index b8cac513..93f64286 100644 --- a/dasp_frame/src/lib.rs +++ b/dasp_frame/src/lib.rs @@ -1,4 +1,4 @@ -//! Use the Frame trait to remain generic over the number of channels at +//! Use the [**Frame** trait](./trait.Frame.html) to remain generic over the number of channels at //! a single discrete moment in time. //! //! Implementations are provided for all fixed-size arrays up to 32 elements in length. diff --git a/dasp_interpolate/src/floor.rs b/dasp_interpolate/src/floor.rs index d97c51dc..72c04c84 100644 --- a/dasp_interpolate/src/floor.rs +++ b/dasp_interpolate/src/floor.rs @@ -1,14 +1,31 @@ +//! A floor interpolator implementation. +//! +//! ### Required Features +//! +//! - When using `dasp_interpolate`, this module requires the **floor** feature to be enabled. +//! - When using `dasp`, this module requires the **interpolate-floor** feature to be enabled. + use crate::Interpolator; use dasp_frame::Frame; use dasp_sample::Duplex; /// Interpolator that rounds off any values to the previous value from the source. +/// +/// ### Required Features +/// +/// - When using `dasp_interpolate`, this item requires the **floor** feature to be enabled. +/// - When using `dasp`, this item requires the **interpolate-floor** feature to be enabled. pub struct Floor { left: F, } impl Floor { /// Create a new Floor Interpolator. + /// + /// ### Required Features + /// + /// - When using `dasp_interpolate`, this item requires the **floor** feature to be enabled. + /// - When using `dasp`, this item requires the **interpolate-floor** feature to be enabled. pub fn new(left: F) -> Floor { Floor { left: left } } diff --git a/dasp_interpolate/src/lib.rs b/dasp_interpolate/src/lib.rs index cf69d44a..62c7a5fa 100644 --- a/dasp_interpolate/src/lib.rs +++ b/dasp_interpolate/src/lib.rs @@ -1,4 +1,19 @@ -//! The Interpolate module allows for conversion between various sample rates. +//! An abstraction for sample/frame rate interpolation. +//! +//! The [**Interpolator**](./trait.Interpolator.html) trait provides an abstraction over different +//! types of rate interpolation. +//! +//! See the `dasp_signal` crate (or `dasp::signal` module) **Converter** type for a convenient way +//! to interpolate the rate of arbitrary signals. +//! +//! ### Optional Features +//! +//! - The **floor** feature (or **interpolate-floor** feature if using `dasp`) provides a floor +//! interpolator implementation. +//! - The **linear** feature (or **interpolate-linear** feature if using `dasp`) provides a linear +//! interpolator implementation. +//! - The **sinc** feature (or **interpolate-sinc** feature if using `dasp`) provides a sinc +//! interpolator implementation. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_interpolate/src/linear.rs b/dasp_interpolate/src/linear.rs index 09b02e14..9e63327b 100644 --- a/dasp_interpolate/src/linear.rs +++ b/dasp_interpolate/src/linear.rs @@ -1,15 +1,33 @@ +//! A linear interpolator implementation. +//! +//! ### Required Features +//! +//! - When using `dasp_interpolate`, this module requires the **linear** feature to be enabled. +//! - When using `dasp`, this module requires the **interpolate-linear** feature to be enabled. + use crate::Interpolator; use dasp_frame::Frame; use dasp_sample::{Duplex, Sample}; /// Interpolator that interpolates linearly between the previous value and the next value +/// +/// ### Required Features +/// +/// - When using `dasp_interpolate`, this item requires the **linear** feature to be enabled. +/// - When using `dasp`, this item requires the **interpolate-linear** feature to be enabled. pub struct Linear { left: F, right: F, } impl Linear { - /// Create a new Linear Interpolator. + /// Create a new Linear Interpolator, where `left` and `right` are the first two frames to be + /// interpolated. + /// + /// ### Required Features + /// + /// - When using `dasp_interpolate`, this item requires the **linear** feature to be enabled. + /// - When using `dasp`, this item requires the **interpolate-linear** feature to be enabled. pub fn new(left: F, right: F) -> Linear { Linear { left: left, diff --git a/dasp_interpolate/src/sinc/mod.rs b/dasp_interpolate/src/sinc/mod.rs index 1f3567d4..1fefe926 100644 --- a/dasp_interpolate/src/sinc/mod.rs +++ b/dasp_interpolate/src/sinc/mod.rs @@ -1,3 +1,10 @@ +//! A sinc interpolator implementation. +//! +//! ### Required Features +//! +//! - When using `dasp_interpolate`, this module requires the **sinc** feature to be enabled. +//! - When using `dasp`, this module requires the **interpolate-sinc** feature to be enabled. + use crate::Interpolator; use core::f64::consts::PI; use dasp_frame::Frame; @@ -11,6 +18,11 @@ mod ops; /// /// Generally accepted as one of the better sample rate converters, although it uses significantly /// more computation. +/// +/// ### Required Features +/// +/// - When using `dasp_interpolate`, this item requires the **sinc** feature to be enabled. +/// - When using `dasp`, this item requires the **interpolate-sinc** feature to be enabled. pub struct Sinc { frames: ring_buffer::Fixed, idx: usize, @@ -25,6 +37,11 @@ impl Sinc { /// The initial contents of the ring_buffer will act as padding for the interpolated signal. /// /// **panic!**s if the given ring buffer's length is not a multiple of `2`. + /// + /// ### Required Features + /// + /// - When using `dasp_interpolate`, this item requires the **sinc** feature to be enabled. + /// - When using `dasp`, this item requires the **interpolate-sinc** feature to be enabled. pub fn new(frames: ring_buffer::Fixed) -> Self where S: ring_buffer::SliceMut, diff --git a/dasp_signal/src/bus.rs b/dasp_signal/src/bus.rs index 778ccdf9..50f12d7b 100644 --- a/dasp_signal/src/bus.rs +++ b/dasp_signal/src/bus.rs @@ -1,3 +1,10 @@ +//! An extension to the **Signal** trait that enables multiple signal outputs. +//! +//! ### Required Features +//! +//! - When using `dasp_signal`, this item requires the **bus** feature to be enabled. +//! - When using `dasp`, this item requires the **signal-bus** feature to be enabled. + use crate::{Rc, Signal}; #[cfg(not(feature = "std"))] @@ -10,6 +17,12 @@ type VecDeque = alloc::collections::vec_deque::VecDeque; #[cfg(feature = "std")] type VecDeque = std::collections::vec_deque::VecDeque; +/// An extension to the **Signal** trait that enables multiple signal outputs. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **bus** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-bus** feature to be enabled. pub trait SignalBus: Signal { /// Moves the `Signal` into a `Bus` from which its output may be divided into multiple other /// `Signal`s in the form of `Output`s. @@ -44,6 +57,11 @@ pub trait SignalBus: Signal { /// assert_eq!(a.take(3).collect::>(), vec![[0.4], [0.5], [0.6]]); /// } /// ``` + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **bus** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-bus** feature to be enabled. fn bus(self) -> Bus where Self: Sized, @@ -68,7 +86,10 @@ where /// A type which allows for `send`ing a single `Signal` to multiple outputs. /// -/// This type manages +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **bus** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-bus** feature to be enabled. pub struct Bus where S: Signal, @@ -79,6 +100,11 @@ where /// An output node to which some signal `S` is `Output`ing its frames. /// /// It may be more accurate to say that the `Output` "pull"s frames from the signal. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **bus** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-bus** feature to be enabled. pub struct Output where S: Signal, @@ -103,6 +129,11 @@ where } /// Produce a new Output node to which the signal `S` will output its frames. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **bus** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-bus** feature to be enabled. #[inline] pub fn send(&self) -> Output { let mut node = self.node.borrow_mut(); @@ -221,6 +252,11 @@ where /// assert_eq!(monitor.pending_frames(), 2); /// } /// ``` + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **bus** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-bus** feature to be enabled. #[inline] pub fn pending_frames(&self) -> usize { self.node.borrow().pending_frames(self.key) diff --git a/dasp_signal/src/envelope.rs b/dasp_signal/src/envelope.rs index 2342e7c3..c740bb45 100644 --- a/dasp_signal/src/envelope.rs +++ b/dasp_signal/src/envelope.rs @@ -1,6 +1,19 @@ +//! An extension to the **Signal** trait that enables envelope detection. +//! +//! ### Required Features +//! +//! - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. +//! - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. + use crate::Signal; use dasp_envelope as envelope; +/// An extension to the **Signal** trait that enables envelope detection. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. pub trait SignalEnvelope: Signal { /// An adaptor that detects and yields the envelope of the signal. /// @@ -23,6 +36,11 @@ pub trait SignalEnvelope: Signal { /// ); /// } /// ``` + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. fn detect_envelope( self, detector: envelope::Detector, @@ -39,6 +57,11 @@ pub trait SignalEnvelope: Signal { } /// An adaptor that detects and yields the envelope of the signal. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. #[derive(Clone)] pub struct DetectEnvelope where @@ -55,16 +78,31 @@ where D: envelope::Detect, { /// Set the **Detector**'s attack time as a number of frames. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. pub fn set_attack_frames(&mut self, frames: f32) { self.detector.set_attack_frames(frames); } /// Set the **Detector**'s release time as a number of frames. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. pub fn set_release_frames(&mut self, frames: f32) { self.detector.set_release_frames(frames); } /// Consumes `Self` and returns the inner signal `S` and `Detector`. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **envelope** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-envelope** feature to be enabled. pub fn into_parts(self) -> (S, envelope::Detector) { let DetectEnvelope { signal, detector } = self; (signal, detector) diff --git a/dasp_signal/src/interpolate.rs b/dasp_signal/src/interpolate.rs index 12c9dc0c..fd28540d 100644 --- a/dasp_signal/src/interpolate.rs +++ b/dasp_signal/src/interpolate.rs @@ -1,16 +1,17 @@ +//! The [**Converter**](./struct.Converter.html) type for interpolating the rate of a signal. + use crate::Signal; use dasp_interpolate::Interpolator; -/// An iterator that converts the rate at which frames are yielded from some given frame -/// Interpolator into a new type. +/// A signal type that converts the rate at which frames are yielded from some source signal to +/// some target rate. /// /// Other names for `sample::interpolate::Converter` might include: /// -/// - Sample rate converter -/// - {Up/Down}sampler -/// - Sample interpolater -/// - Sample decimator -/// +/// - Sample rate converter. +/// - {Up/Down}sampler. +/// - Sample interpolater. +/// - Sample decimator. #[derive(Clone)] pub struct Converter where diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index f2f685c9..bb5ae7cf 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -1,6 +1,6 @@ -//! Use the **Signal** trait for working with **Iterator**s that yield **Frame**s. To complement -//! the **Iterator** trait, **Signal** provides methods for adding, scaling, offsetting, -//! multiplying, clipping and generating frame iterators and more. +//! Use the [**Signal**](./trait.Signal.html) trait to abstract over infinite-iterator-like types +//! that yield **Frame**s. The **Signal** trait provides methods for adding, scaling, offsetting, +//! multiplying, clipping, generating frame iterators and more. //! //! You may also find a series of **Signal** source functions, including: //! @@ -19,6 +19,19 @@ //! //! Working with **Signal**s allows for easy, readable creation of rich and complex DSP graphs with //! a simple and familiar API. +//! +//! ### Optional Features +//! +//! - The **boxed** feature (or **signal-boxed** feature if using `dasp`) provides a **Signal** +//! implementation for `Box`. +//! - The **bus** feature (or **signal-bus** feature if using `dasp`) provides the +//! [**SignalBus**](./bus/trait.SignalBus.html) trait. +//! - The **envelope** feature (or **signal-envelope** feature if using `dasp`) provides the +//! [**SignalEnvelope**](./envelope/trait.SignalEnvelope.html) trait. +//! - The **rms** feature (or **signal-rms** feature if using `dasp`) provides the +//! [**SignalRms**](./rms/trait.SignalRms.html) trait. +//! - The **window** feature (or **signal-window** feature if using `dasp`) provides the +//! [**window**](./window/index.html) module. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] @@ -38,7 +51,7 @@ pub mod interpolate; mod ops; #[cfg(features = "boxed")] -pub mod boxed; +mod boxed; #[cfg(feature = "bus")] pub mod bus; #[cfg(feature = "envelope")] diff --git a/dasp_signal/src/rms.rs b/dasp_signal/src/rms.rs index c4b43f40..461d8439 100644 --- a/dasp_signal/src/rms.rs +++ b/dasp_signal/src/rms.rs @@ -1,8 +1,21 @@ +//! An extension to the **Signal** trait that monitors the RMS of a signal. +//! +//! ### Required Features +//! +//! - When using `dasp_signal`, this module requires the **rms** feature to be enabled. +//! - When using `dasp`, this module requires the **signal-rms** feature to be enabled. + use crate::Signal; use dasp_frame::Frame; use dasp_ring_buffer as ring_buffer; use dasp_rms as rms; +/// An extension to the **Signal** trait that monitors the RMS of a signal. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **rms** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-rms** feature to be enabled. pub trait SignalRms: Signal { /// An adaptor that yields the RMS of the signal. /// @@ -26,6 +39,11 @@ pub trait SignalRms: Signal { /// ); /// } /// ``` + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **rms** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-rms** feature to be enabled. fn rms(self, ring_buffer: ring_buffer::Fixed) -> Rms where Self: Sized, @@ -41,6 +59,11 @@ pub trait SignalRms: Signal { /// An adaptor that yields the RMS of the signal. /// /// The window size of the RMS detector is equal to the given ring buffer length. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **rms** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-rms** feature to be enabled. #[derive(Clone)] pub struct Rms where @@ -58,11 +81,21 @@ where { /// The same as `Signal::next` but does not calculate the final square root required to /// determine the RMS. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **rms** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-rms** feature to be enabled. pub fn next_squared(&mut self) -> ::Frame { self.rms.next_squared(self.signal.next()) } /// Consumes the `Rms` signal and returns its inner signal `S` and `Rms` detector. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **rms** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-rms** feature to be enabled. pub fn into_parts(self) -> (S, rms::Rms) { let Rms { signal, rms } = self; (signal, rms) diff --git a/dasp_signal/src/window/hanning.rs b/dasp_signal/src/window/hanning.rs index 0d524cd8..a4dc3545 100644 --- a/dasp_signal/src/window/hanning.rs +++ b/dasp_signal/src/window/hanning.rs @@ -1,18 +1,28 @@ use super::{Window, Windower}; use dasp_frame::Frame; -use dasp_window::hanning::Hanning; +use dasp_window::Hanning; impl<'a, F> Windower<'a, F, Hanning> where F: 'a + Frame, { /// Constructor for a `Windower` using the `Hanning` window function. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **window-hanning** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-window-hanning** feature to be enabled. pub fn hanning(frames: &'a [F], bin: usize, hop: usize) -> Self { Windower::new(frames, bin, hop) } } /// A helper function for constructing a `Window` that uses a `Hanning` `Type` function. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **window-hanning** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-window-hanning** feature to be enabled. pub fn hanning(num_frames: usize) -> Window where F: Frame, diff --git a/dasp_signal/src/window/mod.rs b/dasp_signal/src/window/mod.rs index 76c7cf16..0ab5e43a 100644 --- a/dasp_signal/src/window/mod.rs +++ b/dasp_signal/src/window/mod.rs @@ -1,3 +1,5 @@ +//! Items to ease the application of windowing functions to signals. + use crate::{ConstHz, FromIterator, Phase, Signal}; use core::marker::PhantomData; use dasp_frame::Frame; @@ -16,6 +18,11 @@ mod rectangle; /// A `Signal` type that for every yielded `phase`, yields the amplitude across the `window::Type` /// for that phase. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **window** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-window** feature to be enabled. #[derive(Clone)] pub struct Window where @@ -28,6 +35,11 @@ where } /// Takes a long slice of frames and yields `Windowed` chunks of size `bin` once every `hop` frames. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **window** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-window** feature to be enabled. #[derive(Clone)] pub struct Windower<'a, F, W> where @@ -46,6 +58,11 @@ where /// An Iterator that multiplies a Signal with a Window. /// /// Returns `None` once the `Window` has been exhausted. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **window** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-window** feature to be enabled. #[derive(Clone)] pub struct Windowed where @@ -62,6 +79,11 @@ where W: WindowType, { /// Construct a new `Window` with the given length as a number of frames. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **window** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-window** feature to be enabled. pub fn new(len: usize) -> Self { let step = crate::rate(len as f64 - 1.0).const_hz(1.0); Window { @@ -77,6 +99,11 @@ where W: WindowType, { /// Constructor for a new `Windower` iterator. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **window** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-window** feature to be enabled. pub fn new(frames: &'a [F], bin: usize, hop: usize) -> Self { Windower { bin: bin, diff --git a/dasp_signal/src/window/rectangle.rs b/dasp_signal/src/window/rectangle.rs index bd6c31a0..9ad33e39 100644 --- a/dasp_signal/src/window/rectangle.rs +++ b/dasp_signal/src/window/rectangle.rs @@ -1,18 +1,28 @@ use super::{Window, Windower}; use dasp_frame::Frame; -use dasp_window::rectangle::Rectangle; +use dasp_window::Rectangle; impl<'a, F> Windower<'a, F, Rectangle> where F: 'a + Frame, { /// Constructor for a `Windower` using the `Rectangle` window function. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **window-rectangle** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-window-rectangle** feature to be enabled. pub fn rectangle(frames: &'a [F], bin: usize, hop: usize) -> Self { Windower::new(frames, bin, hop) } } /// A helper function for constructing a `Window` that uses a `Rectangle` `Type` function. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **window-rectangle** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-window-rectangle** feature to be enabled. pub fn rectangle(num_frames: usize) -> Window where F: Frame, diff --git a/dasp_slice/src/boxed.rs b/dasp_slice/src/boxed.rs index 6f007bda..e508b3c7 100644 --- a/dasp_slice/src/boxed.rs +++ b/dasp_slice/src/boxed.rs @@ -1,4 +1,9 @@ //! Items related to boxed-slice conversions. +//! +//! ### Required Features +//! +//! - When using `dasp_slice`, this module requires the **boxed** feature to be enabled. +//! - When using `dasp`, this module requires the **slice-boxed** feature to be enabled. #[cfg(not(feature = "std"))] extern crate alloc; @@ -6,8 +11,10 @@ extern crate alloc; use dasp_frame::Frame; use dasp_sample::Sample; +/// Equal to `std::boxed::Box` on std, `alloc::boxed::Box` in `no_std` context. #[cfg(not(feature = "std"))] pub type Box = alloc::boxed::Box; +/// Equal to `std::boxed::Box` on std, `alloc::boxed::Box` in `no_std` context. #[cfg(feature = "std")] pub type Box = std::boxed::Box; @@ -15,6 +22,11 @@ pub type Box = std::boxed::Box; // ---------------------------------------------------------------------------- /// For converting a boxed slice of `Sample`s to a boxed slice of `Frame`s. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait FromBoxedSampleSlice: Sized where S: Sample, @@ -23,6 +35,11 @@ where } /// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait FromBoxedFrameSlice where F: Frame, @@ -31,6 +48,11 @@ where } /// For converting from a boxed slice of `Frame`s to a boxed slice of `Sample`s. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait ToBoxedSampleSlice where S: Sample, @@ -39,6 +61,11 @@ where } /// For converting from a boxed slice of `Sample`s to a boxed slice of `Frame`s. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait ToBoxedFrameSlice where F: Frame, @@ -47,6 +74,11 @@ where } /// For converting to and from a boxed slice of `Sample`s. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait DuplexBoxedSampleSlice: FromBoxedSampleSlice + ToBoxedSampleSlice where S: Sample, @@ -54,6 +86,11 @@ where } /// For converting to and from a boxed slice of `Frame`s. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait DuplexBoxedFrameSlice: FromBoxedFrameSlice + ToBoxedFrameSlice where F: Frame, @@ -62,6 +99,11 @@ where /// For converting to and from a boxed slice of `Sample`s of type `S` and a slice of `Frame`s of /// type `F`. +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub trait DuplexBoxedSlice: DuplexBoxedSampleSlice + DuplexBoxedFrameSlice where S: Sample, @@ -150,6 +192,11 @@ where /// assert_eq!(bar.into_vec(), vec![0.0, 0.5, 0.0, -0.5]); /// } /// ``` +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub fn to_boxed_sample_slice(slice: T) -> Box<[S]> where S: Sample, @@ -178,6 +225,11 @@ where /// assert_eq!(bar, None::>); /// } /// ``` +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub fn to_boxed_frame_slice(slice: T) -> Option> where F: Frame, @@ -202,6 +254,11 @@ where /// assert_eq!(bar.into_vec(), vec![[0.0, 0.5], [0.0, -0.5]]); /// } /// ``` +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub fn from_boxed_sample_slice(slice: Box<[S]>) -> Option where S: Sample, @@ -223,6 +280,11 @@ where /// assert_eq!(bar.into_vec(), vec![0.0, 0.5, 0.0, -0.5]); /// } /// ``` +/// +/// ### Required Features +/// +/// - When using `dasp_slice`, this item requires the **boxed** feature to be enabled. +/// - When using `dasp`, this item requires the **slice-boxed** feature to be enabled. pub fn from_boxed_frame_slice(slice: Box<[F]>) -> T where F: Frame, diff --git a/dasp_slice/src/lib.rs b/dasp_slice/src/lib.rs index 1fa71c7a..e7e78618 100644 --- a/dasp_slice/src/lib.rs +++ b/dasp_slice/src/lib.rs @@ -2,6 +2,11 @@ //! //! Items related to conversion between slices of frames and slices of samples, particularly useful //! for working with interleaved data. +//! +//! ### Optional Features +//! +//! - The **boxed** feature (or **slice-boxed** feature if using `dasp`) provides a suite of boxed +//! slice conversion traits and functions under the [**boxed**](./boxed/index.html) module. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/dasp_window/src/hanning/mod.rs b/dasp_window/src/hanning/mod.rs index 73620899..ae62f9be 100644 --- a/dasp_window/src/hanning/mod.rs +++ b/dasp_window/src/hanning/mod.rs @@ -7,6 +7,11 @@ mod ops; /// A type of window function, also known as the "raised cosine window". /// /// [Wiki entry](https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window). +/// +/// ### Required Features +/// +/// - When using `dasp_window`, this item requires the **hanning** feature to be enabled. +/// - When using `dasp`, this item requires the **window-hanning** feature to be enabled. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Hanning; diff --git a/dasp_window/src/lib.rs b/dasp_window/src/lib.rs index 740f2d3a..89907684 100644 --- a/dasp_window/src/lib.rs +++ b/dasp_window/src/lib.rs @@ -1,5 +1,12 @@ //! Module for windowing over a batch of Frames. Includes default Hanning and Rectangle window //! types. +//! +//! ### Optional Features +//! +//! - The **hanning** feature (or **window-hanning** feature if using `dasp`) provides the +//! [**Hanning**](./struct.Hanning.html) window function implementation. +//! - The **rectangle** feature (or **window-rectangle** feature if using `dasp`) provides the +//! [**Rectangle**](./struct.Rectangle.html) window function implementation. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] @@ -7,9 +14,14 @@ use dasp_sample::Sample; #[cfg(feature = "hanning")] -pub mod hanning; +pub use hanning::Hanning; #[cfg(feature = "rectangle")] -pub mod rectangle; +pub use rectangle::Rectangle; + +#[cfg(feature = "hanning")] +mod hanning; +#[cfg(feature = "rectangle")] +mod rectangle; /// The window function used within a `Window`. pub trait Window { diff --git a/dasp_window/src/rectangle.rs b/dasp_window/src/rectangle.rs index 68ac4c37..5c70c75a 100644 --- a/dasp_window/src/rectangle.rs +++ b/dasp_window/src/rectangle.rs @@ -4,6 +4,11 @@ use dasp_sample::{FloatSample, Sample}; /// The simplest window type, equivalent to replacing all but *N* values of data sequence by /// zeroes, making it appear as though the waveform suddenly turns on and off. +/// +/// ### Required Features +/// +/// - When using `dasp_window`, this item requires the **rectangle** feature to be enabled. +/// - When using `dasp`, this item requires the **window-rectangle** feature to be enabled. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rectangle; From fecf1f03a58bf6be5898a0df29b5ef120974a594 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 16:58:24 +0200 Subject: [PATCH 23/29] Rename all-features-no-std to all-no-std. Add docs about no_std. --- .github/workflows/dasp.yml | 12 ++++++------ README.md | 2 +- dasp/Cargo.toml | 2 +- dasp/src/lib.rs | 9 +++++++++ dasp_envelope/Cargo.toml | 2 +- dasp_envelope/src/lib.rs | 7 +++++++ dasp_interpolate/Cargo.toml | 2 +- dasp_interpolate/src/lib.rs | 7 +++++++ dasp_signal/Cargo.toml | 2 +- dasp_signal/src/lib.rs | 7 +++++++ dasp_slice/Cargo.toml | 2 +- dasp_slice/src/lib.rs | 5 +++++ dasp_window/Cargo.toml | 2 +- dasp_window/src/lib.rs | 7 +++++++ 14 files changed, 55 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dasp.yml b/.github/workflows/dasp.yml index fc3ba357..4e9eb3d6 100644 --- a/.github/workflows/dasp.yml +++ b/.github/workflows/dasp.yml @@ -148,32 +148,32 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path dasp/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + args: --manifest-path dasp/Cargo.toml --no-default-features --features "all-no-std" --verbose - name: cargo test dasp_signal (all features no std) uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path dasp_signal/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + args: --manifest-path dasp_signal/Cargo.toml --no-default-features --features "all-no-std" --verbose - name: cargo test dasp_slice (all features no std) uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path dasp_slice/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + args: --manifest-path dasp_slice/Cargo.toml --no-default-features --features "all-no-std" --verbose - name: cargo test dasp_interpolate (all features no std) uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path dasp_interpolate/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + args: --manifest-path dasp_interpolate/Cargo.toml --no-default-features --features "all-no-std" --verbose - name: cargo test window (all features no std) uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path dasp_window/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + args: --manifest-path dasp_window/Cargo.toml --no-default-features --features "all-no-std" --verbose - name: cargo test dasp_envelope (all features no std) uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path dasp_envelope/Cargo.toml --no-default-features --features "all-features-no-std" --verbose + args: --manifest-path dasp_envelope/Cargo.toml --no-default-features --features "all-no-std" --verbose cargo-doc: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 5a87e76f..cb6d24a0 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ All crates may be compiled with and without the std library. The std library is enabled by default, however it may be disabled via `--no-default-features`. To enable all of a crate's features *without* the std library, you may use -`--no-default-features --features "all-features-no-std"`. +`--no-default-features --features "all-no-std"`. Please note that some of the crates require the `core_intrinsics` feature in order to be able to perform operations like `sin`, `cos` and `powf32` in a diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 4d2865b1..9a4ae88b 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -24,7 +24,7 @@ dasp_window = { version = "0.11", path = "../dasp_window", default-features = fa [features] default = ["std"] -all-features-no-std = [ +all-no-std = [ "envelope", "envelope-peak", "envelope-rms", diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index a198f759..6075c07b 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -68,6 +68,15 @@ //! window implementation. //! - The **window-rectangle** feature enables the //! [**Rectangle**](./window/struct.Rectangle.html) window implementation. +//! +//! Enable all of the above features with the `--all-features` flag. +//! +//! ### no_std +//! +//! If working in a `no_std` context, you can disable the default **std** feature with +//! `--no-default-features`. +//! +//! To enable all of the above features in a `no_std` context, enable the **all-no-std** feature. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/dasp_envelope/Cargo.toml b/dasp_envelope/Cargo.toml index 20d2f65c..8d78a7b8 100644 --- a/dasp_envelope/Cargo.toml +++ b/dasp_envelope/Cargo.toml @@ -19,7 +19,7 @@ dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = fa [features] default = ["std"] -all-features-no-std = [ +all-no-std = [ "peak", "rms", ] diff --git a/dasp_envelope/src/lib.rs b/dasp_envelope/src/lib.rs index 3eac1732..d05205b5 100644 --- a/dasp_envelope/src/lib.rs +++ b/dasp_envelope/src/lib.rs @@ -14,6 +14,13 @@ //! detection implementation. //! - The **rms** feature (or **envelope-rms** feature if using `dasp`) provides an RMS envelope //! detection implementation. +//! +//! ### no_std +//! +//! If working in a `no_std` context, you can disable the default **std** feature with +//! `--no-default-features`. +//! +//! To enable all of the above features in a `no_std` context, enable the **all-no-std** feature. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_interpolate/Cargo.toml b/dasp_interpolate/Cargo.toml index c7a052f5..569ca5bf 100644 --- a/dasp_interpolate/Cargo.toml +++ b/dasp_interpolate/Cargo.toml @@ -17,7 +17,7 @@ dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = fa [features] default = ["std"] -all-features-no-std = [ +all-no-std = [ "floor", "linear", "sinc", diff --git a/dasp_interpolate/src/lib.rs b/dasp_interpolate/src/lib.rs index 62c7a5fa..2e9d02cb 100644 --- a/dasp_interpolate/src/lib.rs +++ b/dasp_interpolate/src/lib.rs @@ -14,6 +14,13 @@ //! interpolator implementation. //! - The **sinc** feature (or **interpolate-sinc** feature if using `dasp`) provides a sinc //! interpolator implementation. +//! +//! ### no_std +//! +//! If working in a `no_std` context, you can disable the default **std** feature with +//! `--no-default-features`. +//! +//! To enable all of the above features in a `no_std` context, enable the **all-no-std** feature. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml index 2592612a..d5d04f99 100644 --- a/dasp_signal/Cargo.toml +++ b/dasp_signal/Cargo.toml @@ -27,7 +27,7 @@ dasp_window = { version = "0.11", path = "../dasp_window", default-features = fa [features] default = ["std"] -all-features-no-std = [ +all-no-std = [ "boxed", "bus", "envelope", diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index bb5ae7cf..869cf791 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -32,6 +32,13 @@ //! [**SignalRms**](./rms/trait.SignalRms.html) trait. //! - The **window** feature (or **signal-window** feature if using `dasp`) provides the //! [**window**](./window/index.html) module. +//! +//! ### no_std +//! +//! If working in a `no_std` context, you can disable the default **std** feature with +//! `--no-default-features`. +//! +//! To enable all of the above features in a `no_std` context, enable the **all-no-std** feature. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_slice/Cargo.toml b/dasp_slice/Cargo.toml index 9f2d63ba..4946dfb6 100644 --- a/dasp_slice/Cargo.toml +++ b/dasp_slice/Cargo.toml @@ -16,7 +16,7 @@ dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = fals [features] default = ["std"] -all-features-no-std = [ +all-no-std = [ "boxed", ] std = [ diff --git a/dasp_slice/src/lib.rs b/dasp_slice/src/lib.rs index e7e78618..0d73c532 100644 --- a/dasp_slice/src/lib.rs +++ b/dasp_slice/src/lib.rs @@ -7,6 +7,11 @@ //! //! - The **boxed** feature (or **slice-boxed** feature if using `dasp`) provides a suite of boxed //! slice conversion traits and functions under the [**boxed**](./boxed/index.html) module. +//! +//! ### no_std +//! +//! If working in a `no_std` context, you can disable the default **std** feature with +//! `--no-default-features`. #![cfg_attr(not(feature = "std"), no_std)] diff --git a/dasp_window/Cargo.toml b/dasp_window/Cargo.toml index 908956a5..25b4a554 100644 --- a/dasp_window/Cargo.toml +++ b/dasp_window/Cargo.toml @@ -15,7 +15,7 @@ dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = fa [features] default = ["std"] -all-features-no-std = [ +all-no-std = [ "hanning", "rectangle", ] diff --git a/dasp_window/src/lib.rs b/dasp_window/src/lib.rs index 89907684..6a28982b 100644 --- a/dasp_window/src/lib.rs +++ b/dasp_window/src/lib.rs @@ -7,6 +7,13 @@ //! [**Hanning**](./struct.Hanning.html) window function implementation. //! - The **rectangle** feature (or **window-rectangle** feature if using `dasp`) provides the //! [**Rectangle**](./struct.Rectangle.html) window function implementation. +//! +//! ### no_std +//! +//! If working in a `no_std` context, you can disable the default **std** feature with +//! `--no-default-features`. +//! +//! To enable all of the above features in a `no_std` context, enable the **all-no-std** feature. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] From 13cd0c240cfac2d1f8b54b2bf05e5455bcbba2c0 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 17:23:25 +0200 Subject: [PATCH 24/29] Improve flexibility of the **Window** trait This allows for **Window** trait implementations to be more specific about the supported phase and output amplitude types of the implementation. The Hanning and Rectangle implementations currently remain generic, however we should consider restricting their implementations to floating point sample types in order to not hide the intermediate conversions that are required. --- CHANGELOG.md | 2 ++ dasp_signal/src/window/mod.rs | 18 +++++++++--------- dasp_window/src/hanning/mod.rs | 8 ++++++-- dasp_window/src/lib.rs | 13 ++++++++----- dasp_window/src/rectangle.rs | 8 ++++++-- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 450c0c2e..2ee8ea76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ - `dasp_slice` crate: - `boxed` - enables conversions between boxed slices. - The `dasp` crate has a feature for each of the above. +- Make **Window** trait generic over its phase and amplitude type. Update the + `dasp_signal::window` module items accordingly. --- diff --git a/dasp_signal/src/window/mod.rs b/dasp_signal/src/window/mod.rs index 0ab5e43a..8e1d0b4d 100644 --- a/dasp_signal/src/window/mod.rs +++ b/dasp_signal/src/window/mod.rs @@ -27,7 +27,7 @@ mod rectangle; pub struct Window where F: Frame, - W: WindowType, + W: WindowType, { /// Yields phase stepped at a constant rate to be passed to the window function `W`. pub phase: Phase, @@ -44,7 +44,7 @@ where pub struct Windower<'a, F, W> where F: 'a + Frame, - W: WindowType, + W: WindowType, { /// The size of each `Windowed` chunk to be yielded. pub bin: usize, @@ -67,7 +67,7 @@ where pub struct Windowed where S: Signal, - W: WindowType, + W: WindowType, { signal: S, window: Window<::Float, W>, @@ -76,7 +76,7 @@ where impl Window where F: Frame, - W: WindowType, + W: WindowType, { /// Construct a new `Window` with the given length as a number of frames. /// @@ -96,7 +96,7 @@ where impl<'a, F, W> Windower<'a, F, W> where F: 'a + Frame, - W: WindowType, + W: WindowType, { /// Constructor for a new `Windower` iterator. /// @@ -117,12 +117,12 @@ where impl Iterator for Window where F: Frame, - W: WindowType, + W: WindowType, { type Item = F; fn next(&mut self) -> Option { - let v = W::at_phase(self.phase.next_phase()); + let v = W::window(self.phase.next_phase()); let v_f: ::Float = v.to_sample(); Some(F::from_fn(|_| v_f.to_sample::())) } @@ -131,7 +131,7 @@ where impl<'a, F, W> Iterator for Windower<'a, F, W> where F: 'a + Frame, - W: WindowType, + W: WindowType, { type Item = Windowed>>, W>; @@ -175,7 +175,7 @@ where impl Iterator for Windowed where S: Signal, - W: WindowType, + W: WindowType, { type Item = S::Frame; fn next(&mut self) -> Option { diff --git a/dasp_window/src/hanning/mod.rs b/dasp_window/src/hanning/mod.rs index ae62f9be..1e5934af 100644 --- a/dasp_window/src/hanning/mod.rs +++ b/dasp_window/src/hanning/mod.rs @@ -15,8 +15,12 @@ mod ops; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Hanning; -impl Window for Hanning { - fn at_phase(phase: S) -> S { +impl Window for Hanning +where + S: Sample, +{ + type Output = S; + fn window(phase: S) -> Self::Output { const PI_2: f64 = core::f64::consts::PI * 2.0; let v = phase.to_float_sample().to_sample() * PI_2; (0.5 * (1.0 - cos(v))) diff --git a/dasp_window/src/lib.rs b/dasp_window/src/lib.rs index 6a28982b..7aff9a80 100644 --- a/dasp_window/src/lib.rs +++ b/dasp_window/src/lib.rs @@ -18,8 +18,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] -use dasp_sample::Sample; - #[cfg(feature = "hanning")] pub use hanning::Hanning; #[cfg(feature = "rectangle")] @@ -30,8 +28,13 @@ mod hanning; #[cfg(feature = "rectangle")] mod rectangle; -/// The window function used within a `Window`. -pub trait Window { +/// An abstraction supporting different types of `Window` functions. +/// +/// The type `S` represents the phase of the window, while the `Output` represents the window +/// amplitude. +pub trait Window { + /// The type used to represent the window amplitude. + type Output; /// Returns the amplitude for the given phase, given as some `Sample` type. - fn at_phase(phase: S) -> S; + fn window(phase: S) -> Self::Output; } diff --git a/dasp_window/src/rectangle.rs b/dasp_window/src/rectangle.rs index 5c70c75a..c966e325 100644 --- a/dasp_window/src/rectangle.rs +++ b/dasp_window/src/rectangle.rs @@ -12,8 +12,12 @@ use dasp_sample::{FloatSample, Sample}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rectangle; -impl Window for Rectangle { - fn at_phase(_phase: S) -> S { +impl Window for Rectangle +where + S: Sample, +{ + type Output = S; + fn window(_phase: S) -> Self::Output { ::identity().to_sample::() } } From c8baad6414f2c21aefeb558fe1d55c591f5dbaa5 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 17:44:28 +0200 Subject: [PATCH 25/29] Remove potentially unsafe uninitialized ring_buffer constructors --- CHANGELOG.md | 1 + dasp_ring_buffer/src/lib.rs | 85 +++------------------------ dasp_ring_buffer/tests/ring_buffer.rs | 6 +- dasp_signal/src/lib.rs | 6 +- 4 files changed, 16 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ee8ea76..2e075dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - The `dasp` crate has a feature for each of the above. - Make **Window** trait generic over its phase and amplitude type. Update the `dasp_signal::window` module items accordingly. +- Remove unsafe uninitialized ring buffer constructors. --- diff --git a/dasp_ring_buffer/src/lib.rs b/dasp_ring_buffer/src/lib.rs index 48d21999..59fae5d8 100644 --- a/dasp_ring_buffer/src/lib.rs +++ b/dasp_ring_buffer/src/lib.rs @@ -446,20 +446,6 @@ where /// dasp_ring_buffer::Bounded::from(&slice[..]); /// } /// ``` -/// -/// Two slightly more efficient constructors are provided for fixed-size arrays and boxed slices. -/// These are generally more efficient as they do not require initialising elements. -/// -/// ``` -/// fn main() { -/// // Fixed-size array. -/// dasp_ring_buffer::Bounded::<[i32; 4]>::array(); -/// -/// // Boxed slice. -/// let mut rb = dasp_ring_buffer::Bounded::boxed_slice(4); -/// rb.push(1); -/// } -/// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Bounded { start: usize, @@ -475,63 +461,11 @@ pub struct DrainBounded<'a, S: 'a> { bounded: &'a mut Bounded, } -impl Bounded> -where - T: Copy, -{ - /// A `Bounded` ring buffer that uses a `Box`ed slice with the given maximum length to - /// represent the data. - /// - /// Slightly more efficient than using the similar `From` constructor as this creates the - /// underlying slice with uninitialised memory. - /// - /// ``` - /// fn main() { - /// let mut rb = dasp_ring_buffer::Bounded::boxed_slice(4); - /// assert_eq!(rb.max_len(), 4); - /// assert_eq!(rb.len(), 0); - /// rb.push(1); - /// rb.push(2); - /// } - /// ``` - pub fn boxed_slice(max_len: usize) -> Self { - let mut vec = Vec::new(); - vec.reserve_exact(max_len); - unsafe { - vec.set_len(max_len); - let data = vec.into_boxed_slice(); - Self::from_raw_parts(0, 0, data) - } - } -} - impl Bounded where S: Slice, S::Element: Copy, { - /// A `Bounded` buffer that uses a fixed-size array to represent data. - /// - /// Slightly more efficient than using the similar `From` constructor as this creates the - /// underlying array with uninitialised memory. - /// - /// ``` - /// fn main() { - /// let mut rb = dasp_ring_buffer::Bounded::<[f32; 3]>::array(); - /// assert_eq!(rb.len(), 0); - /// assert_eq!(rb.max_len(), 3); - /// } - /// ``` - pub fn array() -> Self - where - S: FixedSizeArray, - { - unsafe { - let data = mem::uninitialized(); - Self::from_raw_parts(0, 0, data) - } - } - /// The same as the `From` implementation, but assumes that the given `data` is full of valid /// elements and initialises the ring buffer with a length equal to `max_len`. /// @@ -555,7 +489,7 @@ where /// /// ``` /// fn main() { - /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::from([0i32; 3]); /// assert_eq!(ring_buffer.max_len(), 3); /// } /// ``` @@ -568,7 +502,7 @@ where /// /// ``` /// fn main() { - /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::from([0i32; 3]); /// assert_eq!(ring_buffer.len(), 0); /// } /// ``` @@ -583,7 +517,7 @@ where /// /// ``` /// fn main() { - /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 2]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::from([0i32; 2]); /// assert!(rb.is_empty()); /// rb.push(0); /// assert!(!rb.is_empty()); @@ -599,7 +533,7 @@ where /// /// ``` /// fn main() { - /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 2]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::from([0i32; 2]); /// assert!(!rb.is_full()); /// rb.push(0); /// rb.push(1); @@ -619,7 +553,7 @@ where /// /// ``` /// fn main() { - /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 4]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::from([0i32; 4]); /// assert_eq!(ring_buffer.slices(), (&[][..], &[][..])); /// ring_buffer.push(1); /// ring_buffer.push(2); @@ -664,7 +598,7 @@ where /// /// ``` /// fn main() { - /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::from([0i32; 3]); /// assert_eq!(rb.iter().count(), 0); /// rb.push(1); /// rb.push(2); @@ -695,7 +629,7 @@ where /// /// ``` /// fn main() { - /// let mut rb = dasp_ring_buffer::Bounded::<[i32; 4]>::array(); + /// let mut rb = dasp_ring_buffer::Bounded::from([0i32; 4]); /// assert_eq!(rb.get(1), None); /// rb.push(0); /// rb.push(1); @@ -736,7 +670,7 @@ where /// /// ``` /// fn main() { - /// let mut ring_buffer = dasp_ring_buffer::Bounded::<[i32; 3]>::array(); + /// let mut ring_buffer = dasp_ring_buffer::Bounded::from([0i32; 3]); /// assert_eq!(ring_buffer.push(1), None); /// assert_eq!(ring_buffer.push(2), None); /// assert_eq!(ring_buffer.len(), 2); @@ -841,8 +775,7 @@ where /// elements when using this method. /// /// **Note:** This method should only be necessary if you require specifying the `start` and - /// initial `len`. Please see the `Bounded::array` and `Bounded::boxed_slice` functions for - /// simpler constructor options that do not require manually passing indices. + /// initial `len`. /// /// **Panic!**s if the following conditions are not met: /// diff --git a/dasp_ring_buffer/tests/ring_buffer.rs b/dasp_ring_buffer/tests/ring_buffer.rs index 948e2578..938ec085 100644 --- a/dasp_ring_buffer/tests/ring_buffer.rs +++ b/dasp_ring_buffer/tests/ring_buffer.rs @@ -2,7 +2,7 @@ use dasp_ring_buffer as ring_buffer; #[test] fn test_bounded_boxed_slice() { - let mut rb = ring_buffer::Bounded::boxed_slice(3); + let mut rb = ring_buffer::Bounded::from(vec![0; 3].into_boxed_slice()); assert_eq!(rb.push(1), None); assert_eq!(rb.push(2), None); assert_eq!(rb.push(3), None); @@ -11,7 +11,7 @@ fn test_bounded_boxed_slice() { #[test] fn test_bounded_array() { - let mut rb = ring_buffer::Bounded::<[i32; 3]>::array(); + let mut rb = ring_buffer::Bounded::from([0i32; 3]); assert_eq!(rb.push(1), None); assert_eq!(rb.push(2), None); assert_eq!(rb.push(3), None); @@ -36,6 +36,6 @@ fn test_bounded_from_vec() { #[test] #[should_panic] fn test_bounded_get_out_of_range() { - let rb = ring_buffer::Bounded::<[i32; 3]>::array(); + let rb = ring_buffer::Bounded::from([0i32; 3]); let _ = rb[0]; } diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index 869cf791..d102926b 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -624,7 +624,7 @@ pub trait Signal { /// /// fn main() { /// let signal = signal::rate(44_100.0).const_hz(440.0).sine(); - /// let ring_buffer = ring_buffer::Bounded::<[[f64; 1]; 64]>::array(); + /// let ring_buffer = ring_buffer::Bounded::from([[0f64; 1]; 64]); /// let mut fork = signal.fork(ring_buffer); /// /// // Forks can be split into their branches via reference. @@ -721,7 +721,7 @@ pub trait Signal { /// fn main() { /// let frames = [[0.1], [0.2], [0.3], [0.4]]; /// let signal = signal::from_iter(frames.iter().cloned()); - /// let ring_buffer = ring_buffer::Bounded::<[[f32; 1]; 2]>::array(); + /// let ring_buffer = ring_buffer::Bounded::from([[0f32; 1]; 2]); /// let mut buffered_signal = signal.buffered(ring_buffer); /// assert_eq!(buffered_signal.next(), [0.1]); /// assert_eq!(buffered_signal.next(), [0.2]); @@ -2406,7 +2406,7 @@ where /// fn main() { /// let frames = [[0.1], [0.2], [0.3], [0.4]]; /// let signal = signal::from_iter(frames.iter().cloned()); - /// let ring_buffer = ring_buffer::Bounded::<[[f32; 1]; 2]>::array(); + /// let ring_buffer = ring_buffer::Bounded::from([[0f32; 1]; 2]); /// let mut buffered_signal = signal.buffered(ring_buffer); /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![[0.1], [0.2]]); /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![[0.3], [0.4]]); From 3a2113cef8bdfedc55fd5104d87412fe55ac6579 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 18:11:17 +0200 Subject: [PATCH 26/29] Remove equilibrium, identity and n_channels in favour of consts Makes the following changes: - `FloatSample::identity` -> `FloatSample::IDENTITY` - `Sample::equilibrium` -> `Sample::EQUILIBRIUM` - `Sample::identity` -> `Sample::IDENTITY` - `Frame::equilibrium` -> `Frame::EQUILIBRIUM` - `Frame::identity` -> `Frame::IDENTITY` - `Frame::n_channels` -> `Frame::CHANNELS` --- CHANGELOG.md | 4 ++ README.md | 8 ++-- dasp_envelope/src/detect/mod.rs | 2 +- dasp_frame/src/lib.rs | 62 +++++++++++++------------- dasp_interpolate/src/sinc/mod.rs | 2 +- dasp_peak/src/lib.rs | 10 ++--- dasp_rms/src/lib.rs | 10 ++--- dasp_sample/src/lib.rs | 74 +++++++++++++------------------- dasp_signal/src/lib.rs | 12 +++--- dasp_slice/src/lib.rs | 2 +- dasp_window/src/rectangle.rs | 4 +- examples/play_wav.rs | 2 +- 12 files changed, 94 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e075dcc..78d923db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ - Make **Window** trait generic over its phase and amplitude type. Update the `dasp_signal::window` module items accordingly. - Remove unsafe uninitialized ring buffer constructors. +- Remove `equilibrium()` and `identity()` constructors from `Sample` and `Frame` + traitsin favour of `EQUILIBRIUM` and `IDENTITY` associated consts. +- Remove `Frame::n_channels` function in favour of `Frame::CHANNELS` associated + const. --- diff --git a/README.md b/README.md index cb6d24a0..28d6d095 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ assert_eq!((-1.0).to_sample::(), 0); assert_eq!(0.0.to_sample::(), 128); assert_eq!(0i32.to_sample::(), 2_147_483_648); assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); -assert_eq!(0.0, Sample::equilibrium()); +assert_eq!(0.0, Sample::EQUILIBRIUM); ``` Use the **Frame** trait to remain generic over the number of channels at a @@ -68,9 +68,9 @@ let foo = [0.1, 0.2, -0.1, -0.2]; let bar = foo.scale_amp(2.0); assert_eq!(bar, [0.2, 0.4, -0.2, -0.4]); -assert_eq!(Mono::::equilibrium(), [0.0]); -assert_eq!(Stereo::::equilibrium(), [0.0, 0.0]); -assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); +assert_eq!(Mono::::EQUILIBRIUM, [0.0]); +assert_eq!(Stereo::::EQUILIBRIUM, [0.0, 0.0]); +assert_eq!(<[f32; 3]>::EQUILIBRIUM, [0.0, 0.0, 0.0]); let foo = [0i16, 0]; let bar: [u8; 2] = foo.map(Sample::to_sample); diff --git a/dasp_envelope/src/detect/mod.rs b/dasp_envelope/src/detect/mod.rs index e10632ba..e4c3c72a 100644 --- a/dasp_envelope/src/detect/mod.rs +++ b/dasp_envelope/src/detect/mod.rs @@ -51,7 +51,7 @@ where /// Construct a **Detector** with the given **Detect** implementation. pub fn new(detect: D, attack_frames: f32, release_frames: f32) -> Self { Detector { - last_env_frame: D::Output::equilibrium(), + last_env_frame: D::Output::EQUILIBRIUM, attack_gain: calc_gain(attack_frames), release_gain: calc_gain(release_frames), detect: detect, diff --git a/dasp_frame/src/lib.rs b/dasp_frame/src/lib.rs index 93f64286..a14022a0 100644 --- a/dasp_frame/src/lib.rs +++ b/dasp_frame/src/lib.rs @@ -28,7 +28,21 @@ pub trait Frame: Copy + Clone + PartialEq { /// The equilibrium value for the wave that this `Sample` type represents. This is normally the /// value that is equal distance from both the min and max ranges of the sample. /// - /// **NOTE:** This will likely be changed to an "associated const" if the feature lands. + /// # Examples + /// + /// ```rust + /// use dasp_frame::{Frame, Mono, Stereo}; + /// + /// fn main() { + /// assert_eq!(Mono::::EQUILIBRIUM, [0.0]); + /// assert_eq!(Stereo::::EQUILIBRIUM, [0.0, 0.0]); + /// assert_eq!(<[f32; 3]>::EQUILIBRIUM, [0.0, 0.0, 0.0]); + /// assert_eq!(<[u8; 2]>::EQUILIBRIUM, [128u8, 128]); + /// } + /// ``` + const EQUILIBRIUM: Self; + + /// The total number of channels within the frame. /// /// # Examples /// @@ -36,13 +50,13 @@ pub trait Frame: Copy + Clone + PartialEq { /// use dasp_frame::{Frame, Mono, Stereo}; /// /// fn main() { - /// assert_eq!(Mono::::equilibrium(), [0.0]); - /// assert_eq!(Stereo::::equilibrium(), [0.0, 0.0]); - /// assert_eq!(<[f32; 3]>::equilibrium(), [0.0, 0.0, 0.0]); - /// assert_eq!(<[u8; 2]>::equilibrium(), [128u8, 128]); + /// assert_eq!(Mono::::CHANNELS, 1); + /// assert_eq!(Stereo::::CHANNELS, 2); + /// assert_eq!(<[f32; 3]>::CHANNELS, 3); + /// assert_eq!(<[u8; 2]>::CHANNELS, 2); /// } /// ``` - fn equilibrium() -> Self; + const CHANNELS: usize; /// Create a new `Frame` where the `Sample` for each channel is produced by the given function. /// @@ -61,9 +75,6 @@ pub trait Frame: Copy + Clone + PartialEq { where I: Iterator; - /// The total number of channels (and in turn samples) stored within the frame. - fn n_channels() -> usize; - /// Converts the frame into an iterator yielding the sample for each channel in the frame. fn channels(self) -> Self::Channels; @@ -224,16 +235,6 @@ pub trait Frame: Copy + Clone + PartialEq { } } -pub type Mono = [S; 1]; -pub type Stereo = [S; 2]; - -/// An iterator that yields the sample for each channel in the frame by value. -#[derive(Clone)] -pub struct Channels { - next_idx: usize, - frame: F, -} - /// Restricts the types that may be used as the `Frame::NumChannels` associated type. /// /// `NumChannels` allows us to enforce the number of channels that a `Frame` must have in certain @@ -244,6 +245,16 @@ pub struct Channels { /// This trait is implemented for types `N1`...`N32`. pub trait NumChannels {} +pub type Mono = [S; 1]; +pub type Stereo = [S; 2]; + +/// An iterator that yields the sample for each channel in the frame by value. +#[derive(Clone)] +pub struct Channels { + next_idx: usize, + frame: F, +} + macro_rules! impl_frame { ($($NChan:ident $N:expr, [$($idx:expr)*],)*) => { $( @@ -261,15 +272,8 @@ macro_rules! impl_frame { type Float = [S::Float; $N]; type Signed = [S::Signed; $N]; - #[inline] - fn equilibrium() -> Self { - [S::equilibrium(); $N] - } - - #[inline] - fn n_channels() -> usize { - $N - } + const EQUILIBRIUM: Self = [S::EQUILIBRIUM; $N]; + const CHANNELS: usize = $N; #[inline] fn channels(self) -> Self::Channels { @@ -438,6 +442,6 @@ where { #[inline] fn len(&self) -> usize { - F::n_channels() - self.next_idx + F::CHANNELS - self.next_idx } } diff --git a/dasp_interpolate/src/sinc/mod.rs b/dasp_interpolate/src/sinc/mod.rs index 1fefe926..3557b252 100644 --- a/dasp_interpolate/src/sinc/mod.rs +++ b/dasp_interpolate/src/sinc/mod.rs @@ -88,7 +88,7 @@ where depth }; - (0..max_depth).fold(Self::Frame::equilibrium(), |mut v, n| { + (0..max_depth).fold(Self::Frame::EQUILIBRIUM, |mut v, n| { v = { let a = PI * (phil + n as f64); let first = if a == 0.0 { 1.0 } else { sin(a) / a }; diff --git a/dasp_peak/src/lib.rs b/dasp_peak/src/lib.rs index 05b8e005..fda9eb12 100644 --- a/dasp_peak/src/lib.rs +++ b/dasp_peak/src/lib.rs @@ -63,7 +63,7 @@ where { frame.map(|s| { let signed = s.to_signed_sample(); - if signed < Sample::equilibrium() { + if signed < Sample::EQUILIBRIUM { -signed } else { signed @@ -77,8 +77,8 @@ where F: Frame, { frame.map(|s| { - if s < Sample::equilibrium() { - Sample::equilibrium() + if s < Sample::EQUILIBRIUM { + Sample::EQUILIBRIUM } else { s } @@ -91,8 +91,8 @@ where F: Frame, { frame.map(|s| { - if s > Sample::equilibrium() { - Sample::equilibrium() + if s > Sample::EQUILIBRIUM { + Sample::EQUILIBRIUM } else { s } diff --git a/dasp_rms/src/lib.rs b/dasp_rms/src/lib.rs index ebb7114e..8322885e 100644 --- a/dasp_rms/src/lib.rs +++ b/dasp_rms/src/lib.rs @@ -53,7 +53,7 @@ where Rms { frame: PhantomData, window: ring_buffer, - square_sum: Frame::equilibrium(), + square_sum: Frame::EQUILIBRIUM, } } @@ -77,9 +77,9 @@ where S: ring_buffer::SliceMut, { for sample_square in self.window.iter_mut() { - *sample_square = Frame::equilibrium(); + *sample_square = Frame::EQUILIBRIUM; } - self.square_sum = Frame::equilibrium(); + self.square_sum = Frame::EQUILIBRIUM; } /// The length of the window as a number of frames. @@ -149,8 +149,8 @@ where .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() + if diff < Sample::EQUILIBRIUM { + Sample::EQUILIBRIUM } else { diff } diff --git a/dasp_sample/src/lib.rs b/dasp_sample/src/lib.rs index 4e5358d8..bb3ef8d2 100644 --- a/dasp_sample/src/lib.rs +++ b/dasp_sample/src/lib.rs @@ -38,7 +38,7 @@ pub mod types; /// assert_eq!(0.0.to_sample::(), 128); /// assert_eq!(0i32.to_sample::(), 2_147_483_648); /// assert_eq!(I24::new(0).unwrap(), Sample::from_sample(0.0)); -/// assert_eq!(0.0, Sample::equilibrium()); +/// assert_eq!(0.0, Sample::EQUILIBRIUM); /// } /// ``` pub trait Sample: Copy + Clone + PartialOrd + PartialEq { @@ -80,15 +80,15 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// use dasp_sample::Sample; /// /// fn main() { - /// assert_eq!(0.0, f32::equilibrium()); - /// assert_eq!(0, i32::equilibrium()); - /// assert_eq!(128, u8::equilibrium()); - /// assert_eq!(32_768_u16, Sample::equilibrium()); + /// assert_eq!(0.0, f32::EQUILIBRIUM); + /// assert_eq!(0, i32::EQUILIBRIUM); + /// assert_eq!(128, u8::EQUILIBRIUM); + /// assert_eq!(32_768_u16, Sample::EQUILIBRIUM); /// } /// ``` /// /// **Note:** This will likely be changed to an "associated const" if the feature lands. - fn equilibrium() -> Self; + const EQUILIBRIUM: Self; /// The multiplicative identity of the signal. /// @@ -103,16 +103,13 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// use dasp_sample::{Sample, U48}; /// /// fn main() { - /// assert_eq!(1.0, f32::identity()); - /// assert_eq!(1.0, i8::identity()); - /// assert_eq!(1.0, u8::identity()); - /// assert_eq!(1.0, U48::identity()); + /// assert_eq!(1.0, f32::IDENTITY); + /// assert_eq!(1.0, i8::IDENTITY); + /// assert_eq!(1.0, u8::IDENTITY); + /// assert_eq!(1.0, U48::IDENTITY); /// } /// ``` - #[inline] - fn identity() -> Self::Float { - ::identity() - } + const IDENTITY: Self::Float = ::IDENTITY; /// Convert `self` to any type that implements `FromSample`. /// @@ -224,7 +221,7 @@ pub trait Sample: Copy + Clone + PartialOrd + PartialEq { /// - `amp` > 1.0 amplifies the sample. /// - `amp` < 1.0 attenuates the sample. /// - `amp` == 1.0 yields the same sample. - /// - `amp` == 0.0 yields the `Sample::equilibrium`. + /// - `amp` == 0.0 yields the `Sample::EQUILIBRIUM`. /// /// `Self` will be converted to `Self::Float`, the multiplication will occur and then the /// result will be converted back to `Self`. These conversions allow us to correctly handle the @@ -253,16 +250,13 @@ macro_rules! impl_sample { ($($T:ty: Signed: $Addition:ty, Float: $Modulation:ty, - equilibrium: $equilibrium:expr),*) => + EQUILIBRIUM: $EQUILIBRIUM:expr),*) => { $( impl Sample for $T { type Signed = $Addition; type Float = $Modulation; - #[inline] - fn equilibrium() -> Self { - $equilibrium - } + const EQUILIBRIUM: Self = $EQUILIBRIUM; } )* } @@ -270,20 +264,20 @@ macro_rules! impl_sample { // Expands to `Sample` implementations for all of the following types. impl_sample! { - i8: Signed: i8, Float: f32, equilibrium: 0, - i16: Signed: i16, Float: f32, equilibrium: 0, - I24: Signed: I24, Float: f32, equilibrium: types::i24::EQUILIBRIUM, - i32: Signed: i32, Float: f32, equilibrium: 0, - I48: Signed: I48, Float: f64, equilibrium: types::i48::EQUILIBRIUM, - i64: Signed: i64, Float: f64, equilibrium: 0, - u8: Signed: i8, Float: f32, equilibrium: 128, - u16: Signed: i16, Float: f32, equilibrium: 32_768, - U24: Signed: i32, Float: f32, equilibrium: types::u24::EQUILIBRIUM, - u32: Signed: i32, Float: f32, equilibrium: 2_147_483_648, - U48: Signed: i64, Float: f64, equilibrium: types::u48::EQUILIBRIUM, - u64: Signed: i64, Float: f64, equilibrium: 9_223_372_036_854_775_808, - f32: Signed: f32, Float: f32, equilibrium: 0.0, - f64: Signed: f64, Float: f64, equilibrium: 0.0 + i8: Signed: i8, Float: f32, EQUILIBRIUM: 0, + i16: Signed: i16, Float: f32, EQUILIBRIUM: 0, + I24: Signed: I24, Float: f32, EQUILIBRIUM: types::i24::EQUILIBRIUM, + i32: Signed: i32, Float: f32, EQUILIBRIUM: 0, + I48: Signed: I48, Float: f64, EQUILIBRIUM: types::i48::EQUILIBRIUM, + i64: Signed: i64, Float: f64, EQUILIBRIUM: 0, + u8: Signed: i8, Float: f32, EQUILIBRIUM: 128, + u16: Signed: i16, Float: f32, EQUILIBRIUM: 32_768, + U24: Signed: i32, Float: f32, EQUILIBRIUM: types::u24::EQUILIBRIUM, + u32: Signed: i32, Float: f32, EQUILIBRIUM: 2_147_483_648, + U48: Signed: i64, Float: f64, EQUILIBRIUM: types::u48::EQUILIBRIUM, + u64: Signed: i64, Float: f64, EQUILIBRIUM: 9_223_372_036_854_775_808, + f32: Signed: f32, Float: f32, EQUILIBRIUM: 0.0, + f64: Signed: f64, Float: f64, EQUILIBRIUM: 0.0 } /// Integral and floating-point **Sample** format types whose equilibrium is at 0. @@ -313,16 +307,13 @@ pub trait FloatSample: + Duplex { /// Represents the multiplicative identity of the floating point signal. - fn identity() -> Self; + const IDENTITY: Self; /// Calculate the square root of `Self`. fn sample_sqrt(self) -> Self; } impl FloatSample for f32 { - #[inline] - fn identity() -> Self { - 1.0 - } + const IDENTITY: Self = 1.0; #[inline] fn sample_sqrt(self) -> Self { ops::f32::sqrt(self) @@ -330,10 +321,7 @@ impl FloatSample for f32 { } impl FloatSample for f64 { - #[inline] - fn identity() -> Self { - 1.0 - } + const IDENTITY: Self = 1.0; #[inline] fn sample_sqrt(self) -> Self { ops::f64::sqrt(self) diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index d102926b..6a30867c 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -489,7 +489,7 @@ pub trait Signal { /// Delays the `Signal` by the given number of frames. /// - /// The delay is performed by yielding `Frame::equilibrium()` `n_frames` times before + /// The delay is performed by yielding `Frame::EQUILIBRIUM` `n_frames` times before /// continuing to yield frames from `signal`. /// /// # Example @@ -1008,7 +1008,7 @@ where /// Delays the `signal` by the given number of frames. /// -/// The delay is performed by yielding `Frame::equilibrium()` `n_frames` times before +/// The delay is performed by yielding `Frame::EQUILIBRIUM` `n_frames` times before /// continuing to yield frames from `signal`. #[derive(Clone)] pub struct Delay { @@ -1569,7 +1569,7 @@ where self.next = self.iter.next(); frame } - None => Frame::equilibrium(), + None => Frame::EQUILIBRIUM, } } @@ -1594,7 +1594,7 @@ where self.next = F::from_samples(&mut self.samples); frame } - None => F::equilibrium(), + None => F::EQUILIBRIUM, } } @@ -1612,7 +1612,7 @@ where #[inline] fn next(&mut self) -> Self::Frame { - F::equilibrium() + F::EQUILIBRIUM } } @@ -2223,7 +2223,7 @@ where fn next(&mut self) -> Self::Frame { if self.n_frames > 0 { self.n_frames -= 1; - Self::Frame::equilibrium() + Self::Frame::EQUILIBRIUM } else { self.signal.next() } diff --git a/dasp_slice/src/lib.rs b/dasp_slice/src/lib.rs index 0d73c532..8d3264c5 100644 --- a/dasp_slice/src/lib.rs +++ b/dasp_slice/src/lib.rs @@ -289,7 +289,7 @@ pub fn equilibrium(a: &mut [F]) where F: Frame, { - map_in_place(a, |_| F::equilibrium()) + map_in_place(a, |_| F::EQUILIBRIUM) } /// Mutate every frame in slice `a` while reading from each frame in slice `b` in lock-step using diff --git a/dasp_window/src/rectangle.rs b/dasp_window/src/rectangle.rs index c966e325..29cf69e1 100644 --- a/dasp_window/src/rectangle.rs +++ b/dasp_window/src/rectangle.rs @@ -1,6 +1,6 @@ use crate::Window; -use dasp_sample::{FloatSample, Sample}; +use dasp_sample::Sample; /// The simplest window type, equivalent to replacing all but *N* values of data sequence by /// zeroes, making it appear as though the waveform suddenly turns on and off. @@ -18,6 +18,6 @@ where { type Output = S; fn window(_phase: S) -> Self::Output { - ::identity().to_sample::() + S::IDENTITY.to_sample::() } } diff --git a/examples/play_wav.rs b/examples/play_wav.rs index 29e08392..1c5d89b4 100644 --- a/examples/play_wav.rs +++ b/examples/play_wav.rs @@ -44,7 +44,7 @@ fn main() -> Result<(), anyhow::Error> { Some(frame) => *out_frame = frame, None => { complete_tx.try_send(()).ok(); - *out_frame = dasp::Frame::equilibrium(); + *out_frame = dasp::Frame::EQUILIBRIUM; } } } From 3aaa3a9f6e70a05cd4f0bd346c62d1b50c8d4630 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 19:26:27 +0200 Subject: [PATCH 27/29] Add implementation of `Frame` for primitive types implementing `Sample` This greatly simplifies working with monophonic signals, as we no longer have to use a fixed size array of one element everywhere. --- CHANGELOG.md | 4 + README.md | 16 +-- dasp_frame/src/lib.rs | 140 +++++++++++++++++++++++- dasp_signal/src/envelope.rs | 2 +- dasp_signal/src/lib.rs | 182 ++++++++++++++++--------------- dasp_signal/tests/interpolate.rs | 40 ++++--- dasp_signal/tests/signal.rs | 12 +- dasp_signal/tests/window.rs | 26 ++--- examples/Cargo.toml | 2 +- examples/synth.rs | 2 +- 10 files changed, 282 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d923db..d450b0c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,10 @@ traitsin favour of `EQUILIBRIUM` and `IDENTITY` associated consts. - Remove `Frame::n_channels` function in favour of `Frame::CHANNELS` associated const. +- Add implementation of `Frame` for all primitive `Sample` types where each are + assumed to represent a frame of a monophonic signal. This greatly simplifies + working with monophonic signal sources as demonstrated in the updated + `dasp_signal` crate. --- diff --git a/README.md b/README.md index 28d6d095..ac4bddf2 100644 --- a/README.md +++ b/README.md @@ -90,25 +90,25 @@ let clipped: Vec<_> = signal::from_iter(frames.iter().cloned()).clip_amp(0.9).ta assert_eq!(clipped, vec![[0.9, 0.8], [-0.7, -0.9]]); // Add `a` with `b` and yield the result. -let a = [[0.2], [-0.6], [0.5]]; -let b = [[0.2], [0.1], [-0.8]]; +let a = [0.2, -0.6, 0.5]; +let b = [0.2, 0.1, -0.8]; let a_signal = signal::from_iter(a.iter().cloned()); let b_signal = signal::from_iter(b.iter().cloned()); -let added: Vec<[f32; 1]> = a_signal.add_amp(b_signal).take(3).collect(); -assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); +let added: Vec = a_signal.add_amp(b_signal).take(3).collect(); +assert_eq!(added, vec![0.4, -0.5, -0.3]); // Scale the playback rate by `0.5`. -let foo = [[0.0], [1.0], [0.0], [-1.0]]; +let foo = [0.0, 1.0, 0.0, -1.0]; let mut source = signal::from_iter(foo.iter().cloned()); let a = source.next(); let b = source.next(); let interp = Linear::new(a, b); let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); -assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); +assert_eq!(&frames[..], &[0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5][..]); // Convert a signal to its RMS. let signal = signal::rate(44_100.0).const_hz(440.0).sine();; -let ring_buffer = ring_buffer::Fixed::from([[0.0]; WINDOW_SIZE]); +let ring_buffer = ring_buffer::Fixed::from([0.0; WINDOW_SIZE]); let mut rms_signal = signal.rms(ring_buffer); ``` @@ -177,7 +177,7 @@ let detector = envelope::Detector::peak(attack, release); let mut envelope = signal.detect_envelope(detector); assert_eq!( envelope.take(4).collect::>(), - vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] + vec![0.0, 0.6321205496788025, 0.23254416035257117, 0.7176687675647109] ); ``` diff --git a/dasp_frame/src/lib.rs b/dasp_frame/src/lib.rs index a14022a0..bab033ab 100644 --- a/dasp_frame/src/lib.rs +++ b/dasp_frame/src/lib.rs @@ -10,7 +10,10 @@ use dasp_sample::Sample; /// Represents one sample from each channel at a single discrete instance in time within a /// PCM signal. /// -/// We provide implementations for `Frame` for all fixed-size arrays up to a length of 32 elements. +/// Implementations are provided for: +/// +/// - All fixed-size arrays up to a length of 32 elements. +/// - All primitive types that implement `Sample`. These implementations assume `CHANNELS = 1`. pub trait Frame: Copy + Clone + PartialEq { /// The type of PCM sample stored at each channel within the frame. type Sample: Sample; @@ -255,7 +258,7 @@ pub struct Channels { frame: F, } -macro_rules! impl_frame { +macro_rules! impl_frame_for_fixed_size_array { ($($NChan:ident $N:expr, [$($idx:expr)*],)*) => { $( /// A typified version of a number of channels. @@ -381,13 +384,12 @@ macro_rules! impl_frame { [$(self[$idx].add_amp(*other.channel_unchecked($idx)), )*] } } - } )* }; } -impl_frame! { +impl_frame_for_fixed_size_array! { N1 1, [0], N2 2, [0 1], N3 3, [0 1 2], @@ -422,6 +424,136 @@ impl_frame! { N32 32, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31], } +macro_rules! impl_frame_for_sample { + ($($T:ty)*) => { + $( + impl Frame for $T { + type Sample = $T; + type NumChannels = N1; + type Channels = Channels; + type Float = <$T as Sample>::Float; + type Signed = <$T as Sample>::Signed; + + const EQUILIBRIUM: Self = <$T as Sample>::EQUILIBRIUM; + const CHANNELS: usize = 1; + + #[inline] + fn channels(self) -> Self::Channels { + Channels { + next_idx: 0, + frame: self, + } + } + + #[inline] + fn channel(&self, idx: usize) -> Option<&Self::Sample> { + if idx == 0 { + Some(self) + } else { + None + } + } + + #[inline] + fn from_fn(mut from: F) -> Self + where + F: FnMut(usize) -> Self::Sample, + { + from(0) + } + + #[inline] + fn from_samples(samples: &mut I) -> Option + where + I: Iterator + { + samples.next() + } + + #[inline(always)] + unsafe fn channel_unchecked(&self, _idx: usize) -> &Self::Sample { + self + } + + #[inline] + fn to_signed_frame(self) -> Self::Signed { + self.to_signed_sample() + } + + #[inline] + fn to_float_frame(self) -> Self::Float { + self.to_float_sample() + } + + #[inline] + fn map(self, mut map: M) -> F + where + F: Frame, + M: FnMut(Self::Sample) -> F::Sample, + { + F::from_fn(|channel_idx| { + // Here we do not require run-time bounds checking as we have asserted that + // the two arrays have the same number of channels at compile time with our + // where clause, i.e. + // + // `F: Frame` + unsafe { map(*self.channel_unchecked(channel_idx)) } + }) + } + + #[inline] + fn zip_map(self, other: O, mut zip_map: M) -> F + where + O: Frame, + F: Frame, + M: FnMut(Self::Sample, O::Sample) -> F::Sample + { + F::from_fn(|channel_idx| { + // Here we do not require run-time bounds checking as we have asserted that the two + // arrays have the same number of channels at compile time with our where clause, i.e. + // + // ``` + // O: Frame + // F: Frame + // ``` + unsafe { + zip_map(*self.channel_unchecked(channel_idx), + *other.channel_unchecked(channel_idx)) + } + }) + } + + #[inline] + fn scale_amp(self, amp: <$T as Sample>::Float) -> Self { + Sample::mul_amp(self, amp) + } + + #[inline] + fn add_amp(self, other: F) -> Self + where + F: Frame::Signed, NumChannels=N1>, + { + // Here we do not require run-time bounds checking as we have asserted that the two + // arrays have the same number of channels at compile time with our where clause, i.e. + unsafe { + Sample::add_amp(self, *other.channel_unchecked(0)) + } + } + } + )* + }; +} + +impl_frame_for_sample! { + i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 +} +impl_frame_for_sample! { + dasp_sample::types::I24 + dasp_sample::types::I48 + dasp_sample::types::U24 + dasp_sample::types::U48 +} + impl Iterator for Channels where F: Frame, diff --git a/dasp_signal/src/envelope.rs b/dasp_signal/src/envelope.rs index c740bb45..5383beb9 100644 --- a/dasp_signal/src/envelope.rs +++ b/dasp_signal/src/envelope.rs @@ -32,7 +32,7 @@ pub trait SignalEnvelope: Signal { /// let mut envelope = signal.detect_envelope(detector); /// assert_eq!( /// envelope.take(4).collect::>(), - /// vec![[0.0], [0.6321205496788025], [0.23254416035257117], [0.7176687675647109]] + /// vec![0.0, 0.6321205496788025, 0.23254416035257117, 0.7176687675647109] /// ); /// } /// ``` diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index 6a30867c..63ce6fe6 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -174,10 +174,10 @@ pub trait Signal { /// let sine_wave = signal::rate(4.0).const_hz(1.0).sine(); /// let mut peak = sine_wave /// .map(peak::full_wave) - /// .map(|f| [f[0].round()]); + /// .map(|f| f.round()); /// assert_eq!( /// peak.take(4).collect::>(), - /// vec![[0.0], [1.0], [0.0], [1.0]] + /// vec![0.0, 1.0, 0.0, 1.0] /// ); /// } /// ``` @@ -412,21 +412,21 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; - /// let mul = [[1.0], [1.0], [0.5], [0.5], [0.5], [0.5]]; + /// let foo = [0.0, 1.0, 0.0, -1.0]; + /// let mul = [1.0, 1.0, 0.5, 0.5, 0.5, 0.5]; /// let mut source = signal::from_iter(foo.iter().cloned()); /// let a = source.next(); /// let b = source.next(); /// let interp = Linear::new(a, b); /// let hz_signal = signal::from_iter(mul.iter().cloned()); /// let frames: Vec<_> = source.mul_hz(interp, hz_signal).take(6).collect(); - /// assert_eq!(&frames[..], &[[0.0], [1.0], [0.0], [-0.5], [-1.0], [-0.5]][..]); + /// assert_eq!(&frames[..], &[0.0, 1.0, 0.0, -0.5, -1.0, -0.5][..]); /// } /// ``` fn mul_hz(self, interpolator: I, mul_per_frame: M) -> MulHz where Self: Sized, - M: Signal, + M: Signal, I: Interpolator, { MulHz { @@ -573,17 +573,17 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let mut f = [0.0]; + /// let mut f = 0.0; /// let mut signal = signal::gen_mut(move || { - /// f[0] += 0.1; + /// f += 0.1; /// f /// }); - /// let func = |x: &[f64; 1]| { - /// assert_eq!(*x, [0.1]); + /// let func = |x: &f64| { + /// assert_eq!(*x, 0.1); /// }; /// let mut inspected = signal.inspect(func); /// let out = inspected.next(); - /// assert_eq!(out, [0.1]); + /// assert_eq!(out, 0.1); /// } /// ``` fn inspect(self, inspect: F) -> Inspect @@ -624,7 +624,7 @@ pub trait Signal { /// /// fn main() { /// let signal = signal::rate(44_100.0).const_hz(440.0).sine(); - /// let ring_buffer = ring_buffer::Bounded::from([[0f64; 1]; 64]); + /// let ring_buffer = ring_buffer::Bounded::from([0f64; 64]); /// let mut fork = signal.fork(ring_buffer); /// /// // Forks can be split into their branches via reference. @@ -719,15 +719,15 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.1], [0.2], [0.3], [0.4]]; + /// let frames = [0.1, 0.2, 0.3, 0.4]; /// let signal = signal::from_iter(frames.iter().cloned()); - /// let ring_buffer = ring_buffer::Bounded::from([[0f32; 1]; 2]); + /// let ring_buffer = ring_buffer::Bounded::from([0f32; 2]); /// let mut buffered_signal = signal.buffered(ring_buffer); - /// assert_eq!(buffered_signal.next(), [0.1]); - /// assert_eq!(buffered_signal.next(), [0.2]); - /// assert_eq!(buffered_signal.next(), [0.3]); - /// assert_eq!(buffered_signal.next(), [0.4]); - /// assert_eq!(buffered_signal.next(), [0.0]); + /// assert_eq!(buffered_signal.next(), 0.1); + /// assert_eq!(buffered_signal.next(), 0.2); + /// assert_eq!(buffered_signal.next(), 0.3); + /// assert_eq!(buffered_signal.next(), 0.4); + /// assert_eq!(buffered_signal.next(), 0.0); /// } /// ``` /// @@ -738,17 +738,17 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.1], [0.2], [0.3], [0.4]]; + /// let frames = [0.1, 0.2, 0.3, 0.4]; /// let signal = signal::from_iter(frames.iter().cloned()); - /// let ring_buffer = ring_buffer::Bounded::from_full([[0.8], [0.9]]); + /// let ring_buffer = ring_buffer::Bounded::from_full([0.8, 0.9]); /// let mut buffered_signal = signal.buffered(ring_buffer); - /// assert_eq!(buffered_signal.next(), [0.8]); - /// assert_eq!(buffered_signal.next(), [0.9]); - /// assert_eq!(buffered_signal.next(), [0.1]); - /// assert_eq!(buffered_signal.next(), [0.2]); - /// assert_eq!(buffered_signal.next(), [0.3]); - /// assert_eq!(buffered_signal.next(), [0.4]); - /// assert_eq!(buffered_signal.next(), [0.0]); + /// assert_eq!(buffered_signal.next(), 0.8); + /// assert_eq!(buffered_signal.next(), 0.9); + /// assert_eq!(buffered_signal.next(), 0.1); + /// assert_eq!(buffered_signal.next(), 0.2); + /// assert_eq!(buffered_signal.next(), 0.3); + /// assert_eq!(buffered_signal.next(), 0.4); + /// assert_eq!(buffered_signal.next(), 0.0); /// } /// ``` fn buffered(self, ring_buffer: ring_buffer::Bounded) -> Buffered @@ -773,12 +773,12 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0], [1], [2], [3], [4]]; + /// let frames = [0, 1, 2, 3, 4]; /// let mut signal = signal::from_iter(frames.iter().cloned()); - /// assert_eq!(signal.next(), [0]); - /// assert_eq!(signal.by_ref().take(2).collect::>(), vec![[1], [2]]); - /// assert_eq!(signal.next(), [3]); - /// assert_eq!(signal.next(), [4]); + /// assert_eq!(signal.next(), 0); + /// assert_eq!(signal.by_ref().take(2).collect::>(), vec![1, 2]); + /// assert_eq!(signal.next(), 3); + /// assert_eq!(signal.next(), 4); /// } /// ``` fn by_ref(&mut self) -> &mut Self @@ -1249,8 +1249,8 @@ pub struct BufferedFrames<'a, D: 'a> { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { -/// let equilibrium: Vec<[f32; 1]> = signal::equilibrium().take(4).collect(); -/// assert_eq!(equilibrium, vec![[0.0], [0.0], [0.0], [0.0]]); +/// let equilibrium: Vec = signal::equilibrium().take(4).collect(); +/// assert_eq!(equilibrium, vec![0.0, 0.0, 0.0, 0.0]); /// /// let equilibrium: Vec<[u8; 2]> = signal::equilibrium().take(3).collect(); /// assert_eq!(equilibrium, vec![[128, 128], [128, 128], [128, 128]]); @@ -1410,12 +1410,12 @@ where /// let step = signal::rate(4.0).const_hz(1.0); /// // Note that this is the same as `step.phase()`, a composable alternative. /// let mut phase = signal::phase(step); -/// assert_eq!(phase.next(), [0.0]); -/// assert_eq!(phase.next(), [0.25]); -/// assert_eq!(phase.next(), [0.5]); -/// assert_eq!(phase.next(), [0.75]); -/// assert_eq!(phase.next(), [0.0]); -/// assert_eq!(phase.next(), [0.25]); +/// assert_eq!(phase.next(), 0.0); +/// assert_eq!(phase.next(), 0.25); +/// assert_eq!(phase.next(), 0.5); +/// assert_eq!(phase.next(), 0.75); +/// assert_eq!(phase.next(), 0.0); +/// assert_eq!(phase.next(), 0.25); /// } /// ``` pub fn phase(step: S) -> Phase @@ -1447,10 +1447,10 @@ pub fn rate(hz: f64) -> Rate { /// fn main() { /// // Generates a sine wave signal at 1hz to be sampled 4 times per second. /// let mut signal = signal::rate(4.0).const_hz(1.0).sine(); -/// assert_eq!(signal.next(), [0.0]); -/// assert_eq!(signal.next(), [1.0]); +/// assert_eq!(signal.next(), 0.0); +/// assert_eq!(signal.next(), 1.0); /// signal.next(); -/// assert_eq!(signal.next(), [-1.0]); +/// assert_eq!(signal.next(), -1.0); /// } /// ``` pub fn sine(phase: Phase) -> Sine { @@ -1467,10 +1467,10 @@ pub fn sine(phase: Phase) -> Sine { /// fn main() { /// // Generates a saw wave signal at 1hz to be sampled 4 times per second. /// let mut signal = signal::rate(4.0).const_hz(1.0).saw(); -/// assert_eq!(signal.next(), [1.0]); -/// assert_eq!(signal.next(), [0.5]); -/// assert_eq!(signal.next(), [0.0]); -/// assert_eq!(signal.next(), [-0.5]); +/// assert_eq!(signal.next(), 1.0); +/// assert_eq!(signal.next(), 0.5); +/// assert_eq!(signal.next(), 0.0); +/// assert_eq!(signal.next(), -0.5); /// } /// ``` pub fn saw(phase: Phase) -> Saw { @@ -1487,10 +1487,10 @@ pub fn saw(phase: Phase) -> Saw { /// fn main() { /// // Generates a square wave signal at 1hz to be sampled 4 times per second. /// let mut signal = signal::rate(4.0).const_hz(1.0).square(); -/// assert_eq!(signal.next(), [1.0]); -/// assert_eq!(signal.next(), [1.0]); -/// assert_eq!(signal.next(), [-1.0]); -/// assert_eq!(signal.next(), [-1.0]); +/// assert_eq!(signal.next(), 1.0); +/// assert_eq!(signal.next(), 1.0); +/// assert_eq!(signal.next(), -1.0); +/// assert_eq!(signal.next(), -1.0); /// } /// ``` pub fn square(phase: Phase) -> Square { @@ -1507,7 +1507,7 @@ pub fn square(phase: Phase) -> Square { /// fn main() { /// let mut noise = signal::noise(0); /// for n in noise.take(1_000_000) { -/// assert!(-1.0 <= n[0] && n[0] < 1.0); +/// assert!(-1.0 <= n && n < 1.0); /// } /// } /// ``` @@ -1528,7 +1528,7 @@ pub fn noise(seed: u64) -> Noise { /// // Creates a simplex noise signal oscillating at 440hz sampled 44_100 times per second. /// let mut signal = signal::rate(44_100.0).const_hz(440.0).noise_simplex(); /// for n in signal.take(1_000_000) { -/// assert!(-1.0 <= n[0] && n[0] < 1.0); +/// assert!(-1.0 <= n && n < 1.0); /// } /// } /// ``` @@ -1681,13 +1681,13 @@ where impl Signal for Hz where - S: Signal, + S: Signal, { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { - [self.step()] + self.step() } #[inline] @@ -1697,11 +1697,11 @@ where } impl Signal for ConstHz { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { - [self.step()] + self.step() } } @@ -1709,11 +1709,11 @@ impl Signal for Phase where S: Step, { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { - [self.next_phase()] + self.next_phase() } } @@ -1721,13 +1721,13 @@ impl Signal for Sine where S: Step, { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { const PI_2: f64 = core::f64::consts::PI * 2.0; let phase = self.phase.next_phase(); - [ops::f64::sin(PI_2 * phase)] + ops::f64::sin(PI_2 * phase) } } @@ -1735,12 +1735,12 @@ impl Signal for Saw where S: Step, { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { let phase = self.phase.next_phase(); - [phase * -2.0 + 1.0] + phase * -2.0 + 1.0 } } @@ -1748,12 +1748,16 @@ impl Signal for Square where S: Step, { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { let phase = self.phase.next_phase(); - [if phase < 0.5 { 1.0 } else { -1.0 }] + if phase < 0.5 { + 1.0 + } else { + -1.0 + } } } @@ -1772,19 +1776,19 @@ impl Rate { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let step = signal::rate(4.0).hz(signal::gen(|| [1.0])); + /// let step = signal::rate(4.0).hz(signal::gen(|| 1.0)); /// let mut phase = signal::phase(step); - /// assert_eq!(phase.next(), [0.0]); - /// assert_eq!(phase.next(), [0.25]); - /// assert_eq!(phase.next(), [0.5]); - /// assert_eq!(phase.next(), [0.75]); - /// assert_eq!(phase.next(), [0.0]); - /// assert_eq!(phase.next(), [0.25]); + /// assert_eq!(phase.next(), 0.0); + /// assert_eq!(phase.next(), 0.25); + /// assert_eq!(phase.next(), 0.5); + /// assert_eq!(phase.next(), 0.75); + /// assert_eq!(phase.next(), 0.0); + /// assert_eq!(phase.next(), 0.25); /// } /// ``` pub fn hz(self, hz: S) -> Hz where - S: Signal, + S: Signal, { Hz { hz: hz, rate: self } } @@ -1792,7 +1796,7 @@ impl Rate { impl Hz where - S: Signal, + S: Signal, { /// Construct a `Phase` iterator that, for every `hz` yielded by `self`, yields a phase that is /// stepped by `hz / self.rate.hz`. @@ -1880,11 +1884,11 @@ impl Step for ConstHz { impl Step for Hz where - S: Signal, + S: Signal, { #[inline] fn step(&mut self) -> f64 { - let hz = self.hz.next()[0]; + let hz = self.hz.next(); hz / self.rate.hz } } @@ -1964,10 +1968,10 @@ impl Noise { } impl Signal for Noise { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { - [self.next_sample()] + self.next_sample() } } @@ -2063,11 +2067,11 @@ impl Signal for NoiseSimplex where S: Step, { - type Frame = [f64; 1]; + type Frame = f64; #[inline] fn next(&mut self) -> Self::Frame { - [self.next_sample()] + self.next_sample() } } @@ -2195,14 +2199,14 @@ impl Signal for MulHz where S: Signal, ::Sample: Duplex, - M: Signal, + M: Signal, I: Interpolator, { type Frame = S::Frame; #[inline] fn next(&mut self) -> Self::Frame { - let mul = self.mul_per_frame.next()[0]; + let mul = self.mul_per_frame.next(); self.signal.set_playback_hz_scale(mul); self.signal.next() } @@ -2404,13 +2408,13 @@ where /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.1], [0.2], [0.3], [0.4]]; + /// let frames = [0.1, 0.2, 0.3, 0.4]; /// let signal = signal::from_iter(frames.iter().cloned()); - /// let ring_buffer = ring_buffer::Bounded::from([[0f32; 1]; 2]); + /// let ring_buffer = ring_buffer::Bounded::from([0f32; 2]); /// let mut buffered_signal = signal.buffered(ring_buffer); - /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![[0.1], [0.2]]); - /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![[0.3], [0.4]]); - /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![[0.0], [0.0]]); + /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![0.1, 0.2]); + /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![0.3, 0.4]); + /// assert_eq!(buffered_signal.next_frames().collect::>(), vec![0.0, 0.0]); /// } /// ``` pub fn next_frames(&mut self) -> BufferedFrames { diff --git a/dasp_signal/tests/interpolate.rs b/dasp_signal/tests/interpolate.rs index 67ca9605..065ad74d 100644 --- a/dasp_signal/tests/interpolate.rs +++ b/dasp_signal/tests/interpolate.rs @@ -6,46 +6,46 @@ use dasp_signal::{self as signal, interpolate::Converter, Signal}; #[test] fn test_floor_converter() { - let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]]; + let frames: [f64; 3] = [0.0, 1.0, 2.0]; let mut source = signal::from_iter(frames.iter().cloned()); let interp = Floor::new(source.next()); let mut conv = Converter::scale_playback_hz(source, interp, 0.5); - assert_eq!(conv.next(), [0.0]); - assert_eq!(conv.next(), [0.0]); - assert_eq!(conv.next(), [1.0]); - assert_eq!(conv.next(), [1.0]); + assert_eq!(conv.next(), 0.0); + assert_eq!(conv.next(), 0.0); + assert_eq!(conv.next(), 1.0); + assert_eq!(conv.next(), 1.0); // It may seem odd that we are emitting two values, but consider this: no matter what the next // value would be, Floor would always yield the same frame until we hit an interpolation_value // of 1.0 and had to advance the frame. We don't know what the future holds, so we should // continue yielding frames. - assert_eq!(conv.next(), [2.0]); - assert_eq!(conv.next(), [2.0]); + assert_eq!(conv.next(), 2.0); + assert_eq!(conv.next(), 2.0); } #[test] fn test_linear_converter() { - let frames: [[f64; 1]; 3] = [[0.0], [1.0], [2.0]]; + let frames: [f64; 3] = [0.0, 1.0, 2.0]; let mut source = signal::from_iter(frames.iter().cloned()); let a = source.next(); let b = source.next(); let interp = Linear::new(a, b); let mut conv = Converter::scale_playback_hz(source, interp, 0.5); - assert_eq!(conv.next(), [0.0]); - assert_eq!(conv.next(), [0.5]); - assert_eq!(conv.next(), [1.0]); - assert_eq!(conv.next(), [1.5]); - assert_eq!(conv.next(), [2.0]); + assert_eq!(conv.next(), 0.0); + assert_eq!(conv.next(), 0.5); + assert_eq!(conv.next(), 1.0); + assert_eq!(conv.next(), 1.5); + assert_eq!(conv.next(), 2.0); // There's nothing else here to interpolate toward, but we do want to ensure that we're // emitting the correct number of frames. - assert_eq!(conv.next(), [1.0]); + assert_eq!(conv.next(), 1.0); } #[test] fn test_scale_playback_rate() { // Scale the playback rate by `0.5` - let foo = [[0.0], [1.0], [0.0], [-1.0]]; + let foo = [0.0, 1.0, 0.0, -1.0]; let mut source = signal::from_iter(foo.iter().cloned()); let a = source.next(); let b = source.next(); @@ -53,23 +53,21 @@ fn test_scale_playback_rate() { let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); assert_eq!( &frames[..], - &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..] + &[0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5][..] ); } #[test] fn test_sinc() { - let foo = [[0.0f64], [1.0], [0.0], [-1.0]]; + let foo = [0f64, 1.0, 0.0, -1.0]; let source = signal::from_iter(foo.iter().cloned()); - let frames = ring_buffer::Fixed::from(vec![[0.0]; 50]); + let frames = ring_buffer::Fixed::from(vec![0.0; 50]); let interp = Sinc::new(frames); let resampled = source.from_hz_to_hz(interp, 44100.0, 11025.0); assert_eq!( - resampled - .until_exhausted() - .find(|sample| sample[0].is_nan()), + resampled.until_exhausted().find(|sample| sample.is_nan()), None ); } diff --git a/dasp_signal/tests/signal.rs b/dasp_signal/tests/signal.rs index c6a5eeb1..146789df 100644 --- a/dasp_signal/tests/signal.rs +++ b/dasp_signal/tests/signal.rs @@ -4,28 +4,28 @@ use dasp_signal::{self as signal, Signal}; #[test] fn test_equilibrium() { - let equilibrium: Vec<[i8; 1]> = signal::equilibrium().take(4).collect(); - assert_eq!(equilibrium, vec![[0], [0], [0], [0]]); + let equilibrium: Vec = signal::equilibrium().take(4).collect(); + assert_eq!(equilibrium, vec![0, 0, 0, 0]); } #[test] fn test_scale_amp() { - let foo = [[0.5], [0.8], [-0.4], [-0.2]]; + let foo = [0.5, 0.8, -0.4, -0.2]; let amp = 0.5; let amp_scaled: Vec<_> = signal::from_iter(foo.iter().cloned()) .scale_amp(amp) .take(4) .collect(); - assert_eq!(amp_scaled, vec![[0.25], [0.4], [-0.2], [-0.1]]); + assert_eq!(amp_scaled, vec![0.25, 0.4, -0.2, -0.1]); } #[test] fn test_offset_amp() { - let foo = [[0.5], [0.9], [-0.4], [-0.2]]; + let foo = [0.5, 0.9, -0.4, -0.2]; let amp = -0.5; let amp_offset: Vec<_> = signal::from_iter(foo.iter().cloned()) .offset_amp(amp) .take(4) .collect(); - assert_eq!(amp_offset, vec![[0.0], [0.4], [-0.9], [-0.7]]); + assert_eq!(amp_offset, vec![0.0, 0.4, -0.9, -0.7]); } diff --git a/dasp_signal/tests/window.rs b/dasp_signal/tests/window.rs index ad761430..b199fc98 100644 --- a/dasp_signal/tests/window.rs +++ b/dasp_signal/tests/window.rs @@ -6,27 +6,27 @@ use dasp_signal::window::{self, Windower}; #[cfg(feature = "window-hanning")] #[test] fn test_window_at_phase() { - let window = window::hanning::<[f64; 1]>(9); + let window = window::hanning::(9); let expected = [ 0.0, 0.1464, 0.5000, 0.8536, 1., 0.8536, 0.5000, 0.1464, 0., 0.1464, ]; for (r, e) in window.zip(&expected) { - println!("Expected: {}\t\tFound: {}", e, r[0]); - assert!((r[0] - e).abs() < 0.001); + println!("Expected: {}\t\tFound: {}", e, r); + assert!((r - e).abs() < 0.001); } } #[test] fn test_windower() { - let data = [[0.1f64], [0.1], [0.2], [0.2], [0.3], [0.3], [0.4], [0.4]]; + let data = [0.1f64, 0.1, 0.2, 0.2, 0.3, 0.3, 0.4, 0.4]; let expected = [ - [[0.1], [0.1]], - [[0.1], [0.2]], - [[0.2], [0.2]], - [[0.2], [0.3]], - [[0.3], [0.3]], - [[0.3], [0.4]], - [[0.4], [0.4]], + [0.1f64, 0.1], + [0.1, 0.2], + [0.2, 0.2], + [0.2, 0.3], + [0.3, 0.3], + [0.3, 0.4], + [0.4, 0.4], ]; let windower = Windower::rectangle(&data, 2, 1); for (chunk, expected_chunk) in windower.zip(&expected) { @@ -42,9 +42,9 @@ fn test_windower() { #[cfg(feature = "window-hanning")] #[test] fn test_window_size() { - let v = [[1f32; 1]; 16]; + let v = [1f32; 16]; let windows: Vec> = Windower::hanning(&v, 8, 4) - .map(|i| i.take(8).collect::>()) + .map(|i| i.take(8).collect::>()) .take(3) .collect(); assert_eq!(windows.len(), 3); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index db5190c8..82c5c7e6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["mitchmindtree "] edition = "2018" -[dependencies] +[dev-dependencies] anyhow = "1" cpal = { git = "https://github.com/rustaudio/cpal", branch = "master" } dasp = { version = "0.11", path = "../dasp", features = ["slice", "interpolate", "interpolate-sinc", "ring_buffer", "signal"] } diff --git a/examples/synth.rs b/examples/synth.rs index 96571892..11a16be8 100644 --- a/examples/synth.rs +++ b/examples/synth.rs @@ -34,7 +34,7 @@ where .chain(hz.clone().square().take(one_sec)) .chain(hz.clone().noise_simplex().take(one_sec)) .chain(signal::noise(0).take(one_sec)) - .map(|[s]| s.to_sample::() * 0.2); + .map(|s| s.to_sample::() * 0.2); // A channel for indicating when playback has completed. let (complete_tx, complete_rx) = mpsc::sync_channel(1); From 3c862f0f82a3a09a7affb0d3e87c08787ce608dc Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 20:08:58 +0200 Subject: [PATCH 28/29] Add an "all" feature that simplifies adding all features for each crate --- dasp/Cargo.toml | 1 + dasp/src/lib.rs | 6 +++--- dasp_envelope/Cargo.toml | 1 + dasp_interpolate/Cargo.toml | 1 + dasp_signal/Cargo.toml | 1 + dasp_slice/Cargo.toml | 1 + dasp_window/Cargo.toml | 1 + examples/Cargo.toml | 2 +- 8 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 9a4ae88b..ccd7d280 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -24,6 +24,7 @@ dasp_window = { version = "0.11", path = "../dasp_window", default-features = fa [features] default = ["std"] +all = ["std", "all-no-std"] all-no-std = [ "envelope", "envelope-peak", diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index 6075c07b..018041ef 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -1,5 +1,5 @@ -//! **dasp** is a suite of crates, providing the fundamentals for working with pulse-code -//! modulation digital signal processing. In other words, `dasp` provides a suite of low-level, +//! **dasp** is a suite of crates providing the fundamentals for working with pulse-code modulation +//! **digital audio signal processing**. In other words, **dasp** provides a suite of low-level, //! high-performance tools including types, traits and functions for working with digital audio //! signals. //! @@ -69,7 +69,7 @@ //! - The **window-rectangle** feature enables the //! [**Rectangle**](./window/struct.Rectangle.html) window implementation. //! -//! Enable all of the above features with the `--all-features` flag. +//! Enable all of the above features with the `--all-features` flag or with the **all** feature. //! //! ### no_std //! diff --git a/dasp_envelope/Cargo.toml b/dasp_envelope/Cargo.toml index 8d78a7b8..02444346 100644 --- a/dasp_envelope/Cargo.toml +++ b/dasp_envelope/Cargo.toml @@ -19,6 +19,7 @@ dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = fa [features] default = ["std"] +all = ["std", "all-no-std"] all-no-std = [ "peak", "rms", diff --git a/dasp_interpolate/Cargo.toml b/dasp_interpolate/Cargo.toml index 569ca5bf..47c82d7a 100644 --- a/dasp_interpolate/Cargo.toml +++ b/dasp_interpolate/Cargo.toml @@ -17,6 +17,7 @@ dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = fa [features] default = ["std"] +all = ["std", "all-no-std"] all-no-std = [ "floor", "linear", diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml index d5d04f99..0af0374c 100644 --- a/dasp_signal/Cargo.toml +++ b/dasp_signal/Cargo.toml @@ -27,6 +27,7 @@ dasp_window = { version = "0.11", path = "../dasp_window", default-features = fa [features] default = ["std"] +all = ["std", "all-no-std"] all-no-std = [ "boxed", "bus", diff --git a/dasp_slice/Cargo.toml b/dasp_slice/Cargo.toml index 4946dfb6..04933d80 100644 --- a/dasp_slice/Cargo.toml +++ b/dasp_slice/Cargo.toml @@ -16,6 +16,7 @@ dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = fals [features] default = ["std"] +all = ["std", "all-no-std"] all-no-std = [ "boxed", ] diff --git a/dasp_window/Cargo.toml b/dasp_window/Cargo.toml index 25b4a554..4467cd04 100644 --- a/dasp_window/Cargo.toml +++ b/dasp_window/Cargo.toml @@ -15,6 +15,7 @@ dasp_sample = { version = "0.11", path = "../dasp_sample", default-features = fa [features] default = ["std"] +all = ["std", "all-no-std"] all-no-std = [ "hanning", "rectangle", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 82c5c7e6..0f7dbdf0 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dev-dependencies] anyhow = "1" cpal = { git = "https://github.com/rustaudio/cpal", branch = "master" } -dasp = { version = "0.11", path = "../dasp", features = ["slice", "interpolate", "interpolate-sinc", "ring_buffer", "signal"] } +dasp = { version = "0.11", path = "../dasp", features = ["all"] } find_folder = "0.3" hound = "3" From 221b81038c528bf3fc364a8ab5cc4b2e52f7dbfc Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 29 May 2020 20:48:51 +0200 Subject: [PATCH 29/29] Final documentation cleanup before publishing --- README.md | 2 + dasp/src/lib.rs | 16 +++++--- dasp_frame/src/lib.rs | 2 +- dasp_sample/src/lib.rs | 13 ++---- dasp_signal/src/lib.rs | 90 ++++++++++++++++++++++++++---------------- 5 files changed, 74 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index ac4bddf2..caba0026 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ **Digital Audio Signal Processing in Rust.** +*Formerly the [`sample` crate](https://crates.io/crates/sample).* + A suite of crates providing the fundamentals for working with PCM (pulse-code modulation) DSP (digital signal processing). In other words, `dasp` provides a suite of low-level, high-performance tools including types, traits and functions diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index 018041ef..95112bb9 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -1,7 +1,7 @@ -//! **dasp** is a suite of crates providing the fundamentals for working with pulse-code modulation -//! **digital audio signal processing**. In other words, **dasp** provides a suite of low-level, -//! high-performance tools including types, traits and functions for working with digital audio -//! signals. +//! **dasp** (formerly known as ***sample***) is a suite of crates providing the fundamentals for +//! working with pulse-code modulation **digital audio signal processing**. In other words, +//! **dasp** provides a suite of low-level, high-performance tools including types, traits and +//! functions for working with digital audio signals. //! //! Each of the **dasp** crates are re-exported under their respective //! [modules](file:///home/mindtree/programming/rust/dasp/target/doc/dasp/index.html#modules). @@ -30,6 +30,12 @@ //! within this crate. You may pick and choose between the following features for additional //! functionality. //! +//! - The **all** feature enables all of the following features. +//! - The **std** feature enables the std library. This is enabled by default. +//! - The **all-no-std** feature enables all of the following features (without std). +//! +//! The following features map to each of the sub-crates and their respective features. +//! //! - The **envelope** feature enables the `dasp_envelope` crate via the //! [envelope](./envelope/index.html) module. //! - The **envelope-peak** feature enables peak envelope detection. @@ -69,7 +75,7 @@ //! - The **window-rectangle** feature enables the //! [**Rectangle**](./window/struct.Rectangle.html) window implementation. //! -//! Enable all of the above features with the `--all-features` flag or with the **all** feature. +//! You can also enable all of the above features with the `--all-features` flag. //! //! ### no_std //! diff --git a/dasp_frame/src/lib.rs b/dasp_frame/src/lib.rs index bab033ab..47bf8249 100644 --- a/dasp_frame/src/lib.rs +++ b/dasp_frame/src/lib.rs @@ -1,4 +1,4 @@ -//! Use the [**Frame** trait](./trait.Frame.html) to remain generic over the number of channels at +//! Use the [**Frame**](./trait.Frame.html) trait to remain generic over the number of channels at //! a single discrete moment in time. //! //! Implementations are provided for all fixed-size arrays up to 32 elements in length. diff --git a/dasp_sample/src/lib.rs b/dasp_sample/src/lib.rs index bb3ef8d2..ea25acd8 100644 --- a/dasp_sample/src/lib.rs +++ b/dasp_sample/src/lib.rs @@ -1,13 +1,8 @@ -//! A crate of fundamentals for audio PCM DSP. +//! Use the [**Sample**](./trait.Sample.html) trait to remain generic over sample types, easily +//! access sample type conversions, apply basic audio operations and more. //! -//! - Use the [**Sample** trait](./trait.Sample.html) to remain generic across bit-depth. -//! - Use the [**Frame** trait](./frame/trait.Frame.html) to remain generic over channel layout. -//! - Use the [**Signal** trait](./signal/trait.Signal.html) for working with **Iterators** that yield **Frames**. -//! - Use the [**slice** module](./slice/index.html) for working with slices of **Samples** and **Frames**. -//! - See the [**conv** module](./conv/index.html) for fast conversions between slices, frames and samples. -//! - See the [**types** module](./types/index.html) for provided custom sample types. -//! - See the [**interpolate** module](./interpolate/index.html) for sample rate conversion and scaling. -//! - See the [**ring_buffer** module](./ring_buffer/index.html) for fast FIFO queue options. +//! The **Sample** trait is the core abstraction throughout dasp on which most other abstractions +//! are based. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index 63ce6fe6..da36d9f2 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -73,10 +73,16 @@ type Rc = alloc::rc::Rc; #[cfg(feature = "std")] type Rc = std::rc::Rc; -/// Types that yield `Frame`s as a multi-channel PCM signal. +/// Types that yield `Frame`s of a one-or-more-channel PCM signal. /// /// For example, `Signal` allows us to add two signals, modulate a signal's amplitude by another -/// signal, scale a signals amplitude and much more. +/// signal, scale a signal's amplitude and much more. +/// +/// The **Signal** trait is inspired by the `Iterator` trait but is different in the sense that it +/// will always yield frames and will never return `None`. That said, implementors of `Signal` may +/// optionally indicate exhaustian via the `is_exhausted` method. This allows for converting +/// exhaustive signals back to iterators that are well behaved. Calling **next** on an exhausted +/// signal should always yield `Self::Frame::EQUILIBRIUM`. pub trait Signal { /// The `Frame` type returned by the `Signal`. type Frame: Frame; @@ -85,15 +91,31 @@ pub trait Signal { /// /// # Example /// + /// An example of a mono (single-channel) signal. + /// + /// ```rust + /// use dasp_signal::{self as signal, Signal}; + /// + /// fn main() { + /// let frames = [0.2, -0.6, 0.4]; + /// let mut signal = signal::from_iter(frames.iter().cloned()); + /// assert_eq!(signal.next(), 0.2); + /// assert_eq!(signal.next(), -0.6); + /// assert_eq!(signal.next(), 0.4); + /// } + /// ``` + /// + /// An example of a stereo (dual-channel) signal. + /// /// ```rust /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.2], [-0.6], [0.4]]; + /// let frames = [[0.2, 0.2], [-0.6, -0.6], [0.4, 0.4]]; /// let mut signal = signal::from_iter(frames.iter().cloned()); - /// assert_eq!(signal.next(), [0.2]); - /// assert_eq!(signal.next(), [-0.6]); - /// assert_eq!(signal.next(), [0.4]); + /// assert_eq!(signal.next(), [0.2, 0.2]); + /// assert_eq!(signal.next(), [-0.6, -0.6]); + /// assert_eq!(signal.next(), [0.4, 0.4]); /// } /// ``` fn next(&mut self) -> Self::Frame; @@ -125,15 +147,15 @@ pub trait Signal { /// assert_eq!(sine_signal.is_exhausted(), false); /// /// // Signals over iterators return `true` when the inner iterator is exhausted. - /// let frames = [[0.2], [-0.6], [0.4]]; + /// let frames = [0.2, -0.6, 0.4]; /// let mut iter_signal = signal::from_iter(frames.iter().cloned()); /// assert_eq!(iter_signal.is_exhausted(), false); /// iter_signal.by_ref().take(3).count(); /// assert_eq!(iter_signal.is_exhausted(), true); /// /// // Adaptors return `true` when the first signal becomes exhausted. - /// let a = [[1], [2]]; - /// let b = [[1], [2], [3], [4]]; + /// let a = [1, 2]; + /// let b = [1, 2, 3, 4]; /// let a_signal = signal::from_iter(a.iter().cloned()); /// let b_signal = signal::from_iter(b.iter().cloned()); /// let mut added = a_signal.add_amp(b_signal); @@ -155,8 +177,8 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = signal::gen(|| [0.5]); - /// let mut mapper = frames.map(|f| [f[0], 0.25]); + /// let frames = signal::gen(|| 0.5); + /// let mut mapper = frames.map(|f| [f, 0.25]); /// assert_eq!(mapper.next(), [0.5, 0.25]); /// assert_eq!(mapper.next(), [0.5, 0.25]); /// assert_eq!(mapper.next(), [0.5, 0.25]); @@ -202,9 +224,9 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = signal::gen(|| [0.5]); - /// let more_frames = signal::gen(|| [0.25]); - /// let mut mapper = frames.zip_map(more_frames, |f, o| [f[0], o[0]]); + /// let frames = signal::gen(|| 0.5); + /// let more_frames = signal::gen(|| 0.25); + /// let mut mapper = frames.zip_map(more_frames, |f, o| [f, o]); /// assert_eq!(mapper.next(), [0.5, 0.25]); /// assert_eq!(mapper.next(), [0.5, 0.25]); /// assert_eq!(mapper.next(), [0.5, 0.25]); @@ -234,12 +256,12 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let a = [[0.2], [-0.6], [0.4]]; - /// let b = [[0.2], [0.1], [-0.8]]; + /// let a = [0.2, -0.6, 0.4]; + /// let b = [0.2, 0.1, -0.8]; /// let a_signal = signal::from_iter(a.iter().cloned()); /// let b_signal = signal::from_iter(b.iter().cloned()); /// let added: Vec<_> = a_signal.add_amp(b_signal).take(3).collect(); - /// assert_eq!(added, vec![[0.4], [-0.5], [-0.4]]); + /// assert_eq!(added, vec![0.4, -0.5, -0.4]); /// } /// ``` #[inline] @@ -264,12 +286,12 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let a = [[0.25], [-0.8], [-0.5]]; - /// let b = [[0.2], [0.5], [0.8]]; + /// let a = [0.25, -0.8, -0.5]; + /// let b = [0.2, 0.5, 0.8]; /// let a_signal = signal::from_iter(a.iter().cloned()); /// let b_signal = signal::from_iter(b.iter().cloned()); /// let added: Vec<_> = a_signal.mul_amp(b_signal).take(3).collect(); - /// assert_eq!(added, vec![[0.05], [-0.4], [-0.4]]); + /// assert_eq!(added, vec![0.05, -0.4, -0.4]); /// } /// ``` #[inline] @@ -323,10 +345,10 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.2], [-0.5], [-0.4], [0.3]]; + /// let frames = [0.2, -0.5, -0.4, 0.3]; /// let signal = signal::from_iter(frames.iter().cloned()); /// let scaled: Vec<_> = signal.scale_amp(2.0).take(4).collect(); - /// assert_eq!(scaled, vec![[0.4], [-1.0], [-0.8], [0.6]]); + /// assert_eq!(scaled, vec![0.4, -1.0, -0.8, 0.6]); /// } /// ``` #[inline] @@ -444,13 +466,13 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; + /// let foo = [0.0, 1.0, 0.0, -1.0]; /// let mut source = signal::from_iter(foo.iter().cloned()); /// let a = source.next(); /// let b = source.next(); /// let interp = Linear::new(a, b); /// let frames: Vec<_> = source.from_hz_to_hz(interp, 1.0, 2.0).take(8).collect(); - /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); + /// assert_eq!(&frames[..], &[0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5][..]); /// } /// ``` fn from_hz_to_hz(self, interpolator: I, source_hz: f64, target_hz: f64) -> Converter @@ -470,13 +492,13 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; + /// let foo = [0.0, 1.0, 0.0, -1.0]; /// let mut source = signal::from_iter(foo.iter().cloned()); /// let a = source.next(); /// let b = source.next(); /// let interp = Linear::new(a, b); /// let frames: Vec<_> = source.scale_hz(interp, 0.5).take(8).collect(); - /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); + /// assert_eq!(&frames[..], &[0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5][..]); /// } /// ``` fn scale_hz(self, interpolator: I, multi: f64) -> Converter @@ -498,10 +520,10 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.2], [0.4]]; + /// let frames = [0.2, 0.4]; /// let signal = signal::from_iter(frames.iter().cloned()); /// let delayed: Vec<_> = signal.delay(2).take(4).collect(); - /// assert_eq!(delayed, vec![[0.0], [0.0], [0.2], [0.4]]); + /// assert_eq!(delayed, vec![0.0, 0.0, 0.2, 0.4]); /// } /// ``` fn delay(self, n_frames: usize) -> Delay @@ -672,10 +694,10 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[0.1], [0.2], [0.3], [0.4]]; + /// let frames = [0.1, 0.2, 0.3, 0.4]; /// let mut signal = signal::from_iter(frames.iter().cloned()).take(2); - /// assert_eq!(signal.next(), Some([0.1])); - /// assert_eq!(signal.next(), Some([0.2])); + /// assert_eq!(signal.next(), Some(0.1)); + /// assert_eq!(signal.next(), Some(0.2)); /// assert_eq!(signal.next(), None); /// } /// ``` @@ -695,7 +717,7 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { - /// let frames = [[1], [2]]; + /// let frames = [1, 2]; /// let signal = signal::from_iter(frames.iter().cloned()); /// assert_eq!(signal.until_exhausted().count(), 2); /// } @@ -802,9 +824,9 @@ pub trait Signal { /// use dasp_signal::{self as signal, Signal}; /// /// fn main() { -/// let frames = vec![[0], [1], [2], [3]]; +/// let frames = vec![0, 1, 2, 3]; /// let offset_frames = signal::lift(frames, |signal| signal.offset_amp(2)); -/// assert_eq!(offset_frames.collect::>(), vec![[2], [3], [4], [5]]); +/// assert_eq!(offset_frames.collect::>(), vec![2, 3, 4, 5]); /// } /// ``` pub fn lift(iter: I, f: F) -> UntilExhausted