diff --git a/CHANGES.md b/CHANGES.md index 04fc3b9..60ba73a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ - `sine_phase`, `ramp_phase` and `ramp_hz_phase` opcodes were removed: there is a new builder notation for setting the initial phase, for example, `sine().phase(0.0)`. - New builder notation for setting noise generator seed, for example, `noise().seed(1)`. +- New opcode `biquad_bank()`. +- `BiquadBank` parameters for channel `i` are now set with the syntax `Setting::biquad(...).index(i)`. ### Version 0.20 diff --git a/README.md b/README.md index 3b38c39..536f96c 100644 --- a/README.md +++ b/README.md @@ -651,8 +651,8 @@ The following table lists the oscillator opcodes. The wavetable oscillator is [bandlimited](https://en.wikipedia.org/wiki/Bandlimiting) with pristine quality. -However, unlike the other types it allocates memory in the form of static wavetables. -The DSF oscillator has similar quality but is somewhat expensive to evaluate. +However, unlike the other types it allocates memory in the form of global wavetables. +The [DSF](https://ccrma.stanford.edu/files/papers/stanm5.pdf) oscillator has similar quality but is somewhat expensive to evaluate. The PolyBLEP oscillator is a fast approximation with fair quality. ## Working With Waves @@ -952,6 +952,7 @@ The following table summarizes the available settings. | `bandpass_hz` | `center_q` | | `bell_hz` | `center_q_gain` | | `biquad` | `biquad` to set biquad coefficients | +| `biquad_bank` | `biquad(a1, a2, b0, b1, b2).index(i)` to set channel `i` coefficients | | `butterpass_hz` | `center` | | `constant` | `value` to set scalar value on all channels | | `dbell_hz` | `center_q_gain` | @@ -1121,6 +1122,7 @@ The type parameters in the table refer to the hacker preludes. | `bell_hz(f, q, gain)` | 1 | 1 | Peaking filter (2nd order) centered at `f` Hz with Q `q` and amplitude gain `gain`. | | `bell_q(q, gain)` | 2 (audio, frequency) | 1 | Peaking filter (2nd order) with Q `q` and amplitude gain `gain`. | | `biquad(a1, a2, b0, b1, b2)` | 1 | 1 | Arbitrary [biquad filter](https://en.wikipedia.org/wiki/Digital_biquad_filter) with coefficients in normalized form. | +| `biquad_bank()` | 4/8 | 4/8 | Bank of SIMD accelerated biquad filters with 4 channels in double precision or 8 channels in single precision. | | `brown()` | - | 1 | [Brown](https://en.wikipedia.org/wiki/Brownian_noise) noise. | | `branch(x, y)` | `x = y` | `x + y` | Branch into `x` and `y`. Identical with `x ^ y`. | | `branchf::(f)`| `f` | `U * f` | Branch into `U` nodes from fractional generator `f`, e.g., `\| x \| resonator_hz(xerp(20.0, 20_000.0, x), xerp(5.0, 5_000.0, x))`. | diff --git a/src/adsr.rs b/src/adsr.rs index 0c28532..8cec74d 100644 --- a/src/adsr.rs +++ b/src/adsr.rs @@ -16,7 +16,7 @@ //! an `adsr_live()` envelope. use super::prelude::{clamp01, delerp, envelope2, lerp, shared, var, An, EnvelopeIn, Frame, U1}; -use super::Float; +use super::Real; pub fn adsr_live( attack: f32, @@ -51,7 +51,7 @@ pub fn adsr_live( }) } -fn ads(attack: F, decay: F, sustain: F, time: F) -> F { +fn ads(attack: F, decay: F, sustain: F, time: F) -> F { if time < attack { lerp(F::from_f64(0.0), F::from_f64(1.0), time / attack) } else { diff --git a/src/audionode.rs b/src/audionode.rs index 818cd2c..c02518e 100644 --- a/src/audionode.rs +++ b/src/audionode.rs @@ -9,16 +9,6 @@ use super::*; use core::marker::PhantomData; use num_complex::Complex64; use numeric_array::typenum::*; -use numeric_array::{ArrayLength, NumericArray}; - -/// Type-level integer. These are notated as `U0`, `U1`... -pub trait Size: ArrayLength + Sync + Send + Clone {} - -impl Size for A {} - -/// Frames are arrays with a static size used to transport audio data -/// between `AudioNode` instances. -pub type Frame = NumericArray; /* Order of type arguments in nodes: diff --git a/src/biquad.rs b/src/biquad.rs index 8b0d0b7..11929b1 100644 --- a/src/biquad.rs +++ b/src/biquad.rs @@ -24,13 +24,13 @@ pub struct BiquadCoefs { pub b2: F, } -impl BiquadCoefs { +impl BiquadCoefs { /// Return settings for a Butterworth lowpass filter. /// Sample rate is in Hz. /// Cutoff is the -3 dB point of the filter in Hz. #[inline] pub fn butter_lowpass(sample_rate: F, cutoff: F) -> Self { - let c = F::from_f64; + let c = F::from_f32; let f: F = tan(cutoff * F::PI / sample_rate); let a0r: F = c(1.0) / (c(1.0) + F::SQRT_2 * f + f * f); let a1: F = (c(2.0) * f * f - c(2.0)) * a0r; @@ -46,7 +46,7 @@ impl BiquadCoefs { /// The overall gain of the filter is independent of bandwidth. #[inline] pub fn resonator(sample_rate: F, center: F, q: F) -> Self { - let c = F::from_f64; + let c = F::from_f32; let r: F = exp(-F::PI * center / (q * sample_rate)); let a1: F = c(-2.0) * r * cos(F::TAU * center / sample_rate); let a2: F = r * r; @@ -60,7 +60,7 @@ impl BiquadCoefs { /// Sample rate and cutoff frequency are in Hz. #[inline] pub fn lowpass(sample_rate: F, cutoff: F, q: F) -> Self { - let c = F::from_f64; + let c = F::from_f32; let omega = F::TAU * cutoff / sample_rate; let alpha = sin(omega) / (c(2.0) * q); let beta = cos(omega); @@ -77,7 +77,7 @@ impl BiquadCoefs { /// Sample rate and cutoff frequency are in Hz. #[inline] pub fn highpass(sample_rate: F, cutoff: F, q: F) -> Self { - let c = F::from_f64; + let c = F::from_f32; let omega = F::TAU * cutoff / sample_rate; let alpha = sin(omega) / (c(2.0) * q); let beta = cos(omega); @@ -95,7 +95,7 @@ impl BiquadCoefs { /// Gain is amplitude gain (`gain` > 0). #[inline] pub fn bell(sample_rate: F, center: F, q: F, gain: F) -> Self { - let c = F::from_f64; + let c = F::from_f32; let omega = F::TAU * center / sample_rate; let alpha = sin(omega) / (c(2.0) * q); let beta = cos(omega); @@ -129,7 +129,7 @@ impl BiquadCoefs { } /// 2nd order IIR filter implemented in normalized Direct Form I. -/// - Setting: coefficients as tuple Parameter::Biquad(a1, a2, b0, b1, b2). +/// - Setting: coefficients as tuple `Setting::biquad(a1, a2, b0, b1, b2)`. /// - Input 0: input signal. /// - Output 0: filtered signal. #[derive(Default, Clone)] @@ -142,7 +142,7 @@ pub struct Biquad { sample_rate: f64, } -impl Biquad { +impl Biquad { pub fn new() -> Self { Self { sample_rate: DEFAULT_SR, @@ -164,7 +164,7 @@ impl Biquad { } } -impl AudioNode for Biquad { +impl AudioNode for Biquad { const ID: u64 = 15; type Inputs = typenum::U1; type Outputs = typenum::U1; diff --git a/src/biquad_bank.rs b/src/biquad_bank.rs index 4319181..7989fbb 100644 --- a/src/biquad_bank.rs +++ b/src/biquad_bank.rs @@ -1,221 +1,21 @@ //! Bank of parallel biquad filters with SIMD acceleration. -use core::marker::PhantomData; -use core::ops::Neg; -use hacker::Parameter; -use wide::{f32x8, f64x4}; - use super::audionode::*; -use super::prelude::{U4, U8}; +use super::biquad::*; +use super::setting::*; +use super::signal::*; use super::*; -use crate::setting::Setting; -use numeric_array::ArrayLength; - -pub trait Realx: Num + Sized + Neg { - const PI: Self; - const TAU: Self; - const SQRT_2: Self; - fn tan(self) -> Self; - fn exp(self) -> Self; - fn cos(self) -> Self; - fn sqrt(self) -> Self; - fn reduce_add(self) -> f32; - fn from_frame(frame: &Frame) -> Self; - fn to_frame(self) -> Frame; - fn set(&mut self, index: usize, value: f32); -} - -impl Realx for f32x8 { - const PI: Self = f32x8::PI; - const TAU: Self = f32x8::TAU; - const SQRT_2: Self = f32x8::SQRT_2; - - #[inline(always)] - fn tan(self) -> Self { - f32x8::tan(self) - } - #[inline(always)] - fn exp(self) -> Self { - f32x8::exp(self) - } - #[inline(always)] - fn cos(self) -> Self { - f32x8::cos(self) - } - #[inline(always)] - fn sqrt(self) -> Self { - f32x8::sqrt(self) - } - #[inline(always)] - fn reduce_add(self) -> f32 { - f32x8::reduce_add(self) - } - #[inline(always)] - fn from_frame(frame: &Frame) -> Self { - f32x8::new((*frame.as_array()).into()) - } - #[inline(always)] - fn to_frame(self) -> Frame { - f32x8::to_array(self).into() - } - #[inline(always)] - fn set(&mut self, index: usize, value: f32) { - self.as_array_mut()[index] = value; - } -} - -impl Realx for f64x4 { - const PI: Self = f64x4::PI; - const TAU: Self = f64x4::TAU; - const SQRT_2: Self = f64x4::SQRT_2; - - #[inline(always)] - fn tan(self) -> Self { - f64x4::tan(self) - } - #[inline(always)] - fn exp(self) -> Self { - f64x4::exp(self) - } - #[inline(always)] - fn cos(self) -> Self { - f64x4::cos(self) - } - #[inline(always)] - fn sqrt(self) -> Self { - f64x4::sqrt(self) - } - #[inline(always)] - fn reduce_add(self) -> f32 { - f64x4::reduce_add(self) as f32 - } - #[inline(always)] - fn from_frame(frame: &Frame) -> Self { - let array = frame.as_array(); - let f64_array = [ - f64::from(array[0]), - f64::from(array[1]), - f64::from(array[2]), - f64::from(array[3]), - ]; - f64x4::new(f64_array) - } - #[inline(always)] - fn to_frame(self) -> Frame { - let array_f64: [f64; 4] = f64x4::to_array(self); - let array_f32: [f32; 4] = array_f64.map(|x| x as f32); - array_f32.into() - } - #[inline(always)] - fn set(&mut self, index: usize, value: f32) { - self.as_array_mut()[index] = value as f64; - } -} - -/// BiquadBank coefficients in normalized form using SIMD. -#[derive(Copy, Clone, Debug, Default)] -pub struct BiquadCoefsBank -where - F: Realx, - Size: ArrayLength, -{ - pub a1: F, - pub a2: F, - pub b0: F, - pub b1: F, - pub b2: F, - _marker: PhantomData, -} - -impl BiquadCoefsBank -where - F: Realx, - Size: ArrayLength, -{ - /// Return settings for a Butterworth lowpass filter-bank. - /// Sample rate is in Hz. - /// Cutoff is the -3 dB point of the filter in Hz. - #[inline] - pub fn butter_lowpass(sample_rate: f32, cutoff: F) -> Self { - let c = F::from_f64; - let sr = F::from_f32(sample_rate); - let f: F = (cutoff * F::PI / sr).tan(); - let a0r: F = c(1.0) / (c(1.0) + F::SQRT_2 * f + f * f); - let a1: F = (c(2.0) * f * f - c(2.0)) * a0r; - let a2: F = (c(1.0) - F::SQRT_2 * f + f * f) * a0r; - let b0: F = f * f * a0r; - let b1: F = c(2.0) * b0; - let b2: F = b0; - Self { - a1, - a2, - b0, - b1, - b2, - _marker: PhantomData, - } - } - - /// Return settings for a constant-gain bandpass resonator-bank. - /// Sample rate and center frequency are in Hz. - /// The overall gain of the filter is independent of bandwidth. - #[inline] - pub fn resonator(sample_rate: f32, center: F, q: F) -> Self { - let c = F::from_f64; - let sr = F::from_f32(sample_rate); - let r: F = (-F::PI * center / (q * sr)).exp(); - let a1: F = c(-2.0) * r * (F::TAU * center / sr).cos(); - let a2: F = r * r; - let b0: F = (c(1.0) - r * r).sqrt() * c(0.5); - let b1: F = c(0.0); - let b2: F = -b0; - Self { - a1, - a2, - b0, - b1, - b2, - _marker: PhantomData, - } - } - - /// Arbitrary biquad. - #[inline] - pub fn arbitrary(a1: F, a2: F, b0: F, b1: F, b2: F) -> Self { - Self { - a1, - a2, - b0, - b1, - b2, - _marker: PhantomData, - } - } - - ///// Frequency response at frequency `omega` expressed as fraction of sampling rate. - //pub fn response(&self, omega: f64) -> Complex64 { - // let z1 = Complex64::from_polar(1.0, -f64::TAU * omega); - // let z2 = z1 * z1; - // /// Complex64 with real component `x` and imaginary component zero. - // fn re(x: T) -> Complex64 { - // Complex64::new(x.to_f64(), 0.0) - // } - // (re(self.b0) + re(self.b1) * z1 + re(self.b2) * z2) - // / (re(1.0) + re(self.a1) * z1 + re(self.a2) * z2) - //} -} /// 2nd order IIR filter bank implemented in normalized Direct Form I and SIMD. -/// - Setting: coefficients as tuple Parameter::BiquadBank(a1, a2, b0, b1, b2). -/// - Input 0: input signal. -/// - Output 0: filtered signal. +/// - Setting channel `i` coefficients: `Setting::biquad(a1, a2, b0, b1, b2).index(i)`. +/// - Inputs: input signals. +/// - Outputs: filtered signals. #[derive(Default, Clone)] -pub struct BiquadBank +pub struct BiquadBank where - F: Realx, - Size: ArrayLength + Sync + Send, + F: Float, { - coefs: BiquadCoefsBank, + coefs: BiquadCoefs, x1: F, x2: F, y1: F, @@ -223,10 +23,9 @@ where sample_rate: f64, } -impl BiquadBank +impl BiquadBank where - F: Realx, - Size: ArrayLength + Sync + Send, + F: Float, { pub fn new() -> Self { Self { @@ -235,7 +34,7 @@ where } } - pub fn with_coefs(coefs: BiquadCoefsBank) -> Self { + pub fn with_coefs(coefs: BiquadCoefs) -> Self { Self { coefs, sample_rate: DEFAULT_SR, @@ -243,23 +42,22 @@ where } } - pub fn coefs(&self) -> &BiquadCoefsBank { + pub fn coefs(&self) -> &BiquadCoefs { &self.coefs } - pub fn set_coefs(&mut self, coefs: BiquadCoefsBank) { + pub fn set_coefs(&mut self, coefs: BiquadCoefs) { self.coefs = coefs; } } -impl AudioNode for BiquadBank +impl AudioNode for BiquadBank where - F: Realx, - Size: ArrayLength + Sync + Send, + F: Float, { - const ID: u64 = 15; - type Inputs = Size; - type Outputs = Size; + const ID: u64 = 98; + type Inputs = F::Size; + type Outputs = F::Size; fn reset(&mut self) { self.x1 = F::zero(); @@ -286,24 +84,34 @@ where } fn set(&mut self, setting: Setting) { - if let Parameter::BiquadBank(index, a1, a2, b0, b1, b2) = setting.parameter() { - let mut coefs = self.coefs; - coefs.a1.set(*index, *a1); - coefs.a2.set(*index, *a2); - coefs.b0.set(*index, *b0); - coefs.b1.set(*index, *b1); - coefs.b2.set(*index, *b2); + if let Address::Index(index) = setting.direction() { + if let Parameter::Biquad(a1, a2, b0, b1, b2) = setting.parameter() { + self.coefs.a1.set(index, *a1); + self.coefs.a2.set(index, *a2); + self.coefs.b0.set(index, *b0); + self.coefs.b1.set(index, *b1); + self.coefs.b2.set(index, *b2); + } } } - //fn route(&mut self, input: &SignalFrame, frequency: f64) -> SignalFrame { - // let mut output = SignalFrame::new(self.outputs()); - // output.set( - // 0, - // input.at(0).filter(0.0, |r| { - // r * self.coefs().response(frequency / self.sample_rate) - // }), - // ); - // output - //} + fn route(&mut self, input: &SignalFrame, frequency: f64) -> SignalFrame { + let mut output = SignalFrame::new(self.outputs()); + for i in 0..self.outputs() { + let coefs = BiquadCoefs::::arbitrary( + self.coefs.a1.get(i), + self.coefs.a2.get(i), + self.coefs.b0.get(i), + self.coefs.b1.get(i), + self.coefs.b2.get(i), + ); + output.set( + i, + input + .at(i) + .filter(0.0, |r| r * coefs.response(frequency / self.sample_rate)), + ); + } + output + } } diff --git a/src/buffer.rs b/src/buffer.rs index 3ff05d5..c9e5095 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,6 +1,5 @@ //! SIMD accelerated audio buffers for block processing. -use super::audionode::*; use super::*; extern crate alloc; use alloc::vec::Vec; diff --git a/src/envelope.rs b/src/envelope.rs index ebddec3..1d90ab0 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -15,7 +15,7 @@ use numeric_array::*; #[derive(Default, Clone)] pub struct Envelope where - F: Float, + F: Real, E: FnMut(F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -48,7 +48,7 @@ where impl Envelope where - F: Float, + F: Real, E: FnMut(F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -101,7 +101,7 @@ where impl AudioNode for Envelope where - F: Float, + F: Real, E: FnMut(F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -177,7 +177,7 @@ where #[derive(Default, Clone)] pub struct EnvelopeIn where - F: Float, + F: Real, E: FnMut(F, &Frame) -> R + Clone + Send + Sync, I: Size, R: ConstantFrame, @@ -212,7 +212,7 @@ where impl EnvelopeIn where - F: Float, + F: Real, E: FnMut(F, &Frame) -> R + Clone + Send + Sync, I: Size, R: ConstantFrame, @@ -273,7 +273,7 @@ where impl AudioNode for EnvelopeIn where - F: Float, + F: Real, E: FnMut(F, &Frame) -> R + Clone + Send + Sync, I: Size, R: ConstantFrame, diff --git a/src/filter.rs b/src/filter.rs index 9ece611..2e0e6bf 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -274,7 +274,7 @@ pub struct Allpole> { sample_rate: F, } -impl> Allpole { +impl> Allpole { /// Create new allpass filter. Initial `delay` is specified in samples. pub fn new(delay: F) -> Self { assert!(delay > F::zero()); @@ -296,7 +296,7 @@ impl> Allpole { } } -impl> AudioNode for Allpole { +impl> AudioNode for Allpole { const ID: u64 = 46; type Inputs = N; type Outputs = typenum::U1; diff --git a/src/hacker.rs b/src/hacker.rs index f988035..4055fdf 100644 --- a/src/hacker.rs +++ b/src/hacker.rs @@ -7,6 +7,7 @@ use alloc::sync::Arc; pub use super::audionode::*; pub use super::audiounit::*; pub use super::biquad::*; +pub use super::biquad_bank::*; pub use super::buffer::*; pub use super::combinator::*; pub use super::delay::*; @@ -2681,3 +2682,11 @@ pub fn poly_pulse() -> An> { pub fn poly_pulse_hz(f: f32, width: f32) -> An, PolyPulse>> { dc((f, width)) >> poly_pulse() } + +/// 4-channel SIMD accelerated biquad filter with independent settings for each channel. +/// - Setting channel `i` coefficients: `Setting::biquad(a1, a2, b0, b1, b2).index(i)`. +/// - Inputs 0-3: input signals. +/// - Outputs 0-3: filtered signals. +pub fn biquad_bank() -> An> { + An(BiquadBank::new()) +} diff --git a/src/hacker32.rs b/src/hacker32.rs index 4b8b273..782a786 100644 --- a/src/hacker32.rs +++ b/src/hacker32.rs @@ -7,6 +7,7 @@ use alloc::sync::Arc; pub use super::audionode::*; pub use super::audiounit::*; pub use super::biquad::*; +pub use super::biquad_bank::*; pub use super::buffer::*; pub use super::combinator::*; pub use super::delay::*; @@ -2681,3 +2682,11 @@ pub fn poly_pulse() -> An> { pub fn poly_pulse_hz(f: f32, width: f32) -> An, PolyPulse>> { dc((f, width)) >> poly_pulse() } + +/// 8-channel SIMD accelerated biquad filter with independent settings for each channel. +/// - Setting channel `i` coefficients: `Setting::biquad(a1, a2, b0, b1, b2).index(i)`. +/// - Inputs 0-7: input signals. +/// - Outputs 0-7: filtered signals. +pub fn biquad_bank() -> An> { + An(BiquadBank::new()) +} diff --git a/src/lib.rs b/src/lib.rs index 0e76177..4cbd519 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,24 @@ clippy::comparison_chain )] +use numeric_array::{ArrayLength, NumericArray}; +use typenum::{U1, U4, U8}; + use core::cmp::PartialEq; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use core::ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr}; +use std::marker::{Send, Sync}; + +use wide::{f32x8, f64x4, i32x8, u32x8}; + +/// Type-level integer. These are notated as `U0`, `U1`... +pub trait Size: ArrayLength + Sync + Send + Clone {} + +impl Size for A {} + +/// Frames are arrays with a static size used to transport audio data +/// between `AudioNode` instances. +pub type Frame = NumericArray; /// Default sample rate is 44.1 kHz. pub const DEFAULT_SR: f64 = 44_100.0; @@ -34,13 +49,13 @@ pub const MAX_BUFFER_SIZE: usize = 1 << MAX_BUFFER_LOG; /// Blocks are explicitly SIMD accelerated. This is the type of a SIMD element /// containing successive `f32` samples. -pub type F32x = wide::f32x8; +pub type F32x = f32x8; /// The 32-bit unsigned integer SIMD element corresponding to `F32x`. -pub type U32x = wide::u32x8; +pub type U32x = u32x8; /// The 32-bit signed integer SIMD element corresponding to `F32x`. -pub type I32x = wide::i32x8; +pub type I32x = i32x8; /// Right shift for converting from samples to SIMD elements. pub const SIMD_S: usize = 3; @@ -69,7 +84,7 @@ pub fn full_simd_items(samples: usize) -> usize { samples >> SIMD_S } -/// Number abstraction. +/// Number abstraction that is also defined for SIMD items. pub trait Num: Copy + Default @@ -86,6 +101,9 @@ pub trait Num: + DivAssign + PartialEq { + // The number of elements in the number. + type Size: ArrayLength + Send + Sync; + fn zero() -> Self; fn one() -> Self; fn new(x: i64) -> Self; @@ -106,6 +124,7 @@ pub trait Num: macro_rules! impl_signed_num { ( $($t:ty),* ) => { $( impl Num for $t { + type Size = U1; #[inline(always)] fn zero() -> Self { 0 } #[inline(always)] fn one() -> Self { 1 } #[inline(always)] fn new(x: i64) -> Self { x as Self } @@ -127,6 +146,7 @@ impl_signed_num! { i8, i16, i32, i64, i128, isize } macro_rules! impl_unsigned_num { ( $($t:ty),* ) => { $( impl Num for $t { + type Size = U1; #[inline(always)] fn zero() -> Self { 0 } #[inline(always)] fn one() -> Self { 1 } #[inline(always)] fn new(x: i64) -> Self { x as Self } @@ -146,6 +166,8 @@ macro_rules! impl_unsigned_num { impl_unsigned_num! { u8, u16, u32, u64, u128, usize } impl Num for f32 { + type Size = U1; + #[inline(always)] fn zero() -> Self { 0.0 @@ -201,6 +223,8 @@ impl Num for f32 { } impl Num for f64 { + type Size = U1; + #[inline(always)] fn zero() -> Self { 0.0 @@ -256,6 +280,8 @@ impl Num for f64 { } impl Num for F32x { + type Size = U8; + #[inline(always)] fn zero() -> Self { F32x::ZERO @@ -310,7 +336,9 @@ impl Num for F32x { } } -impl Num for wide::f64x4 { +impl Num for f64x4 { + type Size = U4; + #[inline(always)] fn zero() -> Self { wide::f64x4::ZERO @@ -392,8 +420,8 @@ macro_rules! impl_int { } impl_int! { i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize } -/// Float abstraction. -pub trait Float: Num + PartialOrd + Neg { +/// Float abstraction that also applies to SIMD items. +pub trait Float: Num + Neg { const PI: Self; const TAU: Self; const SQRT_2: Self; @@ -401,6 +429,16 @@ pub trait Float: Num + PartialOrd + Neg { fn to_f64(self) -> f64; fn to_f32(self) -> f32; fn to_i64(self) -> i64; + fn tan(self) -> Self; + fn exp(self) -> Self; + fn sin(self) -> Self; + fn cos(self) -> Self; + fn sqrt(self) -> Self; + fn reduce_add(self) -> f32; + fn from_frame(frame: &Frame) -> Self; + fn to_frame(self) -> Frame; + fn set(&mut self, index: usize, value: f32); + fn get(&self, index: usize) -> f32; } impl Float for f32 { @@ -427,6 +465,56 @@ impl Float for f32 { fn to_i64(self) -> i64 { self as i64 } + + #[inline(always)] + fn tan(self) -> Self { + self.tan() + } + + #[inline(always)] + fn exp(self) -> Self { + self.exp() + } + + #[inline(always)] + fn sin(self) -> Self { + self.sin() + } + + #[inline(always)] + fn cos(self) -> Self { + self.cos() + } + + #[inline(always)] + fn sqrt(self) -> Self { + self.sqrt() + } + + #[inline(always)] + fn reduce_add(self) -> f32 { + self + } + + #[inline(always)] + fn from_frame(frame: &Frame) -> Self { + frame[0] + } + + #[inline(always)] + fn to_frame(self) -> Frame { + [self].into() + } + + #[inline(always)] + fn set(&mut self, _index: usize, value: f32) { + *self = value; + } + + #[inline(always)] + fn get(&self, _index: usize) -> f32 { + *self + } } impl Float for f64 { @@ -453,6 +541,217 @@ impl Float for f64 { fn to_i64(self) -> i64 { self as i64 } + + #[inline(always)] + fn tan(self) -> Self { + self.tan() + } + + #[inline(always)] + fn exp(self) -> Self { + self.exp() + } + + #[inline(always)] + fn sin(self) -> Self { + self.sin() + } + + #[inline(always)] + fn cos(self) -> Self { + self.cos() + } + + #[inline(always)] + fn sqrt(self) -> Self { + self.sqrt() + } + + #[inline(always)] + fn reduce_add(self) -> f32 { + self as f32 + } + + #[inline(always)] + fn from_frame(frame: &Frame) -> Self { + frame[0] as f64 + } + + #[inline(always)] + fn to_frame(self) -> Frame { + [self as f32].into() + } + + #[inline(always)] + fn set(&mut self, _index: usize, value: f32) { + *self = value as f64; + } + + #[inline(always)] + fn get(&self, _index: usize) -> f32 { + *self as f32 + } +} + +impl Float for f32x8 { + const PI: Self = f32x8::PI; + const TAU: Self = f32x8::TAU; + const SQRT_2: Self = f32x8::SQRT_2; + + #[inline(always)] + fn from_float(x: T) -> Self { + Self::splat(x.to_f32()) + } + + #[inline(always)] + fn to_f64(self) -> f64 { + 0.0 + } + + #[inline(always)] + fn to_f32(self) -> f32 { + 0.0 + } + + #[inline(always)] + fn to_i64(self) -> i64 { + 0 + } + + #[inline(always)] + fn tan(self) -> Self { + f32x8::tan(self) + } + + #[inline(always)] + fn exp(self) -> Self { + f32x8::exp(self) + } + + #[inline(always)] + fn sin(self) -> Self { + f32x8::sin(self) + } + + #[inline(always)] + fn cos(self) -> Self { + f32x8::cos(self) + } + + #[inline(always)] + fn sqrt(self) -> Self { + f32x8::sqrt(self) + } + + #[inline(always)] + fn reduce_add(self) -> f32 { + f32x8::reduce_add(self) + } + + #[inline(always)] + fn from_frame(frame: &Frame) -> Self { + f32x8::new((*frame.as_array()).into()) + } + + #[inline(always)] + fn to_frame(self) -> Frame { + f32x8::to_array(self).into() + } + + #[inline(always)] + fn set(&mut self, index: usize, value: f32) { + self.as_array_mut()[index] = value; + } + + #[inline(always)] + fn get(&self, index: usize) -> f32 { + self.as_array_ref()[index] + } +} + +impl Float for f64x4 { + const PI: Self = f64x4::PI; + const TAU: Self = f64x4::TAU; + const SQRT_2: Self = f64x4::SQRT_2; + + #[inline(always)] + fn from_float(x: T) -> Self { + Self::splat(x.to_f64()) + } + + #[inline(always)] + fn to_f64(self) -> f64 { + 0.0 + } + + #[inline(always)] + fn to_f32(self) -> f32 { + 0.0 + } + + #[inline(always)] + fn to_i64(self) -> i64 { + 0 + } + + #[inline(always)] + fn tan(self) -> Self { + f64x4::tan(self) + } + + #[inline(always)] + fn exp(self) -> Self { + f64x4::exp(self) + } + + #[inline(always)] + fn sin(self) -> Self { + f64x4::sin(self) + } + + #[inline(always)] + fn cos(self) -> Self { + f64x4::cos(self) + } + + #[inline(always)] + fn sqrt(self) -> Self { + f64x4::sqrt(self) + } + + #[inline(always)] + fn reduce_add(self) -> f32 { + f64x4::reduce_add(self) as f32 + } + + #[inline(always)] + fn from_frame(frame: &Frame) -> Self { + let array = frame.as_array(); + let f64_array = [ + f64::from(array[0]), + f64::from(array[1]), + f64::from(array[2]), + f64::from(array[3]), + ]; + f64x4::new(f64_array) + } + + #[inline(always)] + fn to_frame(self) -> Frame { + let array_f64: [f64; 4] = f64x4::to_array(self); + let array_f32: [f32; 4] = array_f64.map(|x| x as f32); + array_f32.into() + } + + #[inline(always)] + fn set(&mut self, index: usize, value: f32) { + self.as_array_mut()[index] = value as f64; + } + + #[inline(always)] + fn get(&self, index: usize) -> f32 { + self.as_array_ref()[index] as f32 + } } /// Generic floating point conversion function. @@ -461,30 +760,17 @@ pub fn convert(x: T) -> U { U::from_float(x) } -/// Refined float abstraction. -pub trait Real: Float { - fn sqrt(self) -> Self; - fn exp(self) -> Self; +/// Refined float abstraction for scalars. +pub trait Real: Float + PartialOrd { fn exp2(self) -> Self; fn log(self) -> Self; fn log2(self) -> Self; fn log10(self) -> Self; - fn sin(self) -> Self; - fn cos(self) -> Self; - fn tan(self) -> Self; fn tanh(self) -> Self; fn atan(self) -> Self; } impl Real for f32 { - #[inline(always)] - fn sqrt(self) -> Self { - libm::sqrtf(self) - } - #[inline(always)] - fn exp(self) -> Self { - libm::expf(self) - } #[inline(always)] fn exp2(self) -> Self { libm::exp2f(self) @@ -502,18 +788,6 @@ impl Real for f32 { libm::log10f(self) } #[inline(always)] - fn sin(self) -> Self { - libm::sinf(self) - } - #[inline(always)] - fn cos(self) -> Self { - libm::cosf(self) - } - #[inline(always)] - fn tan(self) -> Self { - libm::tanf(self) - } - #[inline(always)] fn tanh(self) -> Self { libm::tanhf(self) } @@ -524,14 +798,6 @@ impl Real for f32 { } impl Real for f64 { - #[inline(always)] - fn sqrt(self) -> Self { - libm::sqrt(self) - } - #[inline(always)] - fn exp(self) -> Self { - libm::exp(self) - } #[inline(always)] fn exp2(self) -> Self { libm::exp2(self) @@ -549,18 +815,6 @@ impl Real for f64 { libm::log10(self) } #[inline(always)] - fn sin(self) -> Self { - libm::sin(self) - } - #[inline(always)] - fn cos(self) -> Self { - libm::cos(self) - } - #[inline(always)] - fn tan(self) -> Self { - libm::tan(self) - } - #[inline(always)] fn tanh(self) -> Self { libm::tanh(self) } diff --git a/src/math.rs b/src/math.rs index e1cb47e..d183ada 100644 --- a/src/math.rs +++ b/src/math.rs @@ -55,13 +55,13 @@ pub fn round(x: T) -> T { /// Square root function. #[inline] -pub fn sqrt(x: T) -> T { +pub fn sqrt(x: T) -> T { x.sqrt() } /// Exponential function. #[inline] -pub fn exp(x: T) -> T { +pub fn exp(x: T) -> T { x.exp() } @@ -97,19 +97,19 @@ pub fn log10(x: T) -> T { /// Sine function. #[inline] -pub fn sin(x: T) -> T { +pub fn sin(x: T) -> T { x.sin() } #[inline] /// Cosine function. -pub fn cos(x: T) -> T { +pub fn cos(x: T) -> T { x.cos() } /// Tangent function. #[inline] -pub fn tan(x: T) -> T { +pub fn tan(x: T) -> T { x.tan() } @@ -481,7 +481,7 @@ pub fn cos_hz(hz: T, t: T) -> T { /// assert_eq!(sqr_hz(1.0, 1.0), 1.0); /// ``` #[inline] -pub fn sqr_hz(hz: T, t: T) -> T { +pub fn sqr_hz(hz: T, t: T) -> T { let x = t * hz; let x = x - x.floor(); if x < T::from_f32(0.5) { @@ -647,7 +647,7 @@ where } } -impl SegmentInterpolator for (X, Y) +impl SegmentInterpolator for (X, Y) where X: SegmentInterpolator, Y: SegmentInterpolator, diff --git a/src/oscillator.rs b/src/oscillator.rs index 2c9f78a..539e8bf 100644 --- a/src/oscillator.rs +++ b/src/oscillator.rs @@ -509,7 +509,7 @@ impl AudioNode for Ramp { /// PolyBLEP function with phase `t` in 0...1 and phase increment `dt`. #[inline] -fn polyblep(t: F, dt: F) -> F { +fn polyblep(t: F, dt: F) -> F { if t < dt { let z = t / dt; z + z - z * z - F::one() @@ -526,14 +526,14 @@ fn polyblep(t: F, dt: F) -> F { /// - Input 0: frequency (Hz). /// - Output 0: saw waveform in -1...1. #[derive(Default, Clone)] -pub struct PolySaw { +pub struct PolySaw { phase: F, sample_duration: F, hash: u64, initial_phase: Option, } -impl PolySaw { +impl PolySaw { /// Create oscillator. pub fn new() -> Self { let mut osc = Self::default(); @@ -555,7 +555,7 @@ impl PolySaw { } } -impl AudioNode for PolySaw { +impl AudioNode for PolySaw { const ID: u64 = 95; type Inputs = typenum::U1; type Outputs = typenum::U1; @@ -602,14 +602,14 @@ impl AudioNode for PolySaw { /// - Input 0: frequency (Hz). /// - Output 0: square waveform in -1...1. #[derive(Default, Clone)] -pub struct PolySquare { +pub struct PolySquare { phase: F, sample_duration: F, hash: u64, initial_phase: Option, } -impl PolySquare { +impl PolySquare { /// Create oscillator. pub fn new() -> Self { let mut osc = Self::default(); @@ -631,7 +631,7 @@ impl PolySquare { } } -impl AudioNode for PolySquare { +impl AudioNode for PolySquare { const ID: u64 = 96; type Inputs = typenum::U1; type Outputs = typenum::U1; @@ -685,14 +685,14 @@ impl AudioNode for PolySquare { /// - Input 1: pulse width in 0...1. /// - Output 0: pulse waveform in -1...1. #[derive(Default, Clone)] -pub struct PolyPulse { +pub struct PolyPulse { phase: F, sample_duration: F, hash: u64, initial_phase: Option, } -impl PolyPulse { +impl PolyPulse { /// Create oscillator. pub fn new() -> Self { let mut osc = Self::default(); @@ -714,7 +714,7 @@ impl PolyPulse { } } -impl AudioNode for PolyPulse { +impl AudioNode for PolyPulse { const ID: u64 = 97; type Inputs = typenum::U2; type Outputs = typenum::U1; diff --git a/src/prelude.rs b/src/prelude.rs index 952fd30..5492e32 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -477,14 +477,14 @@ pub fn lowpole_hz(cutoff: F) -> An> { /// - Input 0: audio /// - Input 1: delay in samples /// - Output 0: filtered audio -pub fn allpole() -> An> { +pub fn allpole() -> An> { An(Allpole::new(F::new(1))) } /// Allpass filter (1st order) with `delay` (`delay` > 0) in samples at DC. /// - Input 0: audio /// - Output 0: filtered audio -pub fn allpole_delay(delay: F) -> An> { +pub fn allpole_delay(delay: F) -> An> { An(Allpole::new(delay)) } @@ -576,7 +576,7 @@ pub fn moog_hz(frequency: F, q: F) -> An> { /// ``` pub fn envelope(f: E) -> An> where - F: Float, + F: Real, E: FnMut(F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -600,7 +600,7 @@ where /// ``` pub fn lfo(f: E) -> An> where - F: Float, + F: Real, E: FnMut(F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -624,7 +624,7 @@ pub fn envelope2( mut f: E, ) -> An) -> R + Clone, U1, R>> where - F: Float, + F: Real, E: FnMut(F, F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -651,7 +651,7 @@ pub fn lfo2( mut f: E, ) -> An) -> R + Clone, U1, R>> where - F: Float, + F: Real, E: FnMut(F, F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -672,7 +672,7 @@ pub fn envelope3( mut f: E, ) -> An) -> R + Clone, U2, R>> where - F: Float, + F: Real, E: FnMut(F, F, F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -702,7 +702,7 @@ pub fn lfo3( mut f: E, ) -> An) -> R + Clone, U2, R>> where - F: Float, + F: Real, E: FnMut(F, F, F) -> R + Clone + Send + Sync, R: ConstantFrame, R::Size: Size + Size, @@ -721,7 +721,7 @@ where /// - Output(s): envelope linearly interpolated from samples at 2 ms intervals (average). pub fn envelope_in(f: E) -> An> where - F: Float, + F: Real, E: FnMut(F, &Frame) -> R + Clone + Send + Sync, I: Size, R: ConstantFrame, @@ -738,7 +738,7 @@ where /// - Output(s): envelope linearly interpolated from samples at 2 ms intervals (average). pub fn lfo_in(f: E) -> An> where - F: Float, + F: Real, E: FnMut(F, &Frame) -> R + Clone + Send + Sync, I: Size, R: ConstantFrame, @@ -3088,14 +3088,14 @@ pub fn fresonator_hz( /// A fast, fairly bandlimited saw wave algorithm. /// - Input 0: frequency (Hz) /// - Output 0: saw wave -pub fn poly_saw() -> An> { +pub fn poly_saw() -> An> { An(PolySaw::new()) } /// PolyBLEP saw wave oscillator at `f` Hz. /// A fast, fairly bandlimited saw wave algorithm. /// - Output 0: saw wave -pub fn poly_saw_hz(f: f32) -> An, PolySaw>> { +pub fn poly_saw_hz(f: f32) -> An, PolySaw>> { dc(f) >> poly_saw() } @@ -3103,14 +3103,14 @@ pub fn poly_saw_hz(f: f32) -> An, PolySaw>> { /// A fast, fairly bandlimited square wave algorithm. /// - Input 0: frequency (Hz) /// - Output 0: square wave -pub fn poly_square() -> An> { +pub fn poly_square() -> An> { An(PolySquare::new()) } /// PolyBLEP square wave oscillator at `f` Hz. /// A fast, fairly bandlimited square wave algorithm. /// - Output 0: square wave -pub fn poly_square_hz(f: f32) -> An, PolySquare>> { +pub fn poly_square_hz(f: f32) -> An, PolySquare>> { dc(f) >> poly_square() } @@ -3119,13 +3119,13 @@ pub fn poly_square_hz(f: f32) -> An, PolySquare>> /// - Input 0: frequency (Hz) /// - Input 1: pulse width in 0...1 /// - Output 0: pulse wave -pub fn poly_pulse() -> An> { +pub fn poly_pulse() -> An> { An(PolyPulse::new()) } /// PolyBLEP pulse wave oscillator at `f` Hz with pulse `width` in 0...1. /// A fast, fairly bandlimited pulse wave algorithm. /// - Output 0: pulse wave -pub fn poly_pulse_hz(f: f32, width: f32) -> An, PolyPulse>> { +pub fn poly_pulse_hz(f: f32, width: f32) -> An, PolyPulse>> { dc((f, width)) >> poly_pulse() } diff --git a/src/ring.rs b/src/ring.rs index 55053f5..5f9ffe3 100644 --- a/src/ring.rs +++ b/src/ring.rs @@ -4,6 +4,7 @@ use super::audionode::*; use super::combinator::An; use super::signal::*; use super::typenum::*; +use super::*; use thingbuf::mpsc::{channel, Receiver, Sender}; pub struct Ring> { diff --git a/src/setting.rs b/src/setting.rs index 77ffa5a..3985830 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -6,6 +6,7 @@ use super::combinator::*; use super::math::*; use super::net::NodeId; use super::signal::*; +use super::*; pub use thingbuf::mpsc::errors::TrySendError; use thingbuf::mpsc::{channel, Receiver, Sender}; use tinyvec::ArrayVec; @@ -28,11 +29,6 @@ pub enum Parameter { Coefficient(f32), /// Set biquad parameters `(a1, a2, b0, b1, b2)`. Biquad(f32, f32, f32, f32, f32), - /// Set biquadbank parameters `(index, a1, a2, b0, b1, b2)`. - /// `index` range: - /// - 0-3 for a f64x4 based BiquadBank - /// - 0-7 for a f32x8 based BiquadBank - BiquadBank(usize, f32, f32, f32, f32, f32), /// Set delay. Delay(f32), /// Set response time. diff --git a/tests/test_basic.rs b/tests/test_basic.rs index 341be4a..1bd961e 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -306,6 +306,11 @@ fn test_basic() { dc((660.0, 0.1)) >> poly_pulse().phase(0.75) | poly_pulse_hz(6600.0, 0.9).phase(0.9), ); + let mut biq = biquad_bank(); + biq.set(Setting::biquad(0.0, 0.0, 0.2, 0.2, 0.2).index(0)); + biq.set(Setting::biquad(0.2, 0.2, 0.1, 0.3, 0.5).index(1)); + check_wave((noise() | noise() | zero() | zero()) >> biq >> (pass() | pass() | sink() | sink())); + let dc42 = Net::wrap(Box::new(dc(42.))); let dcs = dc42.clone() | dc42; let reverb = Net::wrap(Box::new(reverb_stereo(40., 5., 1.))); diff --git a/tests/test_flow.rs b/tests/test_flow.rs index a30663c..8d02ef9 100644 --- a/tests/test_flow.rs +++ b/tests/test_flow.rs @@ -167,6 +167,14 @@ fn test_responses() { test_response((pass() | dc(1.0)) >> rotate(0.5, 1.0) >> (pass() | sink())); test_response((dc(2.0) | pass()) >> rotate(-0.1, 0.5) >> (pass() | sink())); + let mut biq = biquad_bank(); + biq.set(Setting::biquad(0.05, 0.1, 0.3, 0.1, 0.15).index(3)); + test_response( + (multizero::() | pass() | multizero::()) + >> biq + >> (multisink::() | pass() | multisink::()), + ); + let mut net1 = Net::new(1, 1); net1.chain(Box::new(lowpole_hz(1500.0))); test_response(net1);