#![allow(incomplete_features)] #![feature(generic_associated_types)] use baseplug::{Plugin, ProcessContext}; use serde::{Deserialize, Serialize}; use std::f32::consts::PI; use std::f32::consts::{FRAC_1_SQRT_2, FRAC_PI_2}; const EPS: f32 = 1.18e-37; baseplug::model! { #[derive(Debug, Serialize, Deserialize)] struct XLowpassModel { #[model(min = 0.0, max = 1.0)] #[parameter(name = "gain")] gain: f32, #[model(min = 0.5, max = 1.0)] #[parameter(name = "freq")] freq: f32, #[model(min = 0.0, max = 0.5)] #[parameter(name = "nuke")] nuke: f32, } } impl Default for XLowpassModel { fn default() -> Self { Self { gain: 0.5, freq: 1.0, nuke: 0.0, } } } struct XLowpass { biquad: [f32; 15], biquad_a: [f32; 15], biquad_b: [f32; 15], biquad_c: [f32; 15], biquad_d: [f32; 15], fpd_l: u32, fpd_r: u32, } impl Plugin for XLowpass { const NAME: &'static str = "XLowpass"; const PRODUCT: &'static str = "XLowpass"; const VENDOR: &'static str = "unnieversal"; const INPUT_CHANNELS: usize = 2; const OUTPUT_CHANNELS: usize = 2; type Model = XLowpassModel; #[inline] fn new(_sample_rate: f32, _model: &XLowpassModel) -> Self { Self { biquad: [0.0; 15], biquad_a: [0.0; 15], biquad_b: [0.0; 15], biquad_c: [0.0; 15], biquad_d: [0.0; 15], fpd_l: 8638612, fpd_r: 5638657, } } #[inline] fn process(&mut self, model: &XLowpassModelProcess, ctx: &mut ProcessContext) { let input = &ctx.inputs[0].buffers; let output = &mut ctx.outputs[0].buffers; for i in 0..ctx.nframes { let gain = (model.gain[i] + 0.5).powi(4); self.biquad_a[0] = (model.freq[i].powi(2) * 20000.0) / ctx.sample_rate; if self.biquad_a[0] < 0.001 { self.biquad_a[0] = 0.001; } let compensation = self.biquad_a[0].sqrt() * 6.4; let clip_factor = 1.0 + (self.biquad_a[0].powi(2) * model.nuke[i] * 32.0); let k = (PI * self.biquad_a[0]).tan(); let norm = 1.0 / (1.0 + k / FRAC_1_SQRT_2 + k * k); self.biquad_a[2] = k * k * norm; self.biquad_a[3] = 2.0 * self.biquad_a[2]; self.biquad_a[4] = self.biquad_a[2]; self.biquad_a[5] = 2.0 * (k * k - 1.0) * norm; self.biquad_a[6] = (1.0 - k / FRAC_1_SQRT_2 + k * k) * norm; for j in 0..7 { self.biquad[j] = self.biquad_a[j]; self.biquad_b[j] = self.biquad_a[j]; self.biquad_c[j] = self.biquad_a[j]; self.biquad_d[j] = self.biquad_a[j]; } let mut a_wet = 1.0; let mut b_wet = 1.0; let mut c_wet = 1.0; let mut d_wet = model.nuke[i] * 4.0; //four-stage wet/dry control using progressive stages that bypass when not engaged if d_wet < 1.0 { a_wet = d_wet; b_wet = 0.0; c_wet = 0.0; d_wet = 0.0; } else if d_wet < 2.0 { b_wet = d_wet - 1.0; c_wet = 0.0; d_wet = 0.0; } else if d_wet < 3.0 { c_wet = d_wet - 2.0; d_wet = 0.0; } else { d_wet -= 3.0; } //this is one way to make a little set of dry/wet stages that are successively added to the //output as the control is turned up. Each one independently goes from 0-1 and stays at 1 //beyond that point: this is a way to progressively add a 'black box' sound processing //which lets you fall through to simpler processing at lower settings. // Begin processing let mut in_l = input[0][i]; let mut in_r = input[1][i]; if in_l.abs() < EPS { in_l = self.fpd_l as f32 * EPS; } if in_r.abs() < EPS { in_r = self.fpd_r as f32 * EPS; } in_l *= gain; in_r *= gain; let mut nuke_level_l = in_l; let mut nuke_level_r = in_r; self.set_initial(&mut in_l, &mut nuke_level_l, clip_factor, compensation); self.set_initial(&mut in_r, &mut nuke_level_r, clip_factor, compensation); if a_wet > 0. { self.calc( &mut in_l, Biquad::A, &mut &mut nuke_level_l, clip_factor, compensation, a_wet, ); self.calc( &mut in_r, Biquad::A, &mut &mut nuke_level_r, clip_factor, compensation, a_wet, ); } if b_wet > 0. { self.calc( &mut in_l, Biquad::B, &mut &mut nuke_level_l, clip_factor, compensation, b_wet, ); self.calc( &mut in_r, Biquad::B, &mut &mut nuke_level_r, clip_factor, compensation, b_wet, ); } if c_wet > 0. { self.calc( &mut in_l, Biquad::C, &mut &mut nuke_level_l, clip_factor, compensation, c_wet, ); self.calc( &mut in_r, Biquad::C, &mut &mut nuke_level_r, clip_factor, compensation, c_wet, ); } if d_wet > 0. { self.calc( &mut in_l, Biquad::D, &mut &mut nuke_level_l, clip_factor, compensation, d_wet, ); self.calc( &mut in_r, Biquad::D, &mut &mut nuke_level_r, clip_factor, compensation, d_wet, ); } //begin 32 bit stereo floating point dither let expon = frexp(in_l); self.fpd_l ^= self.fpd_l << 13; self.fpd_l ^= self.fpd_l >> 17; self.fpd_l ^= self.fpd_l << 5; in_l += (self.fpd_l as f32 - 0x7fffffff_i32 as f32) * 5.5e-36 * 2f32.powi(expon + 62); let expon = frexp(in_r); self.fpd_r ^= self.fpd_r << 13; self.fpd_r ^= self.fpd_r >> 17; self.fpd_r ^= self.fpd_r << 5; in_l += (self.fpd_r as f32 - 0x7fffffff_i32 as f32) * 5.5e-36 * 2f32.powi(expon + 62); //end 32 bit stereo floating point dither output[0][i] = in_l; output[1][i] = in_r; } } } fn frexp(val: f32) -> i32 { let mut a = 0; unsafe { frexpf(val, &mut a as *mut i32); a } } extern "C" { fn frexpf(_: libc::c_float, _: *mut libc::c_int) -> libc::c_float; } enum Biquad { A, B, C, D, } impl XLowpass { fn get_biquad(&mut self, b: Biquad) -> &mut [f32] { match b { Biquad::A => &mut self.biquad_a, Biquad::B => &mut self.biquad_b, Biquad::C => &mut self.biquad_c, Biquad::D => &mut self.biquad_d, } } fn calc( &mut self, in_l: &mut f32, b: Biquad, nuke_level_l: &mut f32, clip_factor: f32, compensation: f32, a_wet: f32, ) { let biquad = self.get_biquad(b); let mut out_sample = biquad[2] * *in_l + biquad[3] * biquad[7] + biquad[4] * biquad[8] - biquad[5] * biquad[9] - biquad[6] * biquad[10]; biquad[8] = biquad[7]; biquad[7] = *in_l; biquad[10] = biquad[9]; out_sample *= clip_factor; out_sample = out_sample.clamp(-FRAC_PI_2, FRAC_PI_2); self.biquad[9] = out_sample.sin(); *in_l = out_sample / compensation; *nuke_level_l = *in_l; *in_l = (*in_l * a_wet) + (*nuke_level_l * (1.0 - a_wet)); *nuke_level_l = *in_l; } fn set_initial( &mut self, in_l: &mut f32, nuke_level_l: &mut f32, clip_factor: f32, compensation: f32, ) { let mut out_sample = self.biquad[2] * *in_l + self.biquad[3] * self.biquad[7] + self.biquad[4] * self.biquad[8] - self.biquad[5] * self.biquad[9] - self.biquad[6] * self.biquad[10]; self.biquad[8] = self.biquad[7]; self.biquad[7] = *in_l; self.biquad[10] = self.biquad[9]; out_sample *= clip_factor; out_sample = out_sample.clamp(-FRAC_PI_2, FRAC_PI_2); self.biquad[9] = out_sample.sin(); *in_l = out_sample / compensation; *nuke_level_l = *in_l; } } baseplug::vst2!(XLowpass, b"xlow");