From 460c7b23f0c48cb3ceb89b29de770874eed545e0 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 10:49:20 -0800 Subject: [PATCH 01/24] Create `dasp_filter` subcrate and skeleton module --- dasp_filter/Cargo.toml | 25 +++++++++++++++++++++++++ dasp_filter/src/lib.rs | 3 +++ 2 files changed, 28 insertions(+) create mode 100644 dasp_filter/Cargo.toml create mode 100644 dasp_filter/src/lib.rs diff --git a/dasp_filter/Cargo.toml b/dasp_filter/Cargo.toml new file mode 100644 index 00000000..d425e783 --- /dev/null +++ b/dasp_filter/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "dasp_filter" +description = "Filters for digital audio signals." +version = "0.11.0" +authors = ["mitchmindtree ", "Mark LeMoine "] +readme = "../README.md" +keywords = ["dsp", "filter", "biquad"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rustaudio/dasp.git" +homepage = "https://github.com/rustaudio/dasp" +edition = "2018" + +[dependencies] +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"] +std = [ + "dasp_frame/std", + "dasp_sample/std", +] + +[package.metadata.docs.rs] +all-features = true diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs new file mode 100644 index 00000000..71266e89 --- /dev/null +++ b/dasp_filter/src/lib.rs @@ -0,0 +1,3 @@ + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] From 4cf170f97cd2fe872f7084f25c124c51d14bccdc Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 10:57:16 -0800 Subject: [PATCH 02/24] Plug in `dasp_filter` into top level `dasp/Cargo.toml` --- Cargo.toml | 1 + dasp/Cargo.toml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 920f3cdf..a0a1cf97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "dasp", "dasp_envelope", + "dasp_filter", "dasp_frame", "dasp_graph", "dasp_interpolate", diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 0e780e4c..9128e262 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [dependencies] dasp_envelope = { version = "0.11", path = "../dasp_envelope", default-features = false, optional = true } +dasp_filter = { version = "0.11", path = "../dasp_filter", default-features = false, optional = true } dasp_frame = { version = "0.11", path = "../dasp_frame", default-features = false } dasp_graph = { version = "0.11", path = "../dasp_graph", default-features = false, optional = true } dasp_interpolate = { version = "0.11", path = "../dasp_interpolate", default-features = false, optional = true } @@ -36,6 +37,7 @@ all-no-std = [ "envelope", "envelope-peak", "envelope-rms", + "filter", "interpolate", "interpolate-floor", "interpolate-linear", @@ -59,6 +61,7 @@ all-no-std = [ ] std = [ "dasp_envelope/std", + "dasp_filter/std", "dasp_frame/std", "dasp_interpolate/std", "dasp_peak/std", @@ -72,6 +75,7 @@ std = [ envelope = ["dasp_envelope"] envelope-peak = ["dasp_envelope/peak"] envelope-rms = ["dasp_envelope/rms"] +filter = ["dasp_filter"] graph = ["dasp_graph"] graph-all-nodes = ["dasp_graph/all-nodes"] graph-node-boxed = ["dasp_graph/node-boxed"] From 864b97144c3ddd6acc3fbda17778cdc7879cfee5 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 16:59:22 -0800 Subject: [PATCH 03/24] Add `Coefficients` struct to `dasp_filter` --- dasp_filter/src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 71266e89..ee6f52d8 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -1,3 +1,25 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] + +use dasp_sample::FloatSample; + +/// Coefficients for a digital biquad filter. +/// It is assumed that the `a0` coefficient is always normalized to 1.0, +/// and thus not included. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Coefficients +where + S: FloatSample, +{ + // Numerator coefficients. + pub b0: S, + pub b1: S, + pub b2: S, + + // Denominator coefficients. + pub a1: S, + pub a2: S, +} + + From 2259fccf9e5f9998adffef1177fca5005fc939d9 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 17:23:20 -0800 Subject: [PATCH 04/24] Add `Biquad` struct to `dasp_filter` --- dasp_filter/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index ee6f52d8..3f08011e 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] +use dasp_frame::Frame; use dasp_sample::FloatSample; /// Coefficients for a digital biquad filter. @@ -22,4 +23,15 @@ where pub a2: S, } +/// An implementation of a digital biquad filter. +pub struct Biquad +where + S: FloatSample, + F: Frame, +{ + coeff: Coefficients, + // Since biquad filters are second-order, we require two historical buffers. + m1: F, + m2: F, +} From 5dd4d72a36eb75edd4f6f340634608a772afaf65 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 17:23:37 -0800 Subject: [PATCH 05/24] Add `Biquad::new` --- dasp_filter/src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 3f08011e..3580d2e3 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -35,3 +35,17 @@ where m1: F, m2: F, } + +impl Biquad +where + S: FloatSample, + F: Frame, +{ + pub fn new(coeff: Coefficients) -> Self { + Self { + coeff, + m1: F::EQUILIBRIUM, + m2: F::EQUILIBRIUM, + } + } +} From ac16c435689715973d403a68817869428cdf5a3d Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 17:47:06 -0800 Subject: [PATCH 06/24] Add `Biquad::apply` --- dasp_filter/src/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 3580d2e3..b9251f0f 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] use dasp_frame::Frame; -use dasp_sample::FloatSample; +use dasp_sample::{FloatSample, FromSample, ToSample}; /// Coefficients for a digital biquad filter. /// It is assumed that the `a0` coefficient is always normalized to 1.0, @@ -48,4 +48,23 @@ where m2: F::EQUILIBRIUM, } } + + pub fn apply(&mut self, input: I) -> I + where + I: Frame, + I::Sample: ToSample + FromSample, + { + let input: F = input.map(ToSample::to_sample_); + + // Alias to make calculations less verbose. + let co = &self.coeff; + + let output = self.m1.add_amp(input.scale_amp(co.b0)); + + // Update buffers, which depend on new output. + self.m1 = self.m2.add_amp(input.scale_amp(co.b1).add_amp(output.scale_amp(-co.a1))); + self.m2 = input.scale_amp(co.b2).add_amp(output.scale_amp(-co.a2)); + + output.map(FromSample::from_sample_) + } } From a13aa84f326a22bc49fda4325e9ff0ea632960e7 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 19:09:28 -0800 Subject: [PATCH 07/24] Simplfy type parameter usage. Buff up implementation of `Biquad::apply` --- dasp_filter/src/lib.rs | 48 ++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index b9251f0f..125032a0 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -23,48 +23,60 @@ where pub a2: S, } -/// An implementation of a digital biquad filter. -pub struct Biquad +/// An implementation of a digital biquad filter, using the Direct Form 2 +/// Transposed (DF2T) representation. +pub struct Biquad where - S: FloatSample, - F: Frame, + F: Frame, + F::Sample: FloatSample, { - coeff: Coefficients, + coeff: Coefficients, // Since biquad filters are second-order, we require two historical buffers. + // This state is updated each time the filter is applied to a `Frame`. + m0: F, m1: F, - m2: F, } -impl Biquad +impl Biquad where - S: FloatSample, - F: Frame, + F: Frame, + F::Sample: FloatSample, { - pub fn new(coeff: Coefficients) -> Self { + pub fn new(coeff: Coefficients) -> Self { Self { coeff, + m0: F::EQUILIBRIUM, m1: F::EQUILIBRIUM, - m2: F::EQUILIBRIUM, } } pub fn apply(&mut self, input: I) -> I where I: Frame, - I::Sample: ToSample + FromSample, + I::Sample: ToSample + FromSample, { + // Convert into floating point representation. let input: F = input.map(ToSample::to_sample_); - // Alias to make calculations less verbose. - let co = &self.coeff; + // Calculate scaled inputs. + let input_by_b0 = input.scale_amp(self.coeff.b0); + let input_by_b1 = input.scale_amp(self.coeff.b1); + let input_by_b2 = input.scale_amp(self.coeff.b2); + + // This is the new filtered `Frame`. + let output: F = self.m0.add_amp(input_by_b0); - let output = self.m1.add_amp(input.scale_amp(co.b0)); + // Calculate scaled outputs. + // NOTE: Negative signs on the scaling factors for these. + let output_by_neg_a1 = output.scale_amp(-self.coeff.a1); + let output_by_neg_a2 = output.scale_amp(-self.coeff.a2); - // Update buffers, which depend on new output. - self.m1 = self.m2.add_amp(input.scale_amp(co.b1).add_amp(output.scale_amp(-co.a1))); - self.m2 = input.scale_amp(co.b2).add_amp(output.scale_amp(-co.a2)); + // Update buffers. + self.m0 = self.m1.add_amp(input_by_b1).add_amp(output_by_neg_a1); + self.m1 = input_by_b2.add_amp(output_by_neg_a2); + // Convert back into the original `Frame` format. output.map(FromSample::from_sample_) } } From 619c2225a5fb03d19446deb07db3356b1eead915 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 19:13:48 -0800 Subject: [PATCH 08/24] Add doc comment to `Biquad::apply` --- dasp_filter/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 125032a0..99891c61 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -51,6 +51,8 @@ where } } + /// Performs a single iteration of this filter, calculating a new filtered + /// `Frame` from an input `Frame`. pub fn apply(&mut self, input: I) -> I where I: Frame, From 83b352d88f6683ed951041f7941d1f9f9e583c2b Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 19:22:08 -0800 Subject: [PATCH 09/24] Cleanup `Coefficient`/`Biquad` field names --- dasp_filter/src/lib.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 99891c61..c73ff89b 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -13,14 +13,14 @@ pub struct Coefficients where S: FloatSample, { - // Numerator coefficients. - pub b0: S, - pub b1: S, - pub b2: S, + // Numerator coefficients (normally b0, b1, b2). + pub n0: S, + pub n1: S, + pub n2: S, - // Denominator coefficients. - pub a1: S, - pub a2: S, + // Denominator coefficients (normally a1, a2). + pub d1: S, + pub d2: S, } /// An implementation of a digital biquad filter, using the Direct Form 2 @@ -30,12 +30,12 @@ where F: Frame, F::Sample: FloatSample, { - coeff: Coefficients, + pub coeff: Coefficients, // Since biquad filters are second-order, we require two historical buffers. // This state is updated each time the filter is applied to a `Frame`. - m0: F, - m1: F, + t0: F, + t1: F, } impl Biquad @@ -46,8 +46,8 @@ where pub fn new(coeff: Coefficients) -> Self { Self { coeff, - m0: F::EQUILIBRIUM, - m1: F::EQUILIBRIUM, + t0: F::EQUILIBRIUM, + t1: F::EQUILIBRIUM, } } @@ -62,21 +62,21 @@ where let input: F = input.map(ToSample::to_sample_); // Calculate scaled inputs. - let input_by_b0 = input.scale_amp(self.coeff.b0); - let input_by_b1 = input.scale_amp(self.coeff.b1); - let input_by_b2 = input.scale_amp(self.coeff.b2); + let input_by_n0 = input.scale_amp(self.coeff.n0); + let input_by_n1 = input.scale_amp(self.coeff.n1); + let input_by_n2 = input.scale_amp(self.coeff.n2); // This is the new filtered `Frame`. - let output: F = self.m0.add_amp(input_by_b0); + let output: F = self.t0.add_amp(input_by_n0); // Calculate scaled outputs. // NOTE: Negative signs on the scaling factors for these. - let output_by_neg_a1 = output.scale_amp(-self.coeff.a1); - let output_by_neg_a2 = output.scale_amp(-self.coeff.a2); + let output_by_neg_d1 = output.scale_amp(-self.coeff.d1); + let output_by_neg_d2 = output.scale_amp(-self.coeff.d2); // Update buffers. - self.m0 = self.m1.add_amp(input_by_b1).add_amp(output_by_neg_a1); - self.m1 = input_by_b2.add_amp(output_by_neg_a2); + self.t0 = self.t1.add_amp(input_by_n1).add_amp(output_by_neg_d1); + self.t1 = input_by_n2.add_amp(output_by_neg_d2); // Convert back into the original `Frame` format. output.map(FromSample::from_sample_) From 1116064f27e310ccd2f3bbb0d257e210aac9ac7b Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 20:03:58 -0800 Subject: [PATCH 10/24] Rename `Coefficient` field names. Add doctest for `Biquad::apply` --- dasp_filter/src/lib.rs | 53 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index c73ff89b..196ab58a 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -13,14 +13,14 @@ pub struct Coefficients where S: FloatSample, { - // Numerator coefficients (normally b0, b1, b2). - pub n0: S, - pub n1: S, - pub n2: S, + // Transfer function numerator coefficients. + pub b0: S, + pub b1: S, + pub b2: S, - // Denominator coefficients (normally a1, a2). - pub d1: S, - pub d2: S, + // Transfer function denominator coefficients. + pub a1: S, + pub a2: S, } /// An implementation of a digital biquad filter, using the Direct Form 2 @@ -53,6 +53,29 @@ where /// Performs a single iteration of this filter, calculating a new filtered /// `Frame` from an input `Frame`. + /// + /// ```rust + /// use dasp_filter::{Coefficients, Biquad}; + /// + /// fn main() { + /// // Notch boost filter. + /// let co = Coefficients { + /// b0: 1.0469127398708575f64, + /// b1: -0.27732002669854483, + /// b2: 0.8588151488168104, + /// a1: -0.27732002669854483, + /// a2: 0.9057278886876682, + /// }; + /// + /// // Note that this type argument defines the format of the temporary + /// // values, as well as the number of channels required for input + /// // `Frame`s. + /// let mut b = Biquad::<[f64; 2]>::new(co); + /// + /// assert_eq!(b.apply([32i8, -64]), [33, -67]); + /// assert_eq!(b.apply([0.1f32, -0.3]), [0.107943736, -0.32057875]); + /// } + /// ``` pub fn apply(&mut self, input: I) -> I where I: Frame, @@ -62,21 +85,21 @@ where let input: F = input.map(ToSample::to_sample_); // Calculate scaled inputs. - let input_by_n0 = input.scale_amp(self.coeff.n0); - let input_by_n1 = input.scale_amp(self.coeff.n1); - let input_by_n2 = input.scale_amp(self.coeff.n2); + let input_by_b0 = input.scale_amp(self.coeff.b0); + let input_by_b1 = input.scale_amp(self.coeff.b1); + let input_by_b2 = input.scale_amp(self.coeff.b2); // This is the new filtered `Frame`. - let output: F = self.t0.add_amp(input_by_n0); + let output: F = self.t0.add_amp(input_by_b0); // Calculate scaled outputs. // NOTE: Negative signs on the scaling factors for these. - let output_by_neg_d1 = output.scale_amp(-self.coeff.d1); - let output_by_neg_d2 = output.scale_amp(-self.coeff.d2); + let output_by_neg_a1 = output.scale_amp(-self.coeff.a1); + let output_by_neg_a2 = output.scale_amp(-self.coeff.a2); // Update buffers. - self.t0 = self.t1.add_amp(input_by_n1).add_amp(output_by_neg_d1); - self.t1 = input_by_n2.add_amp(output_by_neg_d2); + self.t0 = self.t1.add_amp(input_by_b1).add_amp(output_by_neg_a1); + self.t1 = input_by_b2.add_amp(output_by_neg_a2); // Convert back into the original `Frame` format. output.map(FromSample::from_sample_) From d2015146008f5c5369f7389304cac14f4416cdb5 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 22:10:12 -0800 Subject: [PATCH 11/24] Add `From` impl for `Biquad` --- dasp_filter/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 196ab58a..8104a8a8 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -105,3 +105,14 @@ where output.map(FromSample::from_sample_) } } + +impl From> for Biquad +where + F: Frame, + F::Sample: FloatSample, +{ + // Same as `new()`, but adding this for the blanket `Into` impl. + fn from(coeff: Coefficients) -> Self { + Self::new(coeff) + } +} From 8d4c76607d13edb7ad945e4c683728805bc76608 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 22:35:01 -0800 Subject: [PATCH 12/24] Add skeleton feature to `dasp_signal` for filtered signals. Add features flags up crate chain --- dasp/Cargo.toml | 2 ++ dasp_signal/Cargo.toml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/dasp/Cargo.toml b/dasp/Cargo.toml index 9128e262..46388838 100644 --- a/dasp/Cargo.toml +++ b/dasp/Cargo.toml @@ -49,6 +49,7 @@ all-no-std = [ "signal-boxed", "signal-bus", "signal-envelope", + "signal-filter", "signal-rms", "signal-window", "signal-window-hann", @@ -94,6 +95,7 @@ signal = ["dasp_signal"] signal-boxed = ["dasp_signal/boxed"] signal-bus = ["dasp_signal/bus"] signal-envelope = ["dasp_signal/envelope", "envelope"] +signal-filter = ["dasp_signal/filter", "filter"] signal-rms = ["dasp_signal/rms", "rms"] signal-window = ["dasp_signal/window", "window"] signal-window-hann = ["dasp_signal/window-hann", "window-hann"] diff --git a/dasp_signal/Cargo.toml b/dasp_signal/Cargo.toml index 139b1b71..95764383 100644 --- a/dasp_signal/Cargo.toml +++ b/dasp_signal/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [dependencies] dasp_envelope = { version = "0.11", path = "../dasp_envelope", default-features = false, optional = true } +dasp_filter = { version = "0.11", path = "../dasp_filter", 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 } @@ -32,6 +33,7 @@ all-no-std = [ "boxed", "bus", "envelope", + "filter", "rms", "window", "window-hann", @@ -39,6 +41,7 @@ all-no-std = [ ] std = [ "dasp_envelope/std", + "dasp_filter/std", "dasp_frame/std", "dasp_interpolate/std", "dasp_peak/std", @@ -50,6 +53,7 @@ std = [ boxed = [] bus = [] envelope = ["dasp_envelope"] +filter = ["dasp_filter"] rms = ["dasp_rms"] window = ["dasp_window"] window-hann = ["dasp_window/hann"] From 4b1e2853d887d872416f6cccd76feab0ec58dfa9 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 22:37:44 -0800 Subject: [PATCH 13/24] Add and plug in `dasp_signal::filter` submodule --- dasp_signal/src/filter.rs | 9 +++++++++ dasp_signal/src/lib.rs | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 dasp_signal/src/filter.rs diff --git a/dasp_signal/src/filter.rs b/dasp_signal/src/filter.rs new file mode 100644 index 00000000..f911dd68 --- /dev/null +++ b/dasp_signal/src/filter.rs @@ -0,0 +1,9 @@ +//! An extension to the **Signal** trait that enables iterative filtering. +//! +//! ### Required Features +//! +//! - When using `dasp_signal`, this item requires the **filter** feature to be enabled. +//! - When using `dasp`, this item requires the **signal-filter** feature to be enabled. + +use crate::Signal; +use dasp_filter as filter; diff --git a/dasp_signal/src/lib.rs b/dasp_signal/src/lib.rs index da36d9f2..19db379b 100644 --- a/dasp_signal/src/lib.rs +++ b/dasp_signal/src/lib.rs @@ -63,6 +63,8 @@ mod boxed; pub mod bus; #[cfg(feature = "envelope")] pub mod envelope; +#[cfg(feature = "filter")] +pub mod filter; #[cfg(feature = "rms")] pub mod rms; #[cfg(feature = "window")] From 77fbcbc1f566e9612f1307f3e488dfccf6960848 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 22:49:12 -0800 Subject: [PATCH 14/24] Create `SignalFilter` trait and `FilteredSignal` struct --- dasp_signal/src/filter.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/dasp_signal/src/filter.rs b/dasp_signal/src/filter.rs index f911dd68..be43a003 100644 --- a/dasp_signal/src/filter.rs +++ b/dasp_signal/src/filter.rs @@ -6,4 +6,43 @@ //! - When using `dasp`, this item requires the **signal-filter** feature to be enabled. use crate::Signal; +use dasp_frame::Frame; use dasp_filter as filter; +use dasp_sample::Sample; + +/// An extension to the **Signal** trait that enables iterative filtering. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **filter** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-filter** feature to be enabled. +pub trait SignalFilter: Signal { + /// An adaptor that calculates and yields a filtered signal. + /// + /// ### Required Features + /// + /// - When using `dasp_signal`, this item requires the **filter** feature to be enabled. + /// - When using `dasp`, this item requires the **signal-filter** feature to be enabled. + fn filtered( + self, + coeff: filter::Coefficients<<::Sample as Sample>::Float>, + ) -> FilteredSignal + where + Self: Sized, + { + let biquad = filter::Biquad::from(coeff); + + FilteredSignal { + signal: self, + biquad, + } + } +} + +pub struct FilteredSignal +where + S: Signal, +{ + signal: S, + biquad: filter::Biquad<::Float>, +} From 0d15bd9fc2d287d822575c3093367f3bbe1ccfbc Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 22:57:00 -0800 Subject: [PATCH 15/24] Begin `Signal` impl for `FilteredSignal`, need to fix `dasp_filter` crate --- dasp_signal/src/filter.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dasp_signal/src/filter.rs b/dasp_signal/src/filter.rs index be43a003..dc33c01a 100644 --- a/dasp_signal/src/filter.rs +++ b/dasp_signal/src/filter.rs @@ -46,3 +46,21 @@ where signal: S, biquad: filter::Biquad<::Float>, } + +impl Signal for FilteredSignal +where + S: Signal, +{ + // Output is the same type as the input. + type Frame = S::Frame; + + fn next(&mut self) -> Self::Frame { + todo!("use `Sample::Float` in `signal` + instead of `ToSample`/`FromSample` in bounds") + // self.biquad.apply(self.signal.next()) + } + + fn is_exhausted(&self) -> bool { + self.signal.is_exhausted() + } +} From ef9426a262977b9910a4c39471452c45a24ffcde Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Wed, 13 Jan 2021 23:14:54 -0800 Subject: [PATCH 16/24] Fix trait bounds for `SignalFilter`/`FilteredSignal` --- dasp_signal/src/filter.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dasp_signal/src/filter.rs b/dasp_signal/src/filter.rs index dc33c01a..e283ea49 100644 --- a/dasp_signal/src/filter.rs +++ b/dasp_signal/src/filter.rs @@ -8,7 +8,7 @@ use crate::Signal; use dasp_frame::Frame; use dasp_filter as filter; -use dasp_sample::Sample; +use dasp_sample::{Sample, FromSample}; /// An extension to the **Signal** trait that enables iterative filtering. /// @@ -29,6 +29,7 @@ pub trait SignalFilter: Signal { ) -> FilteredSignal where Self: Sized, + ::Sample: FromSample<<::Sample as Sample>::Float>, { let biquad = filter::Biquad::from(coeff); @@ -42,6 +43,7 @@ pub trait SignalFilter: Signal { pub struct FilteredSignal where S: Signal, + ::Sample: FromSample<<::Sample as Sample>::Float>, { signal: S, biquad: filter::Biquad<::Float>, @@ -50,14 +52,13 @@ where impl Signal for FilteredSignal where S: Signal, + ::Sample: FromSample<<::Sample as Sample>::Float>, { // Output is the same type as the input. type Frame = S::Frame; fn next(&mut self) -> Self::Frame { - todo!("use `Sample::Float` in `signal` - instead of `ToSample`/`FromSample` in bounds") - // self.biquad.apply(self.signal.next()) + self.biquad.apply(self.signal.next()) } fn is_exhausted(&self) -> bool { From 22558ea4aa18383eab1a542e6fcba6ec50f7b12c Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Thu, 14 Jan 2021 16:41:20 -0800 Subject: [PATCH 17/24] Add doctest for `SignalFilter`. Add impl of `SignalFilter` for all `Signal`s --- dasp_signal/src/filter.rs | 40 +++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/dasp_signal/src/filter.rs b/dasp_signal/src/filter.rs index e283ea49..58eda409 100644 --- a/dasp_signal/src/filter.rs +++ b/dasp_signal/src/filter.rs @@ -12,17 +12,36 @@ use dasp_sample::{Sample, FromSample}; /// An extension to the **Signal** trait that enables iterative filtering. /// +/// # Example +/// +/// ``` +/// use dasp_filter::{self as filter, Coefficients}; +/// use dasp_signal::{self as signal, Signal}; +/// use dasp_signal::filter::SignalFilter; +/// +/// fn main() { +/// let signal = signal::rate(48000.0).const_hz(997.0).sine(); +/// // Notch filter to attenuate 997 Hz. +/// let coeff = Coefficients { +/// b0: 0.9157328640471359f64, +/// b1: -1.8158910212730535, +/// b2: 0.9157328640471359, +/// a1: -1.8158910212730535, +/// a2: 0.831465728094272, +/// }; +/// let mut filtered = signal.filtered(coeff); +/// assert_eq!( +/// filtered.take(4).collect::>(), +/// vec![0.0, 0.11917058366454024, 0.21640079287630784, 0.2938740006664008] +/// ); +/// } +/// ``` +/// /// ### Required Features /// /// - When using `dasp_signal`, this item requires the **filter** feature to be enabled. /// - When using `dasp`, this item requires the **signal-filter** feature to be enabled. pub trait SignalFilter: Signal { - /// An adaptor that calculates and yields a filtered signal. - /// - /// ### Required Features - /// - /// - When using `dasp_signal`, this item requires the **filter** feature to be enabled. - /// - When using `dasp`, this item requires the **signal-filter** feature to be enabled. fn filtered( self, coeff: filter::Coefficients<<::Sample as Sample>::Float>, @@ -40,6 +59,12 @@ pub trait SignalFilter: Signal { } } +/// An adaptor that calculates and yields a filtered signal. +/// +/// ### Required Features +/// +/// - When using `dasp_signal`, this item requires the **filter** feature to be enabled. +/// - When using `dasp`, this item requires the **signal-filter** feature to be enabled. pub struct FilteredSignal where S: Signal, @@ -65,3 +90,6 @@ where self.signal.is_exhausted() } } + +// Impl this for all `Signal`s. +impl SignalFilter for T where T: Signal {} From 7cc935b907206d5289977669a46b6d0a6bc4c3cc Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Thu, 14 Jan 2021 18:58:48 -0800 Subject: [PATCH 18/24] Make format changes --- dasp_filter/src/lib.rs | 1 - dasp_signal/src/filter.rs | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index 8104a8a8..db336a61 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -1,4 +1,3 @@ - #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] diff --git a/dasp_signal/src/filter.rs b/dasp_signal/src/filter.rs index 58eda409..7dd45e1e 100644 --- a/dasp_signal/src/filter.rs +++ b/dasp_signal/src/filter.rs @@ -6,9 +6,9 @@ //! - When using `dasp`, this item requires the **signal-filter** feature to be enabled. use crate::Signal; -use dasp_frame::Frame; use dasp_filter as filter; -use dasp_sample::{Sample, FromSample}; +use dasp_frame::Frame; +use dasp_sample::{FromSample, Sample}; /// An extension to the **Signal** trait that enables iterative filtering. /// @@ -48,7 +48,8 @@ pub trait SignalFilter: Signal { ) -> FilteredSignal where Self: Sized, - ::Sample: FromSample<<::Sample as Sample>::Float>, + ::Sample: + FromSample<<::Sample as Sample>::Float>, { let biquad = filter::Biquad::from(coeff); From f3d70511d55ff1fca5984270ada52ec8ff8992eb Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Thu, 14 Jan 2021 21:08:01 -0800 Subject: [PATCH 19/24] Use `Duplex` trait bound instead of `FromSample + ToSample` --- dasp_filter/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dasp_filter/src/lib.rs b/dasp_filter/src/lib.rs index db336a61..23700f21 100644 --- a/dasp_filter/src/lib.rs +++ b/dasp_filter/src/lib.rs @@ -2,7 +2,7 @@ #![cfg_attr(not(feature = "std"), feature(core_intrinsics))] use dasp_frame::Frame; -use dasp_sample::{FloatSample, FromSample, ToSample}; +use dasp_sample::{Duplex, FloatSample, FromSample, ToSample}; /// Coefficients for a digital biquad filter. /// It is assumed that the `a0` coefficient is always normalized to 1.0, @@ -78,7 +78,7 @@ where pub fn apply(&mut self, input: I) -> I where I: Frame, - I::Sample: ToSample + FromSample, + I::Sample: Duplex, { // Convert into floating point representation. let input: F = input.map(ToSample::to_sample_); From 777ef313d5acd88877683808c71ac02de7bba809 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Sun, 17 Jan 2021 17:52:17 -0800 Subject: [PATCH 20/24] Update `.github/workflows` --- .github/workflows/dasp.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/dasp.yml b/.github/workflows/dasp.yml index 398aad21..5a865b72 100644 --- a/.github/workflows/dasp.yml +++ b/.github/workflows/dasp.yml @@ -63,6 +63,11 @@ jobs: with: command: test args: --manifest-path dasp_envelope/Cargo.toml --no-default-features --verbose + - name: cargo test dasp_filter (no default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_filter/Cargo.toml --no-default-features --verbose - name: cargo test dasp_frame (no default features) uses: actions-rs/cargo@v1 with: @@ -154,6 +159,11 @@ jobs: with: command: test args: --manifest-path dasp/Cargo.toml --no-default-features --features "all-no-std" --verbose + - name: cargo test dasp_filter (all features no std) + uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path dasp_filter/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: @@ -270,6 +280,9 @@ jobs: - name: cargo publish dasp_graph continue-on-error: true run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_graph/Cargo.toml + - name: cargo publish dasp_filter + continue-on-error: true + run: cargo publish --token $CRATESIO_TOKEN --manifest-path dasp_filter/Cargo.toml - name: wait for crates.io run: sleep 5 - name: cargo publish dasp From f476596f22193f93f76e7cf5b5c2a8b79858f741 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Sun, 17 Jan 2021 17:55:33 -0800 Subject: [PATCH 21/24] Remove `dasp_filter` from "all-no-std" section in `.github/workflows` --- .github/workflows/dasp.yml | 5 ----- CHANGELOG.md | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/dasp.yml b/.github/workflows/dasp.yml index 5a865b72..30ac1d8e 100644 --- a/.github/workflows/dasp.yml +++ b/.github/workflows/dasp.yml @@ -159,11 +159,6 @@ jobs: with: command: test args: --manifest-path dasp/Cargo.toml --no-default-features --features "all-no-std" --verbose - - name: cargo test dasp_filter (all features no std) - uses: actions-rs/cargo@v1 - with: - command: test - args: --manifest-path dasp_filter/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: diff --git a/CHANGELOG.md b/CHANGELOG.md index bd4b39ff..1b2e26ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Renamed `window-hanning` to `window-hann` +- Add --- From 5a48a4c0c334e047e1f7dd478edb4108591475c6 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Sun, 17 Jan 2021 18:00:09 -0800 Subject: [PATCH 22/24] Update `CHANGELOG` --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2e26ff..7a9d531a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Unreleased -- Renamed `window-hanning` to `window-hann` -- Add +- Renamed `window-hanning` to `window-hann`. +- Add `dasp_filter` crate (#145). + - Allows for filtering of samples using a digital biquad filter. + - Add `filter` feature gate to `dasp_signal`. + - Add `signal-filter` feature gate to `dasp`. --- From bcef414602831a616d8a74bc0defd3f612e8df17 Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Sun, 17 Jan 2021 22:26:50 -0800 Subject: [PATCH 23/24] Add gated `pub use dasp_filter as filter` to `dasp` top-level lib --- dasp/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index 191dc7f0..ea643e67 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -106,6 +106,9 @@ pub use dasp_envelope as envelope; #[doc(inline)] pub use dasp_frame::{self as frame, Frame}; +#[cfg(feature = "filter")] +#[doc(inline)] +pub use dasp_filter as filter; // TODO: Remove `std` requirement once `dasp_graph` gains `no_std` support. #[cfg(all(feature = "graph", feature = "std"))] #[doc(inline)] From cfdf077aff913b09c15124b7eac528a78a890efc Mon Sep 17 00:00:00 2001 From: Mark LeMoine Date: Sun, 17 Jan 2021 22:30:58 -0800 Subject: [PATCH 24/24] Fix rustfmt check --- dasp/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dasp/src/lib.rs b/dasp/src/lib.rs index ea643e67..535ade1f 100644 --- a/dasp/src/lib.rs +++ b/dasp/src/lib.rs @@ -104,11 +104,11 @@ #[cfg(feature = "envelope")] #[doc(inline)] pub use dasp_envelope as envelope; -#[doc(inline)] -pub use dasp_frame::{self as frame, Frame}; #[cfg(feature = "filter")] #[doc(inline)] pub use dasp_filter as filter; +#[doc(inline)] +pub use dasp_frame::{self as frame, Frame}; // TODO: Remove `std` requirement once `dasp_graph` gains `no_std` support. #[cfg(all(feature = "graph", feature = "std"))] #[doc(inline)]