dirtydata-runtime 0.1.0

High-performance JIT-enabled audio engine for DirtyData forensic graphs
Documentation
use super::base::*;
use dirtydata_core::types::ConfigSnapshot;

pub struct GainNode {
    pub gain_smooth: Option<SmoothedValue>,
}

impl GainNode {
    pub fn new() -> Self { Self { gain_smooth: None } }
}

impl DspNode for GainNode {
    fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
        let gain_db_target = config.get("gain_db").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
        let smooth = self.gain_smooth.get_or_insert_with(|| SmoothedValue::new(gain_db_target, ctx.sample_rate, 10.0));
        let linear = 10.0_f32.powf(smooth.next() / 20.0);
        if inputs.len() >= 2 {
            outputs[0] = [inputs[0] * linear, inputs[1] * linear];
        }
    }
}

pub struct BiquadFilterNode {
    z1: [f32; 2],
    z2: [f32; 2],
    freq_smooth: Option<SmoothedValue>,
}

impl BiquadFilterNode {
    pub fn new() -> Self { Self { z1: [0.0, 0.0], z2: [0.0, 0.0], freq_smooth: None } }
}

impl DspNode for BiquadFilterNode {
    fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
        let freq_target = config.get("frequency").and_then(|v| v.as_float()).unwrap_or(1000.0) as f32;
        let q = config.get("q").and_then(|v| v.as_float()).unwrap_or(0.707) as f32;
        let smooth = self.freq_smooth.get_or_insert_with(|| SmoothedValue::new(freq_target, ctx.sample_rate, 10.0));
        let w0 = 2.0 * std::f32::consts::PI * smooth.next() / ctx.sample_rate;
        let alpha = w0.sin() / (2.0 * q);
        let cos_w0 = w0.cos();
        let b0 = (1.0 - cos_w0) / 2.0;
        let b1 = 1.0 - cos_w0;
        let b2 = (1.0 - cos_w0) / 2.0;
        let a0 = 1.0 + alpha;
        let a1 = -2.0 * cos_w0;
        let a2 = 1.0 - alpha;
        let inv_a0 = 1.0 / a0;
        for i in 0..2 {
            let x = if inputs.len() > i { inputs[i] } else { 0.0 };
            let y = (b0 * x + self.z1[i]) * inv_a0;
            self.z1[i] = b1 * x - a1 * y + self.z2[i];
            self.z2[i] = b2 * x - a2 * y;
            outputs[0][i] = y;
        }
    }
}

pub struct CompressorNode {
    envelope: f32,
}

impl CompressorNode {
    pub fn new() -> Self { Self { envelope: 0.0 } }
}

impl DspNode for CompressorNode {
    fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
        let threshold_db = config.get("threshold_db").and_then(|v| v.as_float()).unwrap_or(-20.0) as f32;
        let ratio = config.get("ratio").and_then(|v| v.as_float()).unwrap_or(4.0) as f32;
        let threshold = 10.0_f32.powf(threshold_db / 20.0);
        let peak = if inputs.len() >= 2 { inputs[0].abs().max(inputs[1].abs()) } else { 0.0 };
        self.envelope += 0.1 * (peak - self.envelope);
        let gain = if self.envelope > threshold {
            let over_db = 20.0 * (self.envelope / threshold).log10();
            10.0_f32.powf(-(over_db * (1.0 - 1.0 / ratio)) / 20.0)
        } else { 1.0 };
        if inputs.len() >= 2 {
            outputs[0] = [inputs[0] * gain, inputs[1] * gain];
        }
    }
}