ruffbox_synth 0.14.0

A beatbox-oriented synthesizer library.
Documentation
/// rust implementation of Nathan Ho's SafetyLimiter https://github.com/nhthn/safety-limiter
use rubato::{
    Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
};

pub struct RockWallLimiter<const BUFSIZE: usize, const NCHAN: usize> {
    release: f32,
    hold_time: i32,
    hold_timer: i32,
    last_amplitude: f32,
    upsampler: SincFixedIn<f32>,
    upsample_buffers: Vec<Vec<f32>>,
    // if true, this will only detect on channel 0
    pub ambi_mode: bool,
}

#[inline(always)]
fn sanitize(x: f32) -> f32 {
    if f32::is_infinite(x) {
        return 0.0;
    }

    if f32::is_nan(x) {
        return 0.0;
    }

    if x.abs() < f32::MIN_POSITIVE {
        return 0.0;
    }

    x
}

impl<const BUFSIZE: usize, const NCHAN: usize> RockWallLimiter<BUFSIZE, NCHAN> {
    pub fn new(samplerate: f32, ambi_mode: bool) -> Self {
        let params = SincInterpolationParameters {
            sinc_len: 256,
            f_cutoff: 0.95,
            interpolation: SincInterpolationType::Linear,
            oversampling_factor: 256,
            window: WindowFunction::BlackmanHarris2,
        };

        let upsampler = SincFixedIn::new(4.0, 4.0, params, BUFSIZE, NCHAN).unwrap();
        let upsampler_bufsize = upsampler.output_frames_max();

        RockWallLimiter {
            hold_time: (0.1 * samplerate) as i32,
            hold_timer: 0,
            release: f32::powf(0.001, 1.0 / (0.1 * samplerate)),
            last_amplitude: 0.0,
            upsampler,
            upsample_buffers: (0..NCHAN).map(|_| vec![0.0; upsampler_bufsize]).collect(),
            ambi_mode,
        }
    }

    #[inline(always)]
    pub fn process_frame(&mut self, input: &mut [f32; NCHAN], sidechain: f32) {
        let inst_amp = sanitize(sidechain).abs();

        let amp_from_follower = if self.hold_timer > 0 {
            self.hold_timer -= 1;
            self.last_amplitude
        } else {
            self.last_amplitude * self.release + inst_amp * (1.0 - self.release)
        };

        let amplitude = if amp_from_follower > inst_amp {
            amp_from_follower
        } else {
            self.hold_timer = self.hold_time;
            inst_amp
        };
        self.last_amplitude = sanitize(amplitude);

        let gain = if amplitude > 1.0 {
            1.0 / amplitude
        } else {
            1.0
        };

        for c in 0..NCHAN {
            input[c] = sanitize(input[c]) * gain;
        }
    }

    pub fn process(&mut self, block: [[f32; BUFSIZE]; NCHAN]) -> [[f32; BUFSIZE]; NCHAN] {
        let mut out_buf = [[0.0; BUFSIZE]; NCHAN];
        let mut workbuf = [0.0; NCHAN];

        // upsample for true peak limiting
        if self.ambi_mode {
            // as we only detect on channel 0 for ambisonics, we only need to upsample
            // channel 0 ...
            let mut mask = [false; NCHAN];
            mask[0] = true;
            self.upsampler
                .process_into_buffer(&block, &mut self.upsample_buffers, Some(&mask))
                .unwrap();
        } else {
            // otherwise, upsample all channels
            self.upsampler
                .process_into_buffer(&block, &mut self.upsample_buffers, None)
                .unwrap();
        }

        for s in 0..BUFSIZE {
            // find true peak over all channels, create channel coupling ...
            let mut true_peak = f32::MIN;
            if self.ambi_mode {
                // only detect on channel 0
                true_peak = f32::max(true_peak, f32::abs(block[0][s]));
                // 4x upsampling
                for j in 0..4 {
                    true_peak = f32::max(true_peak, f32::abs(self.upsample_buffers[0][s * 4 + j]));
                }
                for c in 0..NCHAN {
                    workbuf[c] = block[c][s];
                }
            } else {
                for c in 0..NCHAN {
                    true_peak = f32::max(true_peak, f32::abs(block[c][s]));
                    // 4x upsampling
                    for j in 0..4 {
                        true_peak =
                            f32::max(true_peak, f32::abs(self.upsample_buffers[c][s * 4 + j]));
                    }
                    workbuf[c] = block[c][s];
                }
            }

            self.process_frame(&mut workbuf, true_peak);

            for c in 0..NCHAN {
                out_buf[c][s] = workbuf[c];
            }
        }

        out_buf
    }
}