Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add filtering support and subcrate #148

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
460c7b2
Create `dasp_filter` subcrate and skeleton module
linclelinkpart5 Jan 13, 2021
4cf170f
Plug in `dasp_filter` into top level `dasp/Cargo.toml`
linclelinkpart5 Jan 13, 2021
864b971
Add `Coefficients` struct to `dasp_filter`
linclelinkpart5 Jan 14, 2021
2259fcc
Add `Biquad` struct to `dasp_filter`
linclelinkpart5 Jan 14, 2021
5dd4d72
Add `Biquad::new`
linclelinkpart5 Jan 14, 2021
ac16c43
Add `Biquad::apply`
linclelinkpart5 Jan 14, 2021
a13aa84
Simplfy type parameter usage. Buff up implementation of `Biquad::apply`
linclelinkpart5 Jan 14, 2021
619c222
Add doc comment to `Biquad::apply`
linclelinkpart5 Jan 14, 2021
83b352d
Cleanup `Coefficient`/`Biquad` field names
linclelinkpart5 Jan 14, 2021
1116064
Rename `Coefficient` field names. Add doctest for `Biquad::apply`
linclelinkpart5 Jan 14, 2021
d201514
Add `From<Coefficients>` impl for `Biquad`
linclelinkpart5 Jan 14, 2021
8d4c766
Add skeleton feature to `dasp_signal` for filtered signals. Add featu…
linclelinkpart5 Jan 14, 2021
4b1e285
Add and plug in `dasp_signal::filter` submodule
linclelinkpart5 Jan 14, 2021
77fbcbc
Create `SignalFilter` trait and `FilteredSignal` struct
linclelinkpart5 Jan 14, 2021
0d15bd9
Begin `Signal` impl for `FilteredSignal`, need to fix `dasp_filter` c…
linclelinkpart5 Jan 14, 2021
ef9426a
Fix trait bounds for `SignalFilter`/`FilteredSignal`
linclelinkpart5 Jan 14, 2021
22558ea
Add doctest for `SignalFilter`. Add impl of `SignalFilter` for all `S…
linclelinkpart5 Jan 15, 2021
7cc935b
Make format changes
linclelinkpart5 Jan 15, 2021
f3d7051
Use `Duplex` trait bound instead of `FromSample + ToSample`
linclelinkpart5 Jan 15, 2021
777ef31
Update `.github/workflows`
linclelinkpart5 Jan 18, 2021
f476596
Remove `dasp_filter` from "all-no-std" section in `.github/workflows`
linclelinkpart5 Jan 18, 2021
5a48a4c
Update `CHANGELOG`
linclelinkpart5 Jan 18, 2021
bcef414
Add gated `pub use dasp_filter as filter` to `dasp` top-level lib
linclelinkpart5 Jan 18, 2021
cfdf077
Fix rustfmt check
linclelinkpart5 Jan 18, 2021
e2198b4
Merge branch 'master' into #145 to include changes from #146
linclelinkpart5 Jan 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/dasp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -270,6 +275,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
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Unreleased

- Renamed `window-hanning` to `window-hann`
- 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`.

---

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"dasp",
"dasp_envelope",
"dasp_filter",
"dasp_frame",
"dasp_graph",
"dasp_interpolate",
Expand Down
6 changes: 6 additions & 0 deletions dasp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -36,6 +37,7 @@ all-no-std = [
"envelope",
"envelope-peak",
"envelope-rms",
"filter",
"interpolate",
"interpolate-floor",
"interpolate-linear",
Expand All @@ -47,6 +49,7 @@ all-no-std = [
"signal-boxed",
"signal-bus",
"signal-envelope",
"signal-filter",
"signal-rms",
"signal-window",
"signal-window-hann",
Expand All @@ -59,6 +62,7 @@ all-no-std = [
]
std = [
"dasp_envelope/std",
"dasp_filter/std",
"dasp_frame/std",
"dasp_interpolate/std",
"dasp_peak/std",
Expand All @@ -72,6 +76,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"]
Expand All @@ -90,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"]
Expand Down
3 changes: 3 additions & 0 deletions dasp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
#[cfg(feature = "envelope")]
#[doc(inline)]
pub use dasp_envelope as envelope;
#[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.
Expand Down
25 changes: 25 additions & 0 deletions dasp_filter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "dasp_filter"
description = "Filters for digital audio signals."
version = "0.11.0"
authors = ["mitchmindtree <[email protected]>", "Mark LeMoine <[email protected]>"]
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
117 changes: 117 additions & 0 deletions dasp_filter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(core_intrinsics))]

use dasp_frame::Frame;
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,
/// and thus not included.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Coefficients<S>
where
S: FloatSample,
{
// Transfer function numerator coefficients.
pub b0: S,
pub b1: S,
pub b2: S,

// Transfer function denominator coefficients.
pub a1: S,
pub a2: S,
}

/// An implementation of a digital biquad filter, using the Direct Form 2
/// Transposed (DF2T) representation.
pub struct Biquad<F>
where
F: Frame,
F::Sample: FloatSample,
{
pub coeff: Coefficients<F::Sample>,

// Since biquad filters are second-order, we require two historical buffers.
// This state is updated each time the filter is applied to a `Frame`.
t0: F,
t1: F,
}

impl<F> Biquad<F>
where
F: Frame,
F::Sample: FloatSample,
{
pub fn new(coeff: Coefficients<F::Sample>) -> Self {
Self {
coeff,
t0: F::EQUILIBRIUM,
t1: F::EQUILIBRIUM,
}
}

/// 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<I>(&mut self, input: I) -> I
where
I: Frame<NumChannels = F::NumChannels>,
I::Sample: Duplex<F::Sample>,
{
// Convert into floating point representation.
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);

// This is the new filtered `Frame`.
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_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_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_)
}
}

impl<F> From<Coefficients<F::Sample>> for Biquad<F>
where
F: Frame,
F::Sample: FloatSample,
{
// Same as `new()`, but adding this for the blanket `Into` impl.
fn from(coeff: Coefficients<F::Sample>) -> Self {
Self::new(coeff)
}
}
4 changes: 4 additions & 0 deletions dasp_signal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -32,13 +33,15 @@ all-no-std = [
"boxed",
"bus",
"envelope",
"filter",
"rms",
"window",
"window-hann",
"window-rectangle",
]
std = [
"dasp_envelope/std",
"dasp_filter/std",
"dasp_frame/std",
"dasp_interpolate/std",
"dasp_peak/std",
Expand All @@ -50,6 +53,7 @@ std = [
boxed = []
bus = []
envelope = ["dasp_envelope"]
filter = ["dasp_filter"]
rms = ["dasp_rms"]
window = ["dasp_window"]
window-hann = ["dasp_window/hann"]
Expand Down
96 changes: 96 additions & 0 deletions dasp_signal/src/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! 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;
use dasp_frame::Frame;
use dasp_sample::{FromSample, Sample};

/// 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<_>>(),
/// 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 {
fn filtered(
self,
coeff: filter::Coefficients<<<Self::Frame as Frame>::Sample as Sample>::Float>,
) -> FilteredSignal<Self>
where
Self: Sized,
<Self::Frame as Frame>::Sample:
FromSample<<<Self::Frame as Frame>::Sample as Sample>::Float>,
{
let biquad = filter::Biquad::from(coeff);

FilteredSignal {
signal: self,
biquad,
}
}
}

/// 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<S>
where
S: Signal,
<S::Frame as Frame>::Sample: FromSample<<<S::Frame as Frame>::Sample as Sample>::Float>,
{
signal: S,
biquad: filter::Biquad<<S::Frame as Frame>::Float>,
}

impl<S> Signal for FilteredSignal<S>
where
S: Signal,
<S::Frame as Frame>::Sample: FromSample<<<S::Frame as Frame>::Sample as Sample>::Float>,
{
// Output is the same type as the input.
type Frame = S::Frame;

fn next(&mut self) -> Self::Frame {
self.biquad.apply(self.signal.next())
}

fn is_exhausted(&self) -> bool {
self.signal.is_exhausted()
}
}

// Impl this for all `Signal`s.
impl<T> SignalFilter for T where T: Signal {}
2 changes: 2 additions & 0 deletions dasp_signal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down