use super::{Signal, eval_value};
use crate::dsl::{DriveShape, Mode, Value};
use std::f32::consts::{LN_2, TAU};
#[derive(Clone, Copy)]
pub(crate) enum FilterKind {
Low,
High,
Band,
Notch,
Peak(f32),
LowShelf(f32),
HighShelf(f32),
}
pub(crate) fn biquad_coeffs(
kind: FilterKind,
fc: f32,
q: f32,
sr: u32,
) -> (f32, f32, f32, f32, f32) {
let srf = sr as f32;
let q = q.max(0.05);
let nyq = srf / 2.0;
let f = fc.clamp(20.0, (nyq - 100.0).max(20.0));
let w0 = TAU * f / srf;
let (sin, cos) = w0.sin_cos();
let alpha = sin / (2.0 * q);
let amp = match kind {
FilterKind::Peak(g) | FilterKind::LowShelf(g) | FilterKind::HighShelf(g) => {
10f32.powf(g / 40.0)
}
_ => 1.0,
};
let (b0, b1, b2, a0, a1, a2) = match kind {
FilterKind::Low => (
(1.0 - cos) / 2.0,
1.0 - cos,
(1.0 - cos) / 2.0,
1.0 + alpha,
-2.0 * cos,
1.0 - alpha,
),
FilterKind::High => (
(1.0 + cos) / 2.0,
-(1.0 + cos),
(1.0 + cos) / 2.0,
1.0 + alpha,
-2.0 * cos,
1.0 - alpha,
),
FilterKind::Band => (alpha, 0.0, -alpha, 1.0 + alpha, -2.0 * cos, 1.0 - alpha),
FilterKind::Notch => (1.0, -2.0 * cos, 1.0, 1.0 + alpha, -2.0 * cos, 1.0 - alpha),
FilterKind::Peak(_) => (
1.0 + alpha * amp,
-2.0 * cos,
1.0 - alpha * amp,
1.0 + alpha / amp,
-2.0 * cos,
1.0 - alpha / amp,
),
FilterKind::LowShelf(_) => {
let s = 2.0 * amp.sqrt() * alpha;
let (ap1, am1) = (amp + 1.0, amp - 1.0);
(
amp * (ap1 - am1 * cos + s),
2.0 * amp * (am1 - ap1 * cos),
amp * (ap1 - am1 * cos - s),
ap1 + am1 * cos + s,
-2.0 * (am1 + ap1 * cos),
ap1 + am1 * cos - s,
)
}
FilterKind::HighShelf(_) => {
let s = 2.0 * amp.sqrt() * alpha;
let (ap1, am1) = (amp + 1.0, amp - 1.0);
(
amp * (ap1 + am1 * cos + s),
-2.0 * amp * (am1 + ap1 * cos),
amp * (ap1 + am1 * cos - s),
ap1 - am1 * cos + s,
2.0 * (am1 - ap1 * cos),
ap1 - am1 * cos - s,
)
}
};
(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
pub(super) fn biquad(input: &[f32], cutoff: &Value, q: f32, sr: u32, kind: FilterKind) -> Signal {
let fc = eval_value(cutoff, input.len(), sr);
let (mut x1, mut x2, mut y1, mut y2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
let mut out = Vec::with_capacity(input.len());
let mut prev_f = f32::NAN;
let (mut b0, mut b1, mut b2, mut a1, mut a2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32);
for (i, &x0) in input.iter().enumerate() {
if fc[i] != prev_f {
(b0, b1, b2, a1, a2) = biquad_coeffs(kind, fc[i], q, sr);
prev_f = fc[i];
}
let y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
x1 = x0;
y2 = y1;
y1 = y0;
out.push(y0);
}
out
}
pub(super) fn reverb(input: &[f32], room: f32, mix: f32, sr: u32, spread: usize) -> Signal {
let scale = sr as f32 / 44_100.0;
let comb_tunings = crate::dsp::FREEVERB_COMB_TUNINGS.map(|t| t + spread);
let allpass_tunings = crate::dsp::FREEVERB_ALLPASS_TUNINGS.map(|t| t + spread);
let feedback = 0.7 + 0.28 * room.clamp(0.0, 1.0);
let damp = crate::dsp::FREEVERB_DAMP;
let mut wet = vec![0.0f32; input.len()];
for &tune in &comb_tunings {
let len = ((tune as f32 * scale) as usize).max(1);
let mut buf = vec![0.0f32; len];
let mut idx = 0usize;
let mut filter_store = 0.0f32;
for (i, &x) in input.iter().enumerate() {
let y = buf[idx];
filter_store = y * (1.0 - damp) + filter_store * damp;
buf[idx] = x + filter_store * feedback;
idx = (idx + 1) % len;
wet[i] += y;
}
}
for &tune in &allpass_tunings {
let len = ((tune as f32 * scale) as usize).max(1);
let mut buf = vec![0.0f32; len];
let mut idx = 0usize;
let g = 0.5;
for w in wet.iter_mut() {
let buffered = buf[idx];
let y = -*w + buffered;
buf[idx] = *w + buffered * g;
idx = (idx + 1) % len;
*w = y;
}
}
let mix = mix.clamp(0.0, 1.0);
let comb_norm = 1.0 / comb_tunings.len() as f32;
input
.iter()
.zip(wet)
.map(|(dry, w)| dry * (1.0 - mix) + (w * comb_norm) * mix)
.collect()
}
pub(crate) fn drive_curve(x: f32, shape: DriveShape) -> f32 {
match shape {
DriveShape::Tanh => x.tanh(),
DriveShape::Hard => x.clamp(-1.0, 1.0),
DriveShape::Fold => {
if !x.is_finite() {
return 0.0;
}
let mut y = x;
let mut folds = 0;
while !(-1.0..=1.0).contains(&y) {
if y > 1.0 {
y = 2.0 - y;
} else {
y = -2.0 - y;
}
folds += 1;
if folds > 1024 {
return y.clamp(-1.0, 1.0);
}
}
y
}
}
}
pub(crate) fn drive_antideriv(x: f32, shape: DriveShape) -> f32 {
match shape {
DriveShape::Tanh => {
let a = x.abs();
a + (-2.0 * a).exp().ln_1p() - LN_2
}
DriveShape::Hard => {
let a = x.abs();
if a <= 1.0 { 0.5 * x * x } else { a - 0.5 }
}
DriveShape::Fold => {
let p = (x + 1.0).rem_euclid(4.0);
if p <= 2.0 {
0.5 * (p - 1.0) * (p - 1.0)
} else {
1.0 - 0.5 * (p - 3.0) * (p - 3.0)
}
}
}
}
pub(super) fn drive_adaa(input: &[f32], amount: &[f32], shape: DriveShape) -> Signal {
const EPS: f32 = 1e-5;
const R: f32 = 0.9995;
let mut x_prev = 0.0f32;
let mut f_prev = drive_antideriv(0.0, shape);
let (mut dc_x, mut dc_y) = (0.0f32, 0.0f32);
let mut out = Vec::with_capacity(input.len());
for (&x, &amt) in input.iter().zip(amount) {
let xn = amt.max(0.0) * x;
let f = drive_antideriv(xn, shape);
let d = xn - x_prev;
let y = if d.abs() > EPS {
(f - f_prev) / d
} else {
drive_curve(0.5 * (xn + x_prev), shape)
};
x_prev = xn;
f_prev = f;
let yb = y - dc_x + R * dc_y;
dc_x = y;
dc_y = yb;
out.push(yb);
}
out
}
pub(super) fn modal_bank(input: &[f32], modes: &[Mode], mix: f32, sr: u32) -> Signal {
let srf = sr as f32;
let nyq = srf * 0.5;
let mix = mix.clamp(0.0, 1.0);
let mut wet = vec![0.0f32; input.len()];
for m in modes {
let f0 = m.freq.clamp(1.0, (nyq - 1.0).max(1.0));
let decay = m.decay.max(1e-3);
let w0 = TAU * f0 / srf;
let (sin0, cos0) = (w0.sin(), w0.cos());
let r = (-6.907_755 / (decay * srf)).exp();
let a1 = 2.0 * r * cos0;
let a2 = -r * r;
let b0 = m.gain * sin0; let (mut y1, mut y2) = (0.0f32, 0.0f32);
for (o, &x) in wet.iter_mut().zip(input) {
let y = b0 * x + a1 * y1 + a2 * y2;
y2 = y1;
y1 = y;
*o += y;
}
}
input
.iter()
.zip(wet)
.map(|(d, w)| d * (1.0 - mix) + w * mix)
.collect()
}
pub(super) fn chorus(input: &[f32], rate: f32, depth: f32, mix: f32, sr: u32) -> Signal {
let srf = sr as f32;
let base = crate::dsp::CHORUS_BASE_SECS * srf;
let swing = depth.clamp(0.0, 1.0) * crate::dsp::CHORUS_SWING_SECS * srf;
let max_delay = (base + swing) as usize + 2;
let mut buf = vec![0.0f32; max_delay];
let mut w = 0usize;
let mix = mix.clamp(0.0, 1.0);
let mut out = Vec::with_capacity(input.len());
for (i, &x) in input.iter().enumerate() {
buf[w] = x;
let lfo = (TAU * rate * i as f32 / srf).sin();
let delay = base + swing * lfo;
let read = w as f32 - delay;
let read = read.rem_euclid(max_delay as f32);
let i0 = read.floor() as usize % max_delay;
let i1 = (i0 + 1) % max_delay;
let frac = read - read.floor();
let wet = buf[i0] * (1.0 - frac) + buf[i1] * frac;
out.push(x * (1.0 - mix) + wet * mix);
w = (w + 1) % max_delay;
}
out
}
pub(super) fn flanger(
input: &[f32],
rate: f32,
depth: f32,
feedback: f32,
mix: f32,
sr: u32,
) -> Signal {
let srf = sr as f32;
let base = crate::dsp::FLANGER_BASE_SECS * srf; let swing = depth.clamp(0.0, 1.0) * crate::dsp::FLANGER_SWING_SECS * srf; let max_delay = (base + swing) as usize + 2;
let mut buf = vec![0.0f32; max_delay];
let mut w = 0usize;
let fb = feedback.clamp(0.0, 0.95);
let mix = mix.clamp(0.0, 1.0);
let mut out = Vec::with_capacity(input.len());
for (i, &x) in input.iter().enumerate() {
let lfo = (TAU * rate * i as f32 / srf).sin();
let delay = base + swing * lfo;
let read = (w as f32 - delay).rem_euclid(max_delay as f32);
let i0 = read.floor() as usize % max_delay;
let i1 = (i0 + 1) % max_delay;
let frac = read - read.floor();
let wet = buf[i0] * (1.0 - frac) + buf[i1] * frac;
buf[w] = x + wet * fb;
w = (w + 1) % max_delay;
out.push(x * (1.0 - mix) + wet * mix);
}
out
}
pub(super) fn phaser(
input: &[f32],
rate: f32,
depth: f32,
feedback: f32,
mix: f32,
sr: u32,
) -> Signal {
let srf = sr as f32;
let fb = feedback.clamp(0.0, 0.95);
let mix = mix.clamp(0.0, 1.0);
let depth = depth.clamp(0.0, 1.0);
let mut x1 = [0.0f32; 4];
let mut y1 = [0.0f32; 4];
let mut last_wet = 0.0f32;
let mut out = Vec::with_capacity(input.len());
for (i, &x) in input.iter().enumerate() {
let lfo = 0.5 + 0.5 * (TAU * rate * i as f32 / srf).sin();
let g = 0.15 + 0.7 * depth * lfo;
let mut s = x + last_wet * fb;
for k in 0..4 {
let y = -g * s + x1[k] + g * y1[k];
x1[k] = s;
y1[k] = y;
s = y;
}
last_wet = s;
out.push(x * (1.0 - mix) + s * mix);
}
out
}
pub(super) fn compress(
input: &[f32],
threshold_db: f32,
ratio: f32,
attack: f32,
release: f32,
makeup_db: f32,
sr: u32,
) -> Signal {
let srf = sr as f32;
let at = (-1.0 / (attack.max(1e-4) * srf)).exp();
let rt = (-1.0 / (release.max(1e-4) * srf)).exp();
let makeup = 10f32.powf(makeup_db / 20.0);
let ratio = ratio.max(1.0);
let mut env = 0.0f32; let mut out = Vec::with_capacity(input.len());
for &x in input {
let rect = x.abs();
let coeff = if rect > env { at } else { rt };
env = rect + coeff * (env - rect);
let env_db = 20.0 * env.max(1e-9).log10();
let gain_db = if env_db > threshold_db {
-(env_db - threshold_db) * (1.0 - 1.0 / ratio)
} else {
0.0
};
let g = 10f32.powf(gain_db / 20.0);
out.push(x * g * makeup);
}
out
}