316 lines
9.3 KiB
Rust
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");
|