From b790c20cf1a08db4b022c9d04a5415451e39bf31 Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Wed, 21 Dec 2022 23:03:49 +0100 Subject: [PATCH 1/8] initial work on SampleRate type --- Cargo.toml | 1 + src/lib.rs | 3 +++ src/sampling_rate.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++ src/waveform.rs | 39 +++++++++++++++------------------ 4 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 src/sampling_rate.rs diff --git a/Cargo.toml b/Cargo.toml index 9576235..f718a6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ categories = [ [dependencies] libm = { version = "0.2", optional = true } num-traits = { version = "0.2", default-features = false } +thiserror = "1.0.38" [dev-dependencies] plotters = "^0.3.1" diff --git a/src/lib.rs b/src/lib.rs index 65209a5..a7f9ee4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,10 +128,13 @@ extern crate alloc; pub mod periodic_functions; mod macros; +mod sampling_rate; mod waveform; use alloc::boxed::Box; +pub use sampling_rate::SamplingRate; +pub use sampling_rate::SamplingRateValueError; pub use waveform::SampleType; pub use waveform::Waveform; diff --git a/src/sampling_rate.rs b/src/sampling_rate.rs new file mode 100644 index 0000000..d9911ea --- /dev/null +++ b/src/sampling_rate.rs @@ -0,0 +1,52 @@ +use core::convert::TryFrom; +use thiserror::Error; + +#[derive(Debug, Clone, Copy)] +/// Defines sampling rate value - aka non-zero, positive, finite `f64`. +pub struct SamplingRate(pub(crate) f64); + +impl SamplingRate { + /// Initializes new `SamplingRate` value. + /// + /// # Examples + /// + /// ``` + /// let s = wavegen::SamplingRate::new(44100.0); + /// assert!(s.is_ok()); + /// ```` + pub fn new(value: impl Into) -> Result { + let value = value.into(); + if !Self::is_sane(value) { + return Err(SamplingRateValueError(value)); + } + + Ok(SamplingRate(value)) + } + + fn is_sane(x: f64) -> bool { + x.is_normal() && x.is_sign_positive() + } +} + +#[derive(Error, Debug, Clone, Copy)] +#[error("Invalid SamplingRate value: `{0}`. SamplingRate has to be positive, non-zero and finite.")] +/// Error raised when cosntructing [`SamplingRate`] of invalid value. +pub struct SamplingRateValueError(f64); + +macro_rules! impl_tryfrom { + ($($target:ty),+) => { + $( + impl TryFrom<$target> for SamplingRate { + type Error = SamplingRateValueError; + + fn try_from(value: $target) -> Result { + Self::new(value) + } + } + )+ + }; +} + +impl_tryfrom! { + f64, f32, u32, i32, u16, i16, u8, i8 +} diff --git a/src/waveform.rs b/src/waveform.rs index 98fd32f..ceec93b 100644 --- a/src/waveform.rs +++ b/src/waveform.rs @@ -1,4 +1,4 @@ -use crate::PeriodicFunction; +use crate::{PeriodicFunction, SamplingRate}; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use num_traits::{Bounded, NumCast}; @@ -10,7 +10,7 @@ impl SampleType for T where T: NumCast + Bounded {} /// Struct representing a waveform, consisting of output numeric type, sampling rate and a vector of [PeriodicFunction]s. pub struct Waveform { - sample_rate: f64, + sample_rate: SamplingRate, components: Vec, _phantom: PhantomData, } @@ -31,10 +31,7 @@ impl Waveform { /// /// assert!(wf.iter().take(100).all(|y| y == 0.0)); /// ``` - pub fn new(sample_rate: impl Into) -> Self { - let sample_rate = sample_rate.into(); - Self::assert_sane(sample_rate); - + pub fn new(sample_rate: SamplingRate) -> Self { Waveform { sample_rate, components: vec![], @@ -55,10 +52,7 @@ impl Waveform { /// /// let wf = Waveform::::with_components(100.0, vec![sine!(1), dc_bias!(-50)]); /// ``` - pub fn with_components(sample_rate: impl Into, components: Vec) -> Self { - let sample_rate = sample_rate.into(); - Self::assert_sane(sample_rate); - + pub fn with_components(sample_rate: SamplingRate, components: Vec) -> Self { Waveform { sample_rate, components, @@ -94,8 +88,8 @@ impl Waveform { /// /// assert_eq!(42.0, wf.get_sample_rate()); /// ``` - pub fn get_sample_rate(&self) -> f64 { - self.sample_rate + pub fn get_sample_rate(&self) -> &SamplingRate { + &self.sample_rate } /// Returns number of components this [Waveform] consists of. @@ -129,12 +123,6 @@ impl Waveform { time: 0.0, } } - - #[inline(always)] - fn assert_sane(x: f64) { - assert!(x.is_normal()); - assert!(x.is_sign_positive()); - } } impl<'a, T: SampleType> IntoIterator for &'a Waveform { @@ -172,7 +160,7 @@ impl<'a, T: SampleType> WaveformIterator<'a, T> { } fn increment_time(&mut self, n: usize) { - let new_time = self.time + (n as f64 * (1.0 / self.inner.sample_rate)); + let new_time = self.time + (n as f64 * (1.0 / self.inner.sample_rate.0)); if new_time.is_finite() { self.time = new_time; } else { @@ -334,7 +322,7 @@ mod tests { let wf = Waveform::::new(44100.0); assert_eq!((usize::MAX, None), wf.iter().size_hint()); }; - ($($component:expr,)*) => { + ($($component:expr),*) => { let mut wf = Waveform::::new(44100.0); $( wf.add_component($component); @@ -346,12 +334,13 @@ mod tests { #[test] fn test_size_hint() { test_size_hint!(); - test_size_hint!(sine!(50),); - test_size_hint!(sine!(1), sawtooth!(2), square!(3), dc_bias!(4),); + test_size_hint!(sine!(50)); + test_size_hint!(sine!(1), sawtooth!(2), square!(3), dc_bias!(4)); } #[test] #[allow(clippy::iter_nth_zero)] + #[allow(clippy::unwrap_used)] fn nth_and_next_give_same_results() { let wf = Waveform::::with_components(44100.0, vec![sine!(3000, i32::MAX)]); let mut i1 = wf.iter(); @@ -361,4 +350,10 @@ mod tests { assert_eq!(i1.next().unwrap(), i2.nth(0).unwrap()); } } + + #[test] + fn waveform_is_send_and_sync() { + fn test() {} + test::>(); + } } From f03ce2c00931373a477c49a23e72cc0413708a1c Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Wed, 21 Dec 2022 23:15:40 +0100 Subject: [PATCH 2/8] update macros --- src/macros.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 57245d3..48e50a7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -23,12 +23,14 @@ #[macro_export] macro_rules! wf { ($sample_type:ty, $sample_rate:expr) => { - $crate::Waveform::<$sample_type>::new($sample_rate) + $crate::Waveform::<$sample_type>::new($crate::SamplingRate::new($sample_rate).expect("Sample rate has to be non-zero, finite, positive value.")) }; ($sample_type:ty, $sample_rate:expr, $($comp:expr),+) => { { extern crate alloc; - let __wf = $crate::Waveform::<$sample_type>::with_components($sample_rate, alloc::vec![$($comp,)+]); + let __wf = $crate::Waveform::<$sample_type>::with_components( + $crate::SamplingRate::new($sample_rate).expect("Sample rate has to be non-zero, finite, positive value."), + alloc::vec![$($comp,)+]); __wf } From 766b58b752316fb14df3a11366ea4673bfecd055 Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Thu, 22 Dec 2022 16:10:13 +0100 Subject: [PATCH 3/8] Revert "update macros" This reverts commit f03ce2c00931373a477c49a23e72cc0413708a1c. --- src/macros.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 48e50a7..57245d3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -23,14 +23,12 @@ #[macro_export] macro_rules! wf { ($sample_type:ty, $sample_rate:expr) => { - $crate::Waveform::<$sample_type>::new($crate::SamplingRate::new($sample_rate).expect("Sample rate has to be non-zero, finite, positive value.")) + $crate::Waveform::<$sample_type>::new($sample_rate) }; ($sample_type:ty, $sample_rate:expr, $($comp:expr),+) => { { extern crate alloc; - let __wf = $crate::Waveform::<$sample_type>::with_components( - $crate::SamplingRate::new($sample_rate).expect("Sample rate has to be non-zero, finite, positive value."), - alloc::vec![$($comp,)+]); + let __wf = $crate::Waveform::<$sample_type>::with_components($sample_rate, alloc::vec![$($comp,)+]); __wf } From 0b210b5849f1d5d1da764f8573f00483a0fe1673 Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Thu, 22 Dec 2022 23:18:35 +0100 Subject: [PATCH 4/8] finish refactor --- benches/waveform_benchmark.rs | 3 +- examples/plot.rs | 14 ++--- examples/wave.rs | 2 +- src/errors.rs | 6 ++ src/lib.rs | 19 +++---- src/macros.rs | 12 ++-- src/sampling_rate.rs | 52 ----------------- src/waveform.rs | 104 +++++++++++++++++----------------- 8 files changed, 83 insertions(+), 129 deletions(-) create mode 100644 src/errors.rs delete mode 100644 src/sampling_rate.rs diff --git a/benches/waveform_benchmark.rs b/benches/waveform_benchmark.rs index a977814..b94cfe6 100644 --- a/benches/waveform_benchmark.rs +++ b/benches/waveform_benchmark.rs @@ -5,7 +5,8 @@ fn sample_waveform(n: usize) -> Vec { let wf = Waveform::with_components( 44100.0, vec![sine!(2048), sawtooth!(1024), square!(512), dc_bias!(0.1)], - ); + ) + .unwrap(); wf.iter().take(n).collect::>() } diff --git a/examples/plot.rs b/examples/plot.rs index 4f44088..d534cb7 100644 --- a/examples/plot.rs +++ b/examples/plot.rs @@ -9,21 +9,21 @@ fn main() -> Result<(), Box> { sample_rate, "sine.png", "Sine", - wf!(f32, sample_rate, sine!(1)), + wf!(f32, sample_rate, sine!(1))?, )?; draw( sample_rate, "sine_double.png", "Sines", - wf!(f32, sample_rate, sine!(1.0), sine!(1.0, 1.0, 0.25)), + wf!(f32, sample_rate, sine!(1.0), sine!(1.0, 1.0, 0.25))?, )?; draw( sample_rate, "sawtooth.png", "Sawtooth", - wf!(f32, sample_rate, sawtooth!(2, 1, 0.0)), + wf!(f32, sample_rate, sawtooth!(2, 1, 0.0))?, )?; draw( @@ -35,14 +35,14 @@ fn main() -> Result<(), Box> { sample_rate, sawtooth!(2, 1, 0.0), sine!(frequency: 50, amplitude: 0.1) - ), + )?, )?; draw( sample_rate, "square.png", "Square", - wf!(f32, sample_rate, square!(2)), + wf!(f32, sample_rate, square!(2))?, )?; draw( @@ -55,7 +55,7 @@ fn main() -> Result<(), Box> { sine!(10, 0.3), sawtooth!(2, 0.3), square!(3, 0.3) - ), + )?, )?; draw( @@ -67,7 +67,7 @@ fn main() -> Result<(), Box> { sample_rate, sine!(frequency: 300), sine!(frequency: 50, amplitude: 0.3) - ), + )?, )?; Ok(()) diff --git a/examples/wave.rs b/examples/wave.rs index 6783172..e5111d4 100644 --- a/examples/wave.rs +++ b/examples/wave.rs @@ -8,7 +8,7 @@ const WAVE_TIME_S: f32 = 1.0; // audio length in seconds fn main() { // Define waveform // 500 Hz sine spanned from i16::MIN to i16::MAX - let wf = wf!(i16, SAMPLE_RATE, sine!(500, i16::MAX)); + let wf = wf!(i16, SAMPLE_RATE, sine!(500, i16::MAX)).unwrap(); // WAVE file specification let spec = hound::WavSpec { diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..2f71d0f --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,6 @@ +use thiserror::Error; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Invalid SamplingRate value: `{0}`. SamplingRate has to be positive, non-zero and finite.")] +/// Error raised when cosntructing [`SamplingRate`] of invalid value. +pub struct InvalidSampleRate(pub(crate) f64); diff --git a/src/lib.rs b/src/lib.rs index 6849b9b..0fdfc35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! //! // Define a Waveform with 200Hz sampling rate and three function components, //! // choosing f32 as the output type: -//! let waveform = wf!(f32, 200, sine!(50, 10), sawtooth!(20), dc_bias!(-5)); +//! let waveform = wf!(f32, 200, sine!(50, 10), sawtooth!(20), dc_bias!(-5)).unwrap(); //! //! // Use Waveform as an infinite iterator: //! let two_seconds_of_samples: Vec = waveform.iter().take(400).collect(); @@ -43,7 +43,7 @@ //! ``` //! use wavegen::{wf, periodic_functions::custom}; //! -//! let waveform = wf!(f64, 100.0, custom(|x| x % 2.0)); +//! let waveform = wf!(f64, 100.0, custom(|x| x % 2.0)).unwrap(); //! ``` //! //! # Overflows @@ -56,7 +56,7 @@ //! ``` //! use wavegen::{Waveform, dc_bias}; //! -//! let wf = Waveform::::with_components(100.0, vec![dc_bias![f64::MAX], dc_bias![f64::MAX]]); +//! let wf = Waveform::::with_components(100.0, vec![dc_bias![f64::MAX], dc_bias![f64::MAX]]).unwrap(); //! let sample = wf.iter().take(1).collect::>()[0]; //! //! assert_eq!(sample, f64::INFINITY); @@ -65,7 +65,7 @@ //! ``` //! use wavegen::{Waveform, dc_bias}; //! -//! let wf = Waveform::::with_components(100.0, vec![dc_bias![f64::MAX], dc_bias![f64::MAX]]); +//! let wf = Waveform::::with_components(100.0, vec![dc_bias![f64::MAX], dc_bias![f64::MAX]]).unwrap(); //! let sample = wf.iter().take(1).collect::>()[0]; //! //! assert_eq!(sample, i32::MAX); @@ -81,7 +81,7 @@ //! ``` //! use wavegen::{Waveform, dc_bias}; //! -//! let mut wf = Waveform::::new(100.0); +//! let mut wf = Waveform::::new(100.0).unwrap(); //! wf.add_component(dc_bias!(f64::NAN)); //! //! assert_eq!(None, wf.iter().next()) @@ -91,7 +91,7 @@ //! ``` //! use wavegen::{Waveform, dc_bias}; //! -//! let mut wf = Waveform::::new(100.0); +//! let mut wf = Waveform::::new(100.0).unwrap(); //! wf.add_component(dc_bias!(f64::NAN)); //! //! assert!(wf.iter().next().unwrap().is_nan()) @@ -109,7 +109,7 @@ //! use wavegen::{Waveform, sine}; //! //! // 100 Hz sampling of 80 Hz sine... will not yield realistic results. -//! let wf = Waveform::::with_components(100.0, vec![sine!(80)]); +//! let wf = Waveform::::with_components(100.0, vec![sine!(80)]).unwrap(); //! ``` //! //! As it is often a case, it is you, the programmer, who's left in charge of making sure the input data makes sense. @@ -129,14 +129,13 @@ extern crate alloc; pub mod periodic_functions; +mod errors; mod macros; -mod sampling_rate; mod waveform; use alloc::boxed::Box; -pub use sampling_rate::SamplingRate; -pub use sampling_rate::SamplingRateValueError; +pub use errors::InvalidSampleRate; pub use waveform::SampleType; pub use waveform::Waveform; diff --git a/src/macros.rs b/src/macros.rs index 57245d3..b78314a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -9,13 +9,13 @@ /// ``` /// use wavegen::{wf, sine, square}; /// -/// let empty_waveform = wf!(f32, 16000); +/// let empty_waveform = wf!(f32, 16000).unwrap(); /// assert_eq!(0, empty_waveform.get_components_len()); /// -/// let sine_waveform = wf!(f64, 44100, sine!(50)); +/// let sine_waveform = wf!(f64, 44100, sine!(50)).unwrap(); /// assert_eq!(1, sine_waveform.get_components_len()); /// -/// let some_other_waveform = wf!(i64, 22000, sine!(100), square!(200)); +/// let some_other_waveform = wf!(i64, 22000, sine!(100), square!(200)).unwrap(); /// assert_eq!(2, some_other_waveform.get_components_len()); /// ``` /// @@ -180,18 +180,18 @@ mod tests { #[test] fn empty_waveform_has_zero_components() { - let wf = wf!(f64, 44100); + let wf = wf!(f64, 44100).unwrap(); assert_eq!(0, wf.get_components_len()); } #[test] fn wavefrom_with_one_component() { - let wf = wf!(f64, 44100, sine!(500)); + let wf = wf!(f64, 44100, sine!(500)).unwrap(); assert_eq!(1, wf.get_components_len()); } #[test] fn wavefrom_with_three_components() { - let wf = wf!(f64, 44100, sine!(500), square!(1000), sawtooth!(1500)); + let wf = wf!(f64, 44100, sine!(500), square!(1000), sawtooth!(1500)).unwrap(); assert_eq!(3, wf.get_components_len()); } diff --git a/src/sampling_rate.rs b/src/sampling_rate.rs deleted file mode 100644 index d9911ea..0000000 --- a/src/sampling_rate.rs +++ /dev/null @@ -1,52 +0,0 @@ -use core::convert::TryFrom; -use thiserror::Error; - -#[derive(Debug, Clone, Copy)] -/// Defines sampling rate value - aka non-zero, positive, finite `f64`. -pub struct SamplingRate(pub(crate) f64); - -impl SamplingRate { - /// Initializes new `SamplingRate` value. - /// - /// # Examples - /// - /// ``` - /// let s = wavegen::SamplingRate::new(44100.0); - /// assert!(s.is_ok()); - /// ```` - pub fn new(value: impl Into) -> Result { - let value = value.into(); - if !Self::is_sane(value) { - return Err(SamplingRateValueError(value)); - } - - Ok(SamplingRate(value)) - } - - fn is_sane(x: f64) -> bool { - x.is_normal() && x.is_sign_positive() - } -} - -#[derive(Error, Debug, Clone, Copy)] -#[error("Invalid SamplingRate value: `{0}`. SamplingRate has to be positive, non-zero and finite.")] -/// Error raised when cosntructing [`SamplingRate`] of invalid value. -pub struct SamplingRateValueError(f64); - -macro_rules! impl_tryfrom { - ($($target:ty),+) => { - $( - impl TryFrom<$target> for SamplingRate { - type Error = SamplingRateValueError; - - fn try_from(value: $target) -> Result { - Self::new(value) - } - } - )+ - }; -} - -impl_tryfrom! { - f64, f32, u32, i32, u16, i16, u8, i8 -} diff --git a/src/waveform.rs b/src/waveform.rs index 8072f17..515935e 100644 --- a/src/waveform.rs +++ b/src/waveform.rs @@ -1,14 +1,14 @@ -use crate::PeriodicFunction; +use crate::{InvalidSampleRate, PeriodicFunction}; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; use num_traits::{Bounded, NumCast}; -/// Helper trait defining all the types that can be used as [Waveform]'s sample type. +/// Helper trait defining all the types that can be used as [`Waveform`]'s sample type. pub trait SampleType: NumCast + Bounded {} impl SampleType for T where T: NumCast + Bounded {} -/// Struct representing a waveform, consisting of output numeric type, sampling rate and a vector of [PeriodicFunction]s. +/// Struct representing a waveform, consisting of output numeric type, sampling rate and a vector of [`PeriodicFunction`]s. pub struct Waveform { sample_rate: f64, components: Vec, @@ -16,64 +16,67 @@ pub struct Waveform { } impl Waveform { - /// Initializes new empty [Waveform] + /// Initializes new empty [`Waveform`]. /// - /// # Panics - /// - /// This method will panic if `sample_rate` is not a finite, positive, non-zero number. + /// Will return [`InvalidSampleRate`] error, if the `sample_rate` value is not a finite, positive, non-zero number. /// /// # Examples /// /// ``` /// use wavegen::Waveform; /// - /// let wf = Waveform::::new(500.0); + /// let wf = Waveform::::new(500.0).unwrap(); /// /// assert!(wf.iter().take(100).all(|y| y == 0.0)); /// ``` - pub fn new(sample_rate: impl Into) -> Self { + pub fn new(sample_rate: impl Into) -> Result { let sample_rate = sample_rate.into(); - Self::assert_sane(sample_rate); + if !Self::sane_sample_rate(sample_rate) { + return Err(InvalidSampleRate(sample_rate)); + } - Waveform { + Ok(Waveform { sample_rate, components: vec![], _phantom: PhantomData, - } + }) } - /// Initializes new [Waveform] with predefined components + /// Initializes new [`Waveform`] with predefined components. /// - /// # Panics - /// - /// This method will panic if `sample_rate` is not a finite, positive, non-zero number. + /// Will return [`InvalidSampleRate`] error, if the `sample_rate` value is not a finite, positive, non-zero number. /// /// # Examples /// /// ``` /// use wavegen::{Waveform, sine, dc_bias}; /// - /// let wf = Waveform::::with_components(100.0, vec![sine!(1), dc_bias!(-50)]); + /// let wf = Waveform::::with_components(100.0, vec![sine!(1), dc_bias!(-50)]).unwrap(); /// ``` - pub fn with_components(sample_rate: impl Into, components: Vec) -> Self { + pub fn with_components( + sample_rate: impl Into, + components: Vec, + ) -> Result { let sample_rate = sample_rate.into(); - Self::assert_sane(sample_rate); + if !Self::sane_sample_rate(sample_rate) { + return Err(InvalidSampleRate(sample_rate)); + } - Waveform { + Ok(Waveform { sample_rate, components, _phantom: PhantomData, - } + }) } - /// Ads a new component to existing [Waveform]. + /// Ads a new component to existing [`Waveform``]. /// /// # Examples /// /// ``` /// use wavegen::{Waveform, sine, dc_bias}; /// - /// let mut wf = Waveform::::new(100.0); + /// let mut wf = Waveform::::new(100.0).unwrap(); /// wf.add_component(sine!(10)); /// wf.add_component(dc_bias!(5)); /// @@ -83,14 +86,14 @@ impl Waveform { self.components.push(component); } - /// Getter for sample rate of a [Waveform]. + /// Getter for sample rate of a [`Waveform`]. /// /// # Examples /// /// ``` /// use wavegen::Waveform; /// - /// let wf = Waveform::::new(42.0); + /// let wf = Waveform::::new(42.0).unwrap(); /// /// assert_eq!(42.0, wf.get_sample_rate()); /// ``` @@ -98,14 +101,14 @@ impl Waveform { self.sample_rate } - /// Returns number of components this [Waveform] consists of. + /// Returns number of components this [`Waveform`] consists of. /// /// # Examples /// /// ``` /// use wavegen::{Waveform, sine, dc_bias}; /// - /// let wf = Waveform::::with_components(42.0, vec![sine!(1), dc_bias!(5)]); + /// let wf = Waveform::::with_components(42.0, vec![sine!(1), dc_bias!(5)]).unwrap(); /// /// assert_eq!(2, wf.get_components_len()); /// ``` @@ -113,14 +116,14 @@ impl Waveform { self.components.len() } - /// Returns an iterator over this [Waveform] samples. + /// Returns an iterator over this [`Waveform`] samples. /// /// # Examples /// /// ``` /// use wavegen::{Waveform, sine}; /// - /// let wf = Waveform::::with_components(42.0, vec![sine!(1)]); + /// let wf = Waveform::::with_components(42.0, vec![sine!(1)]).unwrap(); /// let samples = wf.iter().take(100).collect::>(); /// ``` pub fn iter(&self) -> WaveformIterator { @@ -131,9 +134,8 @@ impl Waveform { } #[inline(always)] - fn assert_sane(x: f64) { - assert!(x.is_normal()); - assert!(x.is_sign_positive()); + fn sane_sample_rate(x: f64) -> bool { + x.is_normal() && x.is_sign_positive() } } @@ -208,19 +210,19 @@ impl<'a, T: SampleType> Iterator for WaveformIterator<'a, T> { #[cfg(test)] mod tests { - use alloc::{vec, vec::Vec}; - - use float_cmp::approx_eq; - use paste::paste; + #![allow(clippy::unwrap_used)] use super::Waveform; use crate::{dc_bias, sawtooth, sine, square}; + use alloc::{vec, vec::Vec}; + use float_cmp::approx_eq; + use paste::paste; const EPS: f32 = 1e-3; #[test] fn sine_waveform_has_default_amplitude_of_one() { - let wf = Waveform::::with_components(100.0, vec![sine!(1)]); + let wf = Waveform::::with_components(100.0, vec![sine!(1)]).unwrap(); let samples = wf.iter().take(100).collect::>(); @@ -230,7 +232,7 @@ mod tests { #[test] fn sine_waveform_as_integers_has_amplitude_of_one() { - let wf = Waveform::::with_components(100.0, vec![sine!(1)]); + let wf = Waveform::::with_components(100.0, vec![sine!(1)]).unwrap(); let samples = wf.iter().take(100).collect::>(); @@ -240,7 +242,7 @@ mod tests { #[test] fn sine_waveform_with_bias_has_correct_amplitude() { - let wf = Waveform::::with_components(100.0, vec![sine!(1), dc_bias!(5)]); + let wf = Waveform::::with_components(100.0, vec![sine!(1), dc_bias!(5)]).unwrap(); let samples = wf.iter().take(100).collect::>(); @@ -254,7 +256,7 @@ mod tests { paste! { #[test] fn []() { - let wf = Waveform::::with_components(100.0, vec![$func]); + let wf = Waveform::::with_components(100.0, vec![$func]).unwrap(); let bias = wf.iter().take(100).sum::() / 100.0; @@ -274,7 +276,7 @@ mod tests { #[test] #[allow(clippy::iter_skip_next)] fn waveform_iterator_is_infinite() { - let wf = Waveform::::new(f64::MIN_POSITIVE); + let wf = Waveform::::new(f64::MIN_POSITIVE).unwrap(); let mut iter = wf.iter().skip(usize::MAX); assert_eq!(Some(0f64), iter.next()); @@ -283,7 +285,7 @@ mod tests { #[test] fn oversaturated_amplitude_clips_to_max() { - let wf = Waveform::::with_components(100.0, vec![dc_bias!(300)]); + let wf = Waveform::::with_components(100.0, vec![dc_bias!(300)]).unwrap(); let samples = wf.iter().take(1).collect::>(); assert_eq!(samples.len(), 1); @@ -292,36 +294,34 @@ mod tests { #[test] fn undersaturated_amplitude_clips_to_min() { - let wf = Waveform::::with_components(100.0, vec![dc_bias!(-300)]); + let wf = Waveform::::with_components(100.0, vec![dc_bias!(-300)]).unwrap(); let samples = wf.iter().take(1).collect::>(); assert_eq!(samples.len(), 1); assert_eq!(samples[0], u8::MIN); } - macro_rules! test_wavefrom_panic { + macro_rules! test_wavefrom_err { ($($name:ident: $sample_rate:expr)*) => { $( paste! { #[test] - #[should_panic] fn []() { - Waveform::::new($sample_rate); + assert!(Waveform::::new($sample_rate).is_err()); } #[test] - #[should_panic] fn []() { - Waveform::::with_components($sample_rate, vec![]); + assert!(Waveform::::with_components($sample_rate, vec![]).is_err()); } } )* }; } - test_wavefrom_panic! { + test_wavefrom_err! { nan: f64::NAN negative: -1f64 zero: 0.0 @@ -331,11 +331,11 @@ mod tests { macro_rules! test_size_hint { () => { - let wf = Waveform::::new(44100.0); + let wf = Waveform::::new(44100.0).unwrap(); assert_eq!((usize::MAX, None), wf.iter().size_hint()); }; ($($component:expr),*) => { - let mut wf = Waveform::::new(44100.0); + let mut wf = Waveform::::new(44100.0).unwrap(); $( wf.add_component($component); )* @@ -354,7 +354,7 @@ mod tests { #[allow(clippy::iter_nth_zero)] #[allow(clippy::unwrap_used)] fn nth_and_next_give_same_results() { - let wf = Waveform::::with_components(44100.0, vec![sine!(3000, i32::MAX)]); + let wf = Waveform::::with_components(44100.0, vec![sine!(3000, i32::MAX)]).unwrap(); let mut i1 = wf.iter(); let mut i2 = wf.iter(); From 87e4d65c47b228f18fd373b89714d3111ee5766b Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Fri, 23 Dec 2022 14:13:03 +0100 Subject: [PATCH 5/8] fix typos --- examples/plot.rs | 2 ++ src/lib.rs | 4 ++-- src/macros.rs | 10 +++++----- src/waveform.rs | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/plot.rs b/examples/plot.rs index d534cb7..1622ca8 100644 --- a/examples/plot.rs +++ b/examples/plot.rs @@ -118,5 +118,7 @@ fn draw_internal, P: AsRef>( .border_style(BLACK) .draw()?; + root.present()?; + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 0fdfc35..79b4621 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,9 +101,9 @@ //! //! # Note about Nyquist-Shannon rule enforcement //! -//! As a rule of thumb in signal processing, the sampling frequency should be *at least* 2 times bigger than the highest frequency of sampled continous signal. +//! As a rule of thumb in signal processing, the sampling frequency should be *at least* 2 times bigger than the highest frequency of sampled continuos signal. //! -//! This lib will **not** enforce the Nyquist-Shannon rule on the waveforms you create, therefore abominations like this are possible (altough not recommended): +//! This lib will **not** enforce the Nyquist-Shannon rule on the waveforms you create, therefore abominations like this are possible (although not recommended): //! //! ``` //! use wavegen::{Waveform, sine}; diff --git a/src/macros.rs b/src/macros.rs index b78314a..cbfe2ea 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -62,7 +62,7 @@ macro_rules! dc_bias { /// /// | argument | unit | notes | /// | -------- | ---- | ----- | -/// | frequency | Hz | Frequecy of the periodic function. Also: 1 / period | +/// | frequency | Hz | Frequency of the periodic function. Also: 1 / period | /// | amplitude | *arbitrary* | The amplitude of the function in 0-peak notation. | /// | phase | *periods* | The phase shift of the function. Value of 1 means full shift around. /// @@ -95,7 +95,7 @@ macro_rules! sawtooth { /// /// | argument | unit | notes | /// | -------- | ---- | ----- | -/// | frequency | Hz | Frequecy of the periodic function. Also: 1 / period | +/// | frequency | Hz | Frequency of the periodic function. Also: 1 / period | /// | amplitude | *arbitrary* | The amplitude of the function in 0-peak notation. | /// | phase | *periods* | The phase shift of the function. Value of 1 means full shift around. /// @@ -145,7 +145,7 @@ macro_rules! sine { /// /// | argument | unit | notes | /// | -------- | ---- | ----- | -/// | frequency | Hz | Frequecy of the periodic function. Also: 1 / period | +/// | frequency | Hz | Frequency of the periodic function. Also: 1 / period | /// | amplitude | *arbitrary* | The amplitude of the function in 0-peak notation. | /// | phase | *periods* | The phase shift of the function. Value of 1 means full shift around. /// @@ -185,12 +185,12 @@ mod tests { } #[test] - fn wavefrom_with_one_component() { + fn waveform_with_one_component() { let wf = wf!(f64, 44100, sine!(500)).unwrap(); assert_eq!(1, wf.get_components_len()); } #[test] - fn wavefrom_with_three_components() { + fn waveform_with_three_components() { let wf = wf!(f64, 44100, sine!(500), square!(1000), sawtooth!(1500)).unwrap(); assert_eq!(3, wf.get_components_len()); } diff --git a/src/waveform.rs b/src/waveform.rs index 515935e..c0ea2e9 100644 --- a/src/waveform.rs +++ b/src/waveform.rs @@ -301,7 +301,7 @@ mod tests { assert_eq!(samples[0], u8::MIN); } - macro_rules! test_wavefrom_err { + macro_rules! test_waveform_err { ($($name:ident: $sample_rate:expr)*) => { $( paste! { @@ -321,7 +321,7 @@ mod tests { }; } - test_wavefrom_err! { + test_waveform_err! { nan: f64::NAN negative: -1f64 zero: 0.0 From f987c4e25b95dece0b38baf9443929aa54da053e Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Mon, 26 Dec 2022 10:45:32 +0100 Subject: [PATCH 6/8] bump version 0.4.0 --- CHANGELOG.md | 8 ++++++-- Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1cf8c7..69af6ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,19 @@ # wavegen +## 0.4.0 + +- `Waveform` constructors now return `Result` to allow for handling the errors instead of outright panicking. + ## 0.3.0 - `wf` helper macro added. - `WaveformIterator` no longer will panic on failed conversion to output type. - Switch to using `Into` instead of directly casting numeric types in numerous places. -# 0.2.2 +## 0.2.2 - Implemented `size_hint`. -# 0.2.1 +## 0.2.1 - Added `Sync` and `Send` constraints on `PeriodicFunction`. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index f718a6f..16abb91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wavegen" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = ["Michal Borejszo "] license = "MIT" diff --git a/README.md b/README.md index c79918a..8b190e6 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ ```toml [dependencies] -wavegen = "0.3" +wavegen = "0.4" ``` Or, to use the *no_std* version (custom global allocator is required): ```toml [dependencies] -wavegen = { version = "0.3", default-features = false, features = ["libm"] } +wavegen = { version = "0.4", default-features = false, features = ["libm"] } ``` 2) Define a waveform with sampling frequency and function components: From 42dbb1d6c27456200c3f1e3002f67ebd612f4934 Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Mon, 26 Dec 2022 10:47:40 +0100 Subject: [PATCH 7/8] update example in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b190e6..eb80a6b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ wavegen = { version = "0.4", default-features = false, features = ["libm"] } 2) Define a waveform with sampling frequency and function components: ```rust -let waveform = wf!(f64, 200, sine!(frequency: 100, amplitude: 10), dc_bias!(20)); +let waveform = wf!(f64, 200, sine!(frequency: 100, amplitude: 10), dc_bias!(20)).unwrap(); ``` 3) Turn it into an iterator and sample: From 8580548e31b14c4bacf964e0fcddc9e4259cb04d Mon Sep 17 00:00:00 2001 From: Michal Borejszo Date: Mon, 26 Dec 2022 12:02:32 +0100 Subject: [PATCH 8/8] remove thiserror --- Cargo.toml | 1 - src/errors.rs | 15 +++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16abb91..71d4aa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ categories = [ [dependencies] libm = { version = "0.2", optional = true } num-traits = { version = "0.2", default-features = false } -thiserror = "1.0.38" [dev-dependencies] plotters = "^0.3.1" diff --git a/src/errors.rs b/src/errors.rs index 2f71d0f..a93d8a5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,13 @@ -use thiserror::Error; +use core::fmt::Display; -#[derive(Error, Debug, Clone, Copy)] -#[error("Invalid SamplingRate value: `{0}`. SamplingRate has to be positive, non-zero and finite.")] -/// Error raised when cosntructing [`SamplingRate`] of invalid value. +#[derive(Debug, Clone, Copy)] +/// Error raised when cosntructing [`Waveform`] with sample rate of invalid value. pub struct InvalidSampleRate(pub(crate) f64); + +impl Display for InvalidSampleRate { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Invalid SamplingRate value: `{}`. SamplingRate has to be positive, non-zero and finite.", self.0) + } +} + +impl std::error::Error for InvalidSampleRate {}