Skip to content

Commit

Permalink
More biquad modes.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Aug 28, 2024
1 parent a3fc213 commit 5f0c7ad
Show file tree
Hide file tree
Showing 6 changed files with 626 additions and 14 deletions.
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -734,8 +734,14 @@ Due to nonlinearity, we do not attempt to calculate frequency responses for thes
| Opcode | Type | Parameters | Family | Notes |
| ------------ | ---------------------- | ------------ | ------------ | --------- |
| `bandrez` | bandpass (2nd order) | frequency, Q | nested 1st order | |
| `dresonator` | bandpass (2nd order) | frequency, Q | dirty biquad | Stable when the feedback mapping is nonexpansive. |
| `fresonator` | bandpass (2nd order) | frequency, Q | feedback biquad | -..- |
| `dbell` | peaking (2nd order) | frequency, Q, gain | dirty biquad | Biquad with nonlinear state shaping and adjustable amplitude gain. |
| `dhighpass` | highpass (2nd order) | frequency, Q | dirty biquad | |
| `dlowpass` | lowpass (2nd order) | frequency, Q | dirty biquad | |
| `dresonator` | bandpass (2nd order) | frequency, Q | dirty biquad | |
| `fbell` | peaking (2nd order) | frequency, Q, gain | feedback biquad | Biquad with nonlinear feedback and adjustable amplitude gain. |
| `fhighpass` | highpass (2nd order) | frequency, Q | feedback biquad | |
| `flowpass` | lowpass (2nd order) | frequency, Q | feedback biquad | |
| `fresonator` | bandpass (2nd order) | frequency, Q | feedback biquad | |
| `lowrez` | lowpass (2nd order) | frequency, Q | nested 1st order | |
| `moog` | lowpass (4th order) | frequency, Q | Moog ladder | |

Expand Down Expand Up @@ -899,11 +905,17 @@ The following table summarizes the available settings.
| `biquad` | `biquad` to set biquad coefficients |
| `butterpass_hz` | `center` |
| `constant` | `value` to set scalar value on all channels |
| `dbell_hz` | `center_q_gain` |
| `dc` | `value` to set scalar value on all channels |
| `dcblock_hz` | `center` |
| `dhighpass_hz` | `center_q` |
| `dlowpass_hz` | `center_q` |
| `dresonator_hz` | `center_q` |
| `dsf_saw_r` | `roughness` in 0...1 |
| `dsf_square_r` | `roughness` in 0...1 |
| `fbell_hz` | `center_q_gain` |
| `fhighpass_hz` | `center_q` |
| `flowpass_hz` | `center_q` |
| `follow` | `time` to set follow time in seconds |
| `fresonator_hz` | `center_q` |
| `highpass_hz` | `center_q` |
Expand Down Expand Up @@ -1073,13 +1085,19 @@ The type parameters in the table refer to the hacker preludes.
| `clip()` | 1 | 1 | Clip signal to -1...1. |
| `clip_to(min, max)` | 1 | 1 | Clip signal to min...max. |
| `constant(x)` | - | `x` | Constant signal `x`. Synonymous with `dc`. |
| `dbell(shape)` | 4 (audio, frequency, Q, gain) | 1 | Dirty biquad bell equalizer (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. |
| `dbell_hz(shape, f, q, gain)` | 1 | 1 | Dirty biquad bell equalizer (2nd order) with feedback `shape`, center `f` Hz, Q value `q` and amplitude gain `gain`. |
| `dc(x)` | - | `x` | Constant signal `x`. Synonymous with `constant`. |
| `dcblock()` | 1 | 1 | Zero center signal with cutoff frequency 10 Hz. |
| `dcblock_hz(f)` | 1 | 1 | Zero center signal with cutoff frequency `f`. |
| `declick()` | 1 | 1 | Apply 10 ms of fade-in to signal. |
| `declick_s(t)` | 1 | 1 | Apply `t` seconds of fade-in to signal. |
| `delay(t)` | 1 | 1 | Delay of `t` seconds. Delay time is rounded to the nearest sample. |
| `dresonator(shape)` | 3 (audio, frequency, bandwidth) | 1 | Dirty biquad resonator (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. |
| `dhighpass(shape)` | 3 (audio, frequency, Q) | 1 | Dirty biquad highpass (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. |
| `dhighpass_hz(shape, f, q)` | 1 | 1 | Dirty biquad highpass (2nd order) with feedback `shape`, center `f` Hz and Q `q`. |
| `dlowpass(shape)` | 3 (audio, frequency, Q) | 1 | Dirty biquad lowpass (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. |
| `dlowpass_hz(shape, f, q)` | 1 | 1 | Dirty biquad lowpass (2nd order) with feedback `shape`, center `f` Hz and Q `q`. |
| `dresonator(shape)` | 3 (audio, frequency, Q) | 1 | Dirty biquad resonator (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. |
| `dresonator_hz(shape, f, q)` | 1 | 1 | Dirty biquad resonator (2nd order) with feedback `shape`, center `f` Hz and Q `q`. |
| `dsf_saw()` | 2 (frequency, roughness) | 1 | Saw-like discrete summation formula oscillator. |
| `dsf_saw_r(r)` | 1 (frequency) | 1 | Saw-like discrete summation formula oscillator with roughness `r` in 0...1. |
Expand All @@ -1089,15 +1107,21 @@ The type parameters in the table refer to the hacker preludes.
| `envelope2(f)` | 1 (x) | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, x\| exp(-t * x)`. Synonymous with `lfo2`. |
| `envelope3(f)` | 2 (x, y) | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, x, y\| y * exp(-t * x)`. Synonymous with `lfo3`. |
| `envelope_in(f)` | `f` | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, i: &Frame<f64, U1>\| exp(-t * i[0])`. Synonymous with `lfo_in`. |
| `fbell(shape)` | 4 (audio, frequency, Q, gain) | 1 | Feedback biquad bell equalizer (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. |
| `fbell_hz(shape, f, q, gain)` | 1 | 1 | Feedback biquad bell equalizer (2nd order) with feedback `shape`, center `f` Hz, Q value `q` and amplitude gain `gain`. |
| `fdn(x)` | `x` | `x` | Feedback Delay Network: enclose feedback circuit `x` (with equal number of inputs and outputs) using diffusive [Hadamard](https://en.wikipedia.org/wiki/Hadamard_matrix) feedback. |
| `fdn2(x, y)` | `x`, `y`| `x`, `y`| Feedback Delay Network: enclose feedback circuit `x` (with equal number of inputs and outputs) using diffusive Hadamard feedback, with extra feedback loop processing `y`. The feedforward path does not include `y`. |
| `feedback(x)` | `x` | `x` | Enclose (single sample) feedback circuit `x` (with equal number of inputs and outputs). |
| `feedback2(x, y)` | `x`, `y`| `x`, `y`| Enclose (single sample) feedback circuit `x` (with equal number of inputs and outputs) with extra feedback loop processing `y`. The feedforward path does not include `y`. |
| `fir(weights)` | 1 | 1 | FIR filter with the specified weights, for example, `fir((0.5, 0.5))`. |
| `fir3(gain)` | 1 | 1 | Symmetric 3-point FIR calculated from desired `gain` at the Nyquist frequency. |
| `flanger(fb, min_d, max_d, f)`| 1| 1 | Flanger effect with feedback amount `fb`, minimum delay `min_d` seconds, maximum delay `max_d` seconds and delay function `f`, e.g., `\|t\| lerp11(0.01, 0.02, sin_hz(0.1, t))`. |
| `fhighpass(shape)` | 3 (audio, frequency, Q) | 1 | Feedback biquad highpass (2nd order) with feedback `shape`, for example, `Softsign(1.0)`. |
| `fhighpass_hz(shape, f, q)` | 1 | 1 | Feedback biquad highpass (2nd order) with feedback `shape`, center `f` Hz and Q `q`. |
| `flowpass(shape)` | 3 (audio, frequency, Q) | 1 | Feedback biquad lowpass (2nd order) with feedback `shape`, for example, `Softsign(1.0)`. |
| `flowpass_hz(shape, f, q)` | 1 | 1 | Feedback biquad lowpass (2nd order) with feedback `shape`, center `f` Hz and Q `q`. |
| `follow(t)` | 1 | 1 | Smoothing filter with halfway response time `t` seconds. |
| `fresonator(shape)` | 3 (audio, frequency, bandwidth) | 1 | Feedback biquad resonator (2nd order) with feedback `shape`, for example, `Softsign(1.0)`. |
| `fresonator(shape)` | 3 (audio, frequency, Q) | 1 | Feedback biquad resonator (2nd order) with feedback `shape`, for example, `Softsign(1.0)`. |
| `fresonator_hz(shape, f, q)` | 1 | 1 | Feedback biquad resonator (2nd order) with feedback `shape`, center `f` Hz and Q `q`. |
| `hammond()` | 1 (frequency) | 1 | Bandlimited Hammond oscillator. Emphasizes first three partials. |
| `hammond_hz(f)` | - | 1 | Bandlimited Hammond oscillator at `f` Hz. Emphasizes first three partials. |
Expand Down
10 changes: 8 additions & 2 deletions examples/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum Filter {
Butterworth,
Bandpass,
Peak,
DirtyBiquad,
FeedbackBiquad,
}

Expand Down Expand Up @@ -285,6 +286,7 @@ impl eframe::App for State {
});
ui.horizontal(|ui| {
ui.selectable_value(&mut self.filter, Filter::Peak, "Peak");
ui.selectable_value(&mut self.filter, Filter::DirtyBiquad, "Dirty Biquad");
ui.selectable_value(&mut self.filter, Filter::FeedbackBiquad, "Feedback Biquad");
});
ui.separator();
Expand Down Expand Up @@ -496,10 +498,14 @@ impl eframe::App for State {
(pass() | lfo(move |t| (xerp11(200.0, 10000.0, sin_hz(0.2, t)), 2.0)))
>> peak(),
)),
Filter::DirtyBiquad => Net::wrap(Box::new(
(pass() | lfo(move |t| (max(400.0, 20000.0 * exp(-t * 8.0)), 2.0)))
>> dlowpass(Tanh(1.0)),
)),
Filter::FeedbackBiquad => Net::wrap(Box::new(
(mul(5.0)
(mul(2.0)
| lfo(move |t| (xerp11(200.0, 10000.0, sin_hz(0.2, t)), 5.0)))
>> fresonator(Softsign(1.0)),
>> fresonator(Softsign(1.01)),
)),
};
let mut note = Box::new(waveform >> filter);
Expand Down
124 changes: 122 additions & 2 deletions src/biquad.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Biquad filters with nonlinearities.
//! Biquad filters with optional nonlinearities.
use super::audionode::*;
use super::math::*;
Expand All @@ -9,6 +9,7 @@ use super::*;
use core::marker::PhantomData;
use numeric_array::typenum::*;

/// Biquad coefficients in normalized form.
#[derive(Copy, Clone, Debug, Default)]
pub struct BiquadCoefs<F> {
pub a1: F,
Expand All @@ -20,7 +21,9 @@ pub struct BiquadCoefs<F> {

impl<F: Real> BiquadCoefs<F> {
/// 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 f: F = tan(cutoff * F::PI / sample_rate);
Expand All @@ -34,8 +37,9 @@ impl<F: Real> BiquadCoefs<F> {
}

/// Return settings for a constant-gain bandpass resonator.
/// The center frequency is given in Hz.
/// Sample rate and center frequency are in Hz.
/// 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 r: F = exp(-F::PI * center / (q * sample_rate));
Expand All @@ -47,7 +51,61 @@ impl<F: Real> BiquadCoefs<F> {
Self { a1, a2, b0, b1, b2 }
}

/// Return settings for a lowpass filter.
/// 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 omega = F::TAU * cutoff / sample_rate;
let alpha = sin(omega) / (c(2.0) * q);
let beta = cos(omega);
let a0r = c(1.0) / (c(1.0) + alpha);
let a1 = c(-2.0) * beta * a0r;
let a2 = (c(1.0) - alpha) * a0r;
let b1 = (c(1.0) - beta) * a0r;
let b0 = b1 * c(0.5);
let b2 = b0;
Self { a1, a2, b0, b1, b2 }
}

/// Return settings for a highpass filter.
/// 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 omega = F::TAU * cutoff / sample_rate;
let alpha = sin(omega) / (c(2.0) * q);
let beta = cos(omega);
let a0r = c(1.0) / (c(1.0) + alpha);
let a1 = c(-2.0) * beta * a0r;
let a2 = (c(1.0) - alpha) * a0r;
let b0 = (c(1.0) + beta) * c(0.5) * a0r;
let b1 = (c(-1.0) - beta) * a0r;
let b2 = b0;
Self { a1, a2, b0, b1, b2 }
}

/// Return settings for a bell equalizer filter.
/// Sample rate and center frequencies are in Hz.
/// 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 omega = F::TAU * center / sample_rate;
let alpha = sin(omega) / (c(2.0) * q);
let beta = cos(omega);
let a = sqrt(gain);
let a0r = c(1.0) / (c(1.0) + alpha / a);
let a1 = c(-2.0) * beta * a0r;
let a2 = (c(1.0) - alpha / a) * a0r;
let b0 = (c(1.0) + alpha * a) * a0r;
let b1 = a1;
let b2 = (c(1.0) - alpha * a) * a0r;
Self { a1, a2, b0, b1, b2 }
}

/// Arbitrary biquad.
#[inline]
pub fn arbitrary(a1: F, a2: F, b0: F, b1: F, b2: F) -> Self {
Self { a1, a2, b0, b1, b2 }
}
Expand Down Expand Up @@ -346,6 +404,7 @@ pub trait BiquadMode<F: Real>: Clone + Default + Sync + Send {
fn update(&mut self, params: &BiquadParams<F>, coefs: &mut BiquadCoefs<F>);
}

/// Resonator biquad mode.
#[derive(Clone, Default)]
pub struct ResonatorBiquad<F: Real> {
_marker: PhantomData<F>,
Expand All @@ -359,11 +418,72 @@ impl<F: Real> ResonatorBiquad<F> {

impl<F: Real> BiquadMode<F> for ResonatorBiquad<F> {
type Inputs = U3;
#[inline]
fn update(&mut self, params: &BiquadParams<F>, coefs: &mut BiquadCoefs<F>) {
*coefs = BiquadCoefs::resonator(params.sample_rate, params.center, params.q);
}
}

/// Lowpass biquad mode.
#[derive(Clone, Default)]
pub struct LowpassBiquad<F: Real> {
_marker: PhantomData<F>,
}

impl<F: Real> LowpassBiquad<F> {
pub fn new() -> Self {
Self::default()
}
}

impl<F: Real> BiquadMode<F> for LowpassBiquad<F> {
type Inputs = U3;
#[inline]
fn update(&mut self, params: &BiquadParams<F>, coefs: &mut BiquadCoefs<F>) {
*coefs = BiquadCoefs::lowpass(params.sample_rate, params.center, params.q);
}
}

/// Highpass biquad mode.
#[derive(Clone, Default)]
pub struct HighpassBiquad<F: Real> {
_marker: PhantomData<F>,
}

impl<F: Real> HighpassBiquad<F> {
pub fn new() -> Self {
Self::default()
}
}

impl<F: Real> BiquadMode<F> for HighpassBiquad<F> {
type Inputs = U3;
#[inline]
fn update(&mut self, params: &BiquadParams<F>, coefs: &mut BiquadCoefs<F>) {
*coefs = BiquadCoefs::highpass(params.sample_rate, params.center, params.q);
}
}

/// Bell biquad mode.
#[derive(Clone, Default)]
pub struct BellBiquad<F: Real> {
_marker: PhantomData<F>,
}

impl<F: Real> BellBiquad<F> {
pub fn new() -> Self {
Self::default()
}
}

impl<F: Real> BiquadMode<F> for BellBiquad<F> {
type Inputs = U4;
#[inline]
fn update(&mut self, params: &BiquadParams<F>, coefs: &mut BiquadCoefs<F>) {
*coefs = BiquadCoefs::bell(params.sample_rate, params.center, params.q, params.gain);
}
}

#[derive(Clone)]
/// Biquad in transposed direct form II with nonlinear feedback.
pub struct FbBiquad<F: Real, M: BiquadMode<F>, S: Shape> {
Expand Down
Loading

0 comments on commit 5f0c7ad

Please sign in to comment.