unnieversal/crates/xlowpass/src/lib.rs

316 lines
9.3 KiB
Rust

#![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<Self>) {
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");