Skip to content

Commit

Permalink
Initial setup for FM synth filter internalization
Browse files Browse the repository at this point in the history
 * Create filter module for FM synth to support the different filter configurations
 * Set up initial code for filter application and param source integration
  • Loading branch information
Ameobea committed Jan 13, 2024
1 parent f7da78b commit 9062c3c
Show file tree
Hide file tree
Showing 6 changed files with 425 additions and 60 deletions.
22 changes: 4 additions & 18 deletions engine/dsp/src/band_splitter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{
filters::biquad::{BiquadFilter, FilterMode},
filters::{
biquad::{BiquadFilter, FilterMode},
filter_chain::apply_filter_chain_full,
},
FRAME_SIZE,
};

Expand All @@ -8,23 +11,6 @@ const BAND_SPLITTER_FILTER_CHAIN_LENGTH: usize = BAND_SPLITTER_FILTER_ORDER / 2;
const LOW_BAND_CUTOFF: f32 = 88.3;
const MID_BAND_CUTOFF: f32 = 2500.;

fn apply_filter_chain_full<const N: usize>(
chain: &mut [BiquadFilter; N],
input_buf: [f32; FRAME_SIZE],
output_buf: &mut [f32; FRAME_SIZE],
) {
let mut filtered = input_buf;
for filter in chain.iter_mut() {
for i in 0..FRAME_SIZE {
filtered[i] = filter.apply(filtered[i]);
}
}

for i in 0..FRAME_SIZE {
output_buf[i] = filtered[i];
}
}

pub struct BandSplitter {
pub low_band_filter_chain: [BiquadFilter; BAND_SPLITTER_FILTER_CHAIN_LENGTH],
pub mid_band_filter_chain: [BiquadFilter; BAND_SPLITTER_FILTER_CHAIN_LENGTH * 2],
Expand Down
146 changes: 114 additions & 32 deletions engine/dsp/src/filters/biquad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,41 @@ pub enum FilterMode {
Highshelf,
}

impl FilterMode {
pub fn needs_gain(&self) -> bool {
match self {
FilterMode::Lowpass => false,
FilterMode::Highpass => false,
FilterMode::Bandpass => false,
FilterMode::Notch => false,
FilterMode::Peak => true,
FilterMode::Lowshelf => true,
FilterMode::Highshelf => true,
}
}

pub fn needs_q(&self) -> bool {
match self {
FilterMode::Lowpass => true,
FilterMode::Highpass => true,
FilterMode::Bandpass => true,
FilterMode::Notch => true,
FilterMode::Peak => true,
FilterMode::Lowshelf => false,
FilterMode::Highshelf => false,
}
}
}

impl BiquadFilter {
#[inline]
pub fn set_coefficients(&mut self, mode: FilterMode, q: f32, detune: f32, freq: f32, gain: f32) {
pub fn compute_coefficients(
mode: FilterMode,
q: f32,
detune: f32,
freq: f32,
gain: f32,
) -> (f32, f32, f32, f32, f32) {
// From: https://webaudio.github.io/web-audio-api/#filters-characteristics
let computed_frequency = freq * 2.0f32.powf(detune / 1200.0);
let normalized_freq = computed_frequency / NYQUIST;
Expand All @@ -42,70 +74,83 @@ impl BiquadFilter {

let (b0, b1, b2, a0, a1, a2);

let w0_cos = w0.cos();
match mode {
FilterMode::Lowpass => {
b0 = (1. - w0.cos()) / 2.;
b1 = 1. - w0.cos();
b2 = (1. - w0.cos()) / 2.;
b0 = (1. - w0_cos) / 2.;
b1 = 1. - w0_cos;
b2 = (1. - w0_cos) / 2.;
a0 = 1. + aqdb;
a1 = -2. * w0.cos();
a1 = -2. * w0_cos;
a2 = 1. - aqdb;
},
FilterMode::Highpass => {
b0 = (1. + w0.cos()) / 2.;
b1 = -(1. + w0.cos());
b2 = (1. + w0.cos()) / 2.;
b0 = (1. + w0_cos) / 2.;
b1 = -(1. + w0_cos);
b2 = (1. + w0_cos) / 2.;
a0 = 1. + aqdb;
a1 = -2. * w0.cos();
a1 = -2. * w0_cos;
a2 = 1. - aqdb;
},
FilterMode::Bandpass => {
b0 = aq;
b1 = 0.;
b2 = -aq;
a0 = 1. + aq;
a1 = -2. * w0.cos();
a1 = -2. * w0_cos;
a2 = 1. - aq;
},
FilterMode::Notch => {
b0 = 1.;
b1 = -2. * w0.cos();
b1 = -2. * w0_cos;
b2 = 1.;
a0 = 1. + aq;
a1 = -2. * w0.cos();
a1 = -2. * w0_cos;
a2 = 1. - aq;
},
FilterMode::Peak => {
b0 = 1. + aq * A;
b1 = -2. * w0.cos();
b1 = -2. * w0_cos;
b2 = 1. - aq * A;
a0 = 1. + aq / A;
a1 = -2. * w0.cos();
a1 = -2. * w0_cos;
a2 = 1. - aq / A;
},
FilterMode::Lowshelf => {
b0 = A * ((A + 1.) - (A - 1.) * w0.cos() + 2. * a_s * A.sqrt());
b1 = 2. * A * ((A - 1.) - (A + 1.) * w0.cos());
b2 = A * ((A + 1.) - (A - 1.) * w0.cos() - 2. * a_s * A.sqrt());
a0 = (A + 1.) + (A - 1.) * w0.cos() + 2. * a_s * A.sqrt();
a1 = -2. * ((A - 1.) + (A + 1.) * w0.cos());
a2 = (A + 1.) + (A - 1.) * w0.cos() - 2. * a_s * A.sqrt();
#[allow(non_snake_case)]
let A_sqrt = A.sqrt();
b0 = A * ((A + 1.) - (A - 1.) * w0_cos + 2. * a_s * A_sqrt);
b1 = 2. * A * ((A - 1.) - (A + 1.) * w0_cos);
b2 = A * ((A + 1.) - (A - 1.) * w0_cos - 2. * a_s * A_sqrt);
a0 = (A + 1.) + (A - 1.) * w0_cos + 2. * a_s * A_sqrt;
a1 = -2. * ((A - 1.) + (A + 1.) * w0_cos);
a2 = (A + 1.) + (A - 1.) * w0_cos - 2. * a_s * A_sqrt;
},
FilterMode::Highshelf => {
b0 = A * ((A + 1.) + (A - 1.) * w0.cos() + 2. * a_s * A.sqrt());
b1 = -2. * A * ((A - 1.) + (A + 1.) * w0.cos());
b2 = A * ((A + 1.) + (A - 1.) * w0.cos() - 2. * a_s * A.sqrt());
a0 = (A + 1.) - (A - 1.) * w0.cos() + 2. * a_s * A.sqrt();
a1 = 2. * ((A - 1.) - (A + 1.) * w0.cos());
a2 = (A + 1.) - (A - 1.) * w0.cos() - 2. * a_s * A.sqrt();
#[allow(non_snake_case)]
let A_sqrt = A.sqrt();
b0 = A * ((A + 1.) + (A - 1.) * w0_cos + 2. * a_s * A_sqrt);
b1 = -2. * A * ((A - 1.) + (A + 1.) * w0_cos);
b2 = A * ((A + 1.) + (A - 1.) * w0_cos - 2. * a_s * A_sqrt);
a0 = (A + 1.) - (A - 1.) * w0_cos + 2. * a_s * A_sqrt;
a1 = 2. * ((A - 1.) - (A + 1.) * w0_cos);
a2 = (A + 1.) - (A - 1.) * w0_cos - 2. * a_s * A_sqrt;
},
}

self.b0_over_a0 = b0 / a0;
self.b1_over_a0 = b1 / a0;
self.b2_over_a0 = b2 / a0;
self.a1_over_a0 = a1 / a0;
self.a2_over_a0 = a2 / a0;
(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}

#[inline]
pub fn set_coefficients(&mut self, mode: FilterMode, q: f32, detune: f32, freq: f32, gain: f32) {
let (b0_over_a0, b1_over_a0, b2_over_a0, a1_over_a0, a2_over_a0) =
Self::compute_coefficients(mode, q, detune, freq, gain);

self.b0_over_a0 = b0_over_a0;
self.b1_over_a0 = b1_over_a0;
self.b2_over_a0 = b2_over_a0;
self.a1_over_a0 = a1_over_a0;
self.a2_over_a0 = a2_over_a0;
}

#[inline]
Expand All @@ -127,6 +172,44 @@ impl BiquadFilter {

output
}

#[inline]
pub fn apply_with_coefficients(
&mut self,
input: f32,
b0_over_a0: f32,
b1_over_a0: f32,
b2_over_a0: f32,
a1_over_a0: f32,
a2_over_a0: f32,
) -> f32 {
let output = b0_over_a0 * input + b1_over_a0 * self.x[0] + b2_over_a0 * self.x[1]
- a1_over_a0 * self.y[0]
- a2_over_a0 * self.y[1];

self.x = [input, self.x[0]];
self.y = [output, self.y[0]];

output
}

#[inline]
pub fn compute_coefficients_and_apply(
&mut self,
mode: FilterMode,
q: f32,
detune: f32,
freq: f32,
gain: f32,
input: f32,
) -> f32 {
let (b0_over_a0, b1_over_a0, b2_over_a0, a1_over_a0, a2_over_a0) =
Self::compute_coefficients(mode, q, detune, freq, gain);

self.apply_with_coefficients(
input, b0_over_a0, b1_over_a0, b2_over_a0, a1_over_a0, a2_over_a0,
)
}
}

/// Coefficients and state are stored as SoA. Since applying biquad filter chains has a serial
Expand Down Expand Up @@ -262,7 +345,6 @@ impl<const BANK_COUNT: usize, const BANK_LENGTH: usize>

output[band_ix] = outs;
}
// }
}
}

Expand Down
41 changes: 41 additions & 0 deletions engine/dsp/src/filters/filter_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::FRAME_SIZE;

use super::biquad::{BiquadFilter, FilterMode};

#[inline]
pub fn apply_filter_chain_full<const N: usize>(
chain: &mut [BiquadFilter; N],
input_buf: [f32; FRAME_SIZE],
output_buf: &mut [f32; FRAME_SIZE],
) {
let mut filtered = input_buf;
for filter in chain.iter_mut() {
for i in 0..FRAME_SIZE {
filtered[i] = filter.apply(filtered[i]);
}
}

for i in 0..FRAME_SIZE {
output_buf[i] = filtered[i];
}
}

#[inline]
pub fn apply_filter_chain_and_compute_coefficients<const N: usize>(
chain: &mut [BiquadFilter; N],
frame: &mut [f32; FRAME_SIZE],
filter_mode: FilterMode,
q: &[f32; FRAME_SIZE],
cutoff_freq: &[f32; FRAME_SIZE],
gain: &[f32; FRAME_SIZE],
) {
for i in 0..frame.len() {
let coeffs = BiquadFilter::compute_coefficients(filter_mode, q[i], 0., cutoff_freq[i], gain[i]);

let mut val = frame[i];
for filter in chain.into_iter() {
val = filter.apply_with_coefficients(val, coeffs.0, coeffs.1, coeffs.2, coeffs.3, coeffs.4);
}
frame[i] = val;
}
}
1 change: 1 addition & 0 deletions engine/dsp/src/filters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod biquad;
pub mod butterworth;
pub mod dc_blocker;
pub mod filter_chain;
Loading

0 comments on commit 9062c3c

Please sign in to comment.