static-dsp 0.1.0

A no_std dsp library using const generics in Rust.
use super::{AllPass, Comb, IntoSample, Node, Sample};

const FIXED_GAIN: f64 = 0.015;

const SCALE_WET: f64 = 3.0;
const SCALE_DAMPENING: f64 = 0.4;

const SCALE_ROOM: f64 = 0.28;
const OFFSET_ROOM: f64 = 0.7;

const STEREO_SPREAD: usize = 23;

const COMB_TUNING_L1: usize = 1116;
const COMB_TUNING_R1: usize = 1116 + STEREO_SPREAD;
const COMB_TUNING_L2: usize = 1188;
const COMB_TUNING_R2: usize = 1188 + STEREO_SPREAD;
const COMB_TUNING_L3: usize = 1277;
const COMB_TUNING_R3: usize = 1277 + STEREO_SPREAD;
const COMB_TUNING_L4: usize = 1356;
const COMB_TUNING_R4: usize = 1356 + STEREO_SPREAD;
const COMB_TUNING_L5: usize = 1422;
const COMB_TUNING_R5: usize = 1422 + STEREO_SPREAD;
const COMB_TUNING_L6: usize = 1491;
const COMB_TUNING_R6: usize = 1491 + STEREO_SPREAD;
const COMB_TUNING_L7: usize = 1557;
const COMB_TUNING_R7: usize = 1557 + STEREO_SPREAD;
const COMB_TUNING_L8: usize = 1617;
const COMB_TUNING_R8: usize = 1617 + STEREO_SPREAD;

const ALLPASS_TUNING_L1: usize = 556;
const ALLPASS_TUNING_R1: usize = 556 + STEREO_SPREAD;
const ALLPASS_TUNING_L2: usize = 441;
const ALLPASS_TUNING_R2: usize = 441 + STEREO_SPREAD;
const ALLPASS_TUNING_L3: usize = 341;
const ALLPASS_TUNING_R3: usize = 341 + STEREO_SPREAD;
const ALLPASS_TUNING_L4: usize = 225;
const ALLPASS_TUNING_R4: usize = 225 + STEREO_SPREAD;

pub struct Freeverb<T: Sample> {
    comb_1: (Comb<T, COMB_TUNING_L1>, Comb<T, COMB_TUNING_R1>),
    comb_2: (Comb<T, COMB_TUNING_L2>, Comb<T, COMB_TUNING_R2>),
    comb_3: (Comb<T, COMB_TUNING_L3>, Comb<T, COMB_TUNING_R3>),
    comb_4: (Comb<T, COMB_TUNING_L4>, Comb<T, COMB_TUNING_R4>),
    comb_5: (Comb<T, COMB_TUNING_L5>, Comb<T, COMB_TUNING_R5>),
    comb_6: (Comb<T, COMB_TUNING_L6>, Comb<T, COMB_TUNING_R6>),
    comb_7: (Comb<T, COMB_TUNING_L7>, Comb<T, COMB_TUNING_R7>),
    comb_8: (Comb<T, COMB_TUNING_L8>, Comb<T, COMB_TUNING_R8>),

    allpass_1: (AllPass<T, ALLPASS_TUNING_L1>, AllPass<T, ALLPASS_TUNING_R1>),
    allpass_2: (AllPass<T, ALLPASS_TUNING_L2>, AllPass<T, ALLPASS_TUNING_R2>),
    allpass_3: (AllPass<T, ALLPASS_TUNING_L3>, AllPass<T, ALLPASS_TUNING_R3>),
    allpass_4: (AllPass<T, ALLPASS_TUNING_L4>, AllPass<T, ALLPASS_TUNING_R4>),

    wet_gains: (T, T),
    wet: T,
    width: T,
    dry: T,
    input_gain: T,
    dampening: T,
    room_size: T,
    frozen: bool,
}

impl<T: Sample> Freeverb<T> {
    pub fn new() -> Self {
        let mut freeverb = Self {
            comb_1: (Comb::new(), Comb::new()),
            comb_2: (Comb::new(), Comb::new()),
            comb_3: (Comb::new(), Comb::new()),
            comb_4: (Comb::new(), Comb::new()),
            comb_5: (Comb::new(), Comb::new()),
            comb_6: (Comb::new(), Comb::new()),
            comb_7: (Comb::new(), Comb::new()),
            comb_8: (Comb::new(), Comb::new()),

            allpass_1: (AllPass::new(), AllPass::new()),
            allpass_2: (AllPass::new(), AllPass::new()),
            allpass_3: (AllPass::new(), AllPass::new()),
            allpass_4: (AllPass::new(), AllPass::new()),

            wet_gains: (T::equilibrium(), T::equilibrium()),
            wet: T::equilibrium(),
            width: T::equilibrium(),
            dry: T::equilibrium(),
            input_gain: T::equilibrium(),
            dampening: T::equilibrium(),
            room_size: T::equilibrium(),
            frozen: false,
        };

        freeverb.set_wet(1.0.into_sample());
        freeverb.set_width(0.5.into_sample());
        freeverb.set_dampening(0.5.into_sample());
        freeverb.set_room_size(0.5.into_sample());
        freeverb.set_frozen(false);

        freeverb
    }

    pub fn set_dampening(&mut self, value: T) {
        self.dampening = value * SCALE_DAMPENING.into_sample();
        self.update_combs();
    }

    pub fn set_freeze(&mut self, frozen: bool) {
        self.frozen = frozen;
        self.update_combs();
    }

    pub fn set_wet(&mut self, value: T) {
        self.wet = value * SCALE_WET.into_sample();
        self.update_wet_gains();
    }

    pub fn set_width(&mut self, value: T) {
        self.width = value;
        self.update_wet_gains();
    }

    fn update_wet_gains(&mut self) {
        self.wet_gains = (
            self.wet * (self.width / 2.0.into_sample() + 0.5.into_sample()),
            self.wet * ((1.0.into_sample::<T>() - self.width) / 2.0.into_sample()),
        )
    }

    fn set_frozen(&mut self, frozen: bool) {
        self.frozen = frozen;
        self.input_gain = if frozen { T::equilibrium() } else { T::peak() };
        self.update_combs();
    }

    pub fn set_room_size(&mut self, value: T) {
        self.room_size = value * SCALE_ROOM.into_sample() + OFFSET_ROOM.into_sample();
        self.update_combs();
    }

    fn update_combs(&mut self) {
        let (feedback, dampening) = if self.frozen {
            (T::peak(), T::equilibrium())
        } else {
            (self.room_size, self.dampening)
        };

        self.comb_1.0.set_feedback(feedback);
        self.comb_1.0.set_dampening(dampening);
        self.comb_1.1.set_feedback(feedback);
        self.comb_1.1.set_dampening(dampening);

        self.comb_2.0.set_feedback(feedback);
        self.comb_2.0.set_dampening(dampening);
        self.comb_2.1.set_feedback(feedback);
        self.comb_2.1.set_dampening(dampening);

        self.comb_3.0.set_feedback(feedback);
        self.comb_3.0.set_dampening(dampening);
        self.comb_3.1.set_feedback(feedback);
        self.comb_3.1.set_dampening(dampening);

        self.comb_4.0.set_feedback(feedback);
        self.comb_4.0.set_dampening(dampening);
        self.comb_4.1.set_feedback(feedback);
        self.comb_4.1.set_dampening(dampening);

        self.comb_5.0.set_feedback(feedback);
        self.comb_5.0.set_dampening(dampening);
        self.comb_5.1.set_feedback(feedback);
        self.comb_5.1.set_dampening(dampening);

        self.comb_6.0.set_feedback(feedback);
        self.comb_6.0.set_dampening(dampening);
        self.comb_6.1.set_feedback(feedback);
        self.comb_6.1.set_dampening(dampening);

        self.comb_7.0.set_feedback(feedback);
        self.comb_7.0.set_dampening(dampening);
        self.comb_7.1.set_feedback(feedback);
        self.comb_7.1.set_dampening(dampening);

        self.comb_8.0.set_feedback(feedback);
        self.comb_8.0.set_dampening(dampening);
        self.comb_8.1.set_feedback(feedback);
        self.comb_8.1.set_dampening(dampening);
    }

    pub fn set_dry(&mut self, value: T) {
        self.dry = value;
    }
}

impl<T: Sample> Node<(T, T), (T, T)> for Freeverb<T> {
    #[inline]
    fn process(&mut self, input: (T, T)) -> (T, T) {
        let input_mixed = (input.0 + input.1) * FIXED_GAIN.into_sample() * self.input_gain;

        let mut output = (T::equilibrium(), T::equilibrium());

        output.0 += self.comb_1.0.process(input_mixed);
        output.1 += self.comb_1.1.process(input_mixed);

        output.0 += self.comb_2.0.process(input_mixed);
        output.1 += self.comb_2.1.process(input_mixed);

        output.0 += self.comb_3.0.process(input_mixed);
        output.1 += self.comb_3.1.process(input_mixed);

        output.0 += self.comb_4.0.process(input_mixed);
        output.1 += self.comb_4.1.process(input_mixed);

        output.0 += self.comb_5.0.process(input_mixed);
        output.1 += self.comb_5.1.process(input_mixed);

        output.0 += self.comb_6.0.process(input_mixed);
        output.1 += self.comb_6.1.process(input_mixed);

        output.0 += self.comb_7.0.process(input_mixed);
        output.1 += self.comb_7.1.process(input_mixed);

        output.0 += self.comb_8.0.process(input_mixed);
        output.1 += self.comb_8.1.process(input_mixed);

        output.0 = self.allpass_1.0.process(output.0);
        output.1 = self.allpass_1.1.process(output.1);

        output.0 = self.allpass_2.0.process(output.0);
        output.1 = self.allpass_2.1.process(output.1);

        output.0 = self.allpass_3.0.process(output.0);
        output.1 = self.allpass_3.1.process(output.1);

        output.0 = self.allpass_4.0.process(output.0);
        output.1 = self.allpass_4.1.process(output.1);

        (
            output.0 * self.wet_gains.0 + output.1 * self.wet_gains.1 + input.0 * self.dry,
            output.1 * self.wet_gains.1 + output.0 * self.wet_gains.0 + input.1 * self.dry,
        )
    }
}