use crate::synthesis::automation::Automation;
use crate::track::PRIORITY_EARLY;
#[derive(Debug, Clone)]
pub struct EQ {
pub low_gain: f32, pub mid_gain: f32, pub high_gain: f32, pub low_freq: f32, pub high_freq: f32, pub priority: u8, low_state: [f32; 2],
mid_state: [f32; 2],
high_state: [f32; 2],
low_gain_automation: Option<Automation>,
mid_gain_automation: Option<Automation>,
high_gain_automation: Option<Automation>,
}
impl EQ {
pub fn new(
low_gain: f32,
mid_gain: f32,
high_gain: f32,
low_freq: f32,
high_freq: f32,
) -> Self {
Self {
low_gain: low_gain.clamp(0.0, 4.0),
mid_gain: mid_gain.clamp(0.0, 4.0),
high_gain: high_gain.clamp(0.0, 4.0),
low_freq: low_freq.clamp(20.0, 20000.0),
high_freq: high_freq.clamp(20.0, 20000.0),
priority: PRIORITY_EARLY, low_state: [0.0; 2],
mid_state: [0.0; 2],
high_state: [0.0; 2],
low_gain_automation: None,
mid_gain_automation: None,
high_gain_automation: None,
}
}
pub fn with_priority(mut self, priority: u8) -> Self {
self.priority = priority;
self
}
pub fn with_low_gain_automation(mut self, automation: Automation) -> Self {
self.low_gain_automation = Some(automation);
self
}
pub fn with_mid_gain_automation(mut self, automation: Automation) -> Self {
self.mid_gain_automation = Some(automation);
self
}
pub fn with_high_gain_automation(mut self, automation: Automation) -> Self {
self.high_gain_automation = Some(automation);
self
}
#[inline]
pub fn process(&mut self, input: f32, sample_rate: f32, time: f32, sample_count: u64) -> f32 {
if sample_count & 63 == 0 {
if let Some(auto) = &self.low_gain_automation {
self.low_gain = auto.value_at(time).clamp(0.0, 4.0);
}
if let Some(auto) = &self.mid_gain_automation {
self.mid_gain = auto.value_at(time).clamp(0.0, 4.0);
}
if let Some(auto) = &self.high_gain_automation {
self.high_gain = auto.value_at(time).clamp(0.0, 4.0);
}
}
if (self.low_gain - 1.0).abs() < 0.01
&& (self.mid_gain - 1.0).abs() < 0.01
&& (self.high_gain - 1.0).abs() < 0.01
{
return input;
}
let low_coeff = (2.0 * std::f32::consts::PI * self.low_freq / sample_rate).min(0.9);
let high_coeff = (2.0 * std::f32::consts::PI * self.high_freq / sample_rate).min(0.9);
let diff_low = input - self.low_state[0];
self.low_state[0] = self.low_state[0].mul_add(1.0, low_coeff * diff_low);
let low = self.low_state[0] * self.low_gain;
let diff_high = input - self.high_state[0];
self.high_state[0] = self.high_state[0].mul_add(1.0, high_coeff * diff_high);
let high = diff_high * self.high_gain;
let mid = (input - self.low_state[0] - diff_high) * self.mid_gain;
low + mid + high
}
#[inline]
pub fn process_block(&mut self, buffer: &mut [f32], sample_rate: f32, time: f32, sample_count: u64) {
let time_delta = 1.0 / sample_rate;
for (i, sample) in buffer.iter_mut().enumerate() {
let current_time = time + (i as f32 * time_delta);
let current_sample_count = sample_count + i as u64;
*sample = self.process(*sample, sample_rate, current_time, current_sample_count);
}
}
pub fn reset(&mut self) {
self.low_state = [0.0; 2];
self.mid_state = [0.0; 2];
self.high_state = [0.0; 2];
}
pub fn flat() -> Self {
Self::new(1.0, 1.0, 1.0, 300.0, 3000.0)
}
pub fn bass_boost() -> Self {
Self::new(1.5, 1.0, 1.0, 100.0, 3000.0)
}
pub fn treble_boost() -> Self {
Self::new(1.0, 1.0, 1.5, 300.0, 5000.0)
}
pub fn smiley() -> Self {
Self::new(1.4, 0.7, 1.4, 200.0, 4000.0)
}
pub fn presence() -> Self {
Self::new(1.0, 1.3, 1.1, 500.0, 3000.0)
}
pub fn warmth() -> Self {
Self::new(1.3, 1.0, 0.9, 150.0, 3000.0)
}
pub fn bright() -> Self {
Self::new(0.8, 1.0, 1.4, 300.0, 5000.0)
}
}
#[derive(Debug, Clone)]
pub struct EQBand {
pub frequency: f32,
pub gain_db: f32,
pub q: f32,
pub enabled: bool,
b0: f32,
b1: f32,
b2: f32,
a1: f32,
a2: f32,
x1: f32,
x2: f32,
y1: f32,
y2: f32,
}
impl EQBand {
pub fn new(frequency: f32, gain_db: f32, q: f32) -> Self {
let mut band = Self {
frequency,
gain_db,
q,
enabled: true,
b0: 1.0,
b1: 0.0,
b2: 0.0,
a1: 0.0,
a2: 0.0,
x1: 0.0,
x2: 0.0,
y1: 0.0,
y2: 0.0,
};
band.update_coefficients(44100.0);
band
}
fn update_coefficients(&mut self, sample_rate: f32) {
use std::f32::consts::PI;
let w0 = 2.0 * PI * self.frequency / sample_rate;
let cos_w0 = w0.cos();
let sin_w0 = w0.sin();
let alpha = sin_w0 / (2.0 * self.q);
let a = 10.0_f32.powf(self.gain_db / 40.0);
let b0 = 1.0 + alpha * a;
let b1 = -2.0 * cos_w0;
let b2 = 1.0 - alpha * a;
let a0 = 1.0 + alpha / a;
let a1 = -2.0 * cos_w0;
let a2 = 1.0 - alpha / a;
self.b0 = b0 / a0;
self.b1 = b1 / a0;
self.b2 = b2 / a0;
self.a1 = a1 / a0;
self.a2 = a2 / a0;
}
#[inline]
fn process(&mut self, input: f32) -> f32 {
if !self.enabled {
return input;
}
let output = self.b0 * input + self.b1 * self.x1 + self.b2 * self.x2
- self.a1 * self.y1 - self.a2 * self.y2;
self.x2 = self.x1;
self.x1 = input;
self.y2 = self.y1;
self.y1 = output;
output
}
pub fn reset(&mut self) {
self.x1 = 0.0;
self.x2 = 0.0;
self.y1 = 0.0;
self.y2 = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct ParametricEQ {
pub bands: Vec<EQBand>,
pub priority: u8,
}
impl ParametricEQ {
pub fn new() -> Self {
Self {
bands: Vec::new(),
priority: 50,
}
}
pub fn band(mut self, frequency: f32, gain_db: f32, q: f32) -> Self {
self.bands.push(EQBand::new(frequency, gain_db, q));
self
}
pub fn preset(mut self, preset: EQPreset) -> Self {
match preset {
EQPreset::VocalClarity => {
self.bands.push(EQBand::new(100.0, -6.0, 1.0)); self.bands.push(EQBand::new(250.0, -3.0, 1.5)); self.bands.push(EQBand::new(3000.0, 4.0, 2.0)); self.bands.push(EQBand::new(8000.0, -2.0, 1.5)); }
EQPreset::BassBoost => {
self.bands.push(EQBand::new(60.0, 4.0, 1.0)); self.bands.push(EQBand::new(120.0, 3.0, 1.5)); self.bands.push(EQBand::new(300.0, -2.0, 1.0)); }
EQPreset::BrightAiry => {
self.bands.push(EQBand::new(5000.0, 3.0, 1.5)); self.bands.push(EQBand::new(10000.0, 4.0, 1.0)); self.bands.push(EQBand::new(15000.0, 2.0, 0.7)); }
EQPreset::Telephone => {
self.bands.push(EQBand::new(200.0, -12.0, 0.5)); self.bands.push(EQBand::new(1000.0, 6.0, 1.0)); self.bands.push(EQBand::new(4000.0, -12.0, 0.5)); }
EQPreset::Warmth => {
self.bands.push(EQBand::new(200.0, 3.0, 1.0)); self.bands.push(EQBand::new(500.0, 2.0, 1.5)); self.bands.push(EQBand::new(8000.0, -2.0, 1.0)); }
}
self
}
pub fn enable_band(&mut self, index: usize, enabled: bool) {
if let Some(band) = self.bands.get_mut(index) {
band.enabled = enabled;
}
}
pub fn update_band(&mut self, index: usize, frequency: f32, gain_db: f32, q: f32, sample_rate: f32) {
if let Some(band) = self.bands.get_mut(index) {
band.frequency = frequency;
band.gain_db = gain_db;
band.q = q;
band.update_coefficients(sample_rate);
}
}
pub fn reset(&mut self) {
for band in &mut self.bands {
band.reset();
}
}
pub fn process(&mut self, input: f32, _time: f32, _sample_index: usize) -> f32 {
let mut output = input;
for band in &mut self.bands {
output = band.process(output);
}
output
}
#[inline]
pub fn process_block(&mut self, buffer: &mut [f32], time: f32, sample_index: usize, sample_rate: f32) {
let time_delta = 1.0 / sample_rate;
for (i, sample) in buffer.iter_mut().enumerate() {
let current_time = time + (i as f32 * time_delta);
let current_sample_index = sample_index + i;
*sample = self.process(*sample, current_time, current_sample_index);
}
}
}
impl Default for ParametricEQ {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EQPreset {
VocalClarity,
BassBoost,
BrightAiry,
Telephone,
Warmth,
}