Skip to content

Commit

Permalink
Merge pull request #12 from SolarLiner/feat/nih-plug
Browse files Browse the repository at this point in the history
NIH-Plug integration
  • Loading branch information
SolarLiner authored Mar 22, 2024
2 parents 8c88c6c + 0d1fe6c commit 8c3c20a
Show file tree
Hide file tree
Showing 42 changed files with 4,525 additions and 3,390 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ keywords = ["virtual-analog", "audio", "plugin", "va-modeling", "dsp"]
enum-map = "2.7.3"
nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git" }
nih_plug_vizia = { git = "https://github.com/robbert-vdh/nih-plug.git" }
num-traits = "0.2.18"
nalgebra = "0.32.3"


Expand All @@ -29,7 +30,8 @@ az = "1.2.1"
enum-map.workspace = true
fundsp = { version = "0.16.0", optional = true }
nalgebra.workspace = true
num-traits = "0.2.18"
nih_plug = { workspace = true, optional = true }
num-traits.workspace = true
numeric-array = { version = "0.5.2", optional = true }
numeric_literals = "0.2.0"
portable-atomic = { version = "1.6.0", features = ["float"] }
Expand All @@ -42,7 +44,12 @@ rstest = "0.18.2"
serde = "*"

[features]
default = ["biquad-design", "oversample"]
biquad-design = ["math-polynom"]
math-polynom = []
oversample = ["biquad-design"]
fundsp = ["dep:fundsp", "dep:numeric-array", "dep:typenum"]
nih-plug = ["dep:nih_plug"]

[profile.dev]
opt-level = 1
Expand Down
6 changes: 4 additions & 2 deletions examples/diodeclipper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ keywords.workspace = true
crate-type = ["cdylib"]

[dependencies]
nih_plug = { workspace = true }
valib = { path = "../.." }
enum-map.workspace = true
nih_plug.workspace = true
num-traits.workspace = true
valib = { path = "../..", features = ["nih-plug"] }
245 changes: 245 additions & 0 deletions examples/diodeclipper/src/dsp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
use enum_map::Enum;
use nih_plug::util::gain_to_db_fast;
use num_traits::Zero;
use std::fmt;
use std::fmt::Formatter;

use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam};
use valib::dsp::{DSPBlock, DSP};
use valib::filters::biquad::Biquad;
use valib::oversample::{Oversample, Oversampled};
use valib::saturators::clippers::{DiodeClipper, DiodeClipperModel};
use valib::saturators::Linear;
use valib::simd::{AutoF32x2, AutoF64x2, SimdComplexField};
use valib::{Scalar, SimdCast};

struct DcBlocker<T>(Biquad<T, Linear>);

impl<T> DcBlocker<T> {
const CUTOFF_HZ: f32 = 5.0;
const Q: f32 = 0.707;
fn new(samplerate: f32) -> Self
where
T: Scalar,
{
Self(Biquad::highpass(
T::from_f64((Self::CUTOFF_HZ / samplerate) as f64),
T::from_f64(Self::Q as f64),
))
}
}

impl<T: Scalar> DSP<1, 1> for DcBlocker<T> {
type Sample = T;

fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] {
self.0.process(x)
}

fn set_samplerate(&mut self, samplerate: f32) {
DSP::set_samplerate(&mut self.0, samplerate);
self.0.update_coefficients(&Biquad::highpass(
T::from_f64((Self::CUTOFF_HZ / samplerate) as f64),
T::from_f64(Self::Q as f64),
));
}

fn latency(&self) -> usize {
DSP::latency(&self.0)
}

fn reset(&mut self) {
self.0.reset()
}
}

type Sample = AutoF32x2;
type Sample64 = AutoF64x2;

#[derive(Debug, Copy, Clone, Eq, PartialEq, Enum)]
pub enum DiodeType {
Silicon,
Germanium,
Led,
}

impl fmt::Display for DiodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Silicon => write!(f, "Silicon"),
Self::Germanium => write!(f, "Germanium"),
Self::Led => write!(f, "LED"),
}
}
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Enum)]
pub enum DspParams {
Drive,
ModelSwitch,
DiodeType,
NumForward,
NumBackward,
ForceReset,
}

pub struct DspInner {
drive: SmoothedParam,
model_switch: Parameter,
num_forward: Parameter,
num_backward: Parameter,
diode_type: Parameter,
force_reset: Parameter,
nr_model: DiodeClipperModel<Sample64>,
nr_nr: DiodeClipper<Sample64>,
}

impl DspInner {
fn new(samplerate: f32) -> Self {
Self {
drive: Parameter::new(1.0).smoothed_exponential(samplerate, 10.0),
model_switch: Parameter::new(0.0),
num_forward: Parameter::new(1.0),
num_backward: Parameter::new(1.0),
diode_type: Parameter::new(0.0),
force_reset: Parameter::new(0.0),
nr_model: DiodeClipperModel::new_silicon(1, 1),
nr_nr: DiodeClipper::new_silicon(1, 1, Sample64::zero()),
}
}

fn update_from_params(&mut self) {
if self.num_forward.has_changed()
|| self.num_backward.has_changed()
|| self.diode_type.has_changed()
{
let num_fwd = self.num_forward.get_value() as _;
let num_bck = self.num_backward.get_value() as _;
self.nr_model = match self.diode_type.get_enum::<DiodeType>() {
DiodeType::Silicon => DiodeClipperModel::new_silicon(num_fwd, num_bck),
DiodeType::Germanium => DiodeClipperModel::new_germanium(num_fwd, num_bck),
DiodeType::Led => DiodeClipperModel::new_led(num_fwd, num_bck),
};
let last_vout = self.nr_nr.last_output();
self.nr_nr = match self.diode_type.get_enum::<DiodeType>() {
DiodeType::Silicon => {
DiodeClipper::new_silicon(num_fwd as usize, num_bck as usize, last_vout)
}
DiodeType::Germanium => {
DiodeClipper::new_germanium(num_fwd as usize, num_bck as usize, last_vout)
}
DiodeType::Led => {
DiodeClipper::new_led(num_fwd as usize, num_bck as usize, last_vout)
}
};
}
}
}

impl HasParameters for DspInner {
type Enum = DspParams;

fn get_parameter(&self, param: Self::Enum) -> &Parameter {
match param {
DspParams::Drive => &self.drive.param,
DspParams::ModelSwitch => &self.model_switch,
DspParams::DiodeType => &self.diode_type,
DspParams::NumForward => &self.num_forward,
DspParams::NumBackward => &self.num_backward,
DspParams::ForceReset => &self.force_reset,
}
}
}

impl DSP<1, 1> for DspInner {
type Sample = Sample;

fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] {
if self.force_reset.has_changed() {
DSP::reset(self)
}
self.update_from_params();

let drive = Sample64::from_f64(self.drive.next_sample() as _);
let x64 = x.map(|x| x.cast() * drive);
if self.model_switch.get_bool() {
self.nr_model.process(x64)
} else {
self.nr_nr.process(x64)
}
.map(|x| x / drive.simd_asinh())
.map(|x| x.cast())
}

fn set_samplerate(&mut self, samplerate: f32) {
DSP::set_samplerate(&mut self.nr_model, samplerate);
DSP::set_samplerate(&mut self.nr_nr, samplerate);
}

fn latency(&self) -> usize {
if self.model_switch.get_bool() {
DSP::latency(&self.nr_model)
} else {
DSP::latency(&self.nr_nr)
}
}

fn reset(&mut self) {
DSP::reset(&mut self.nr_model);
DSP::reset(&mut self.nr_nr);
}
}

pub struct Dsp {
inner: Oversampled<Sample, DspInner>,
max_oversampling: usize,
dc_blocker: DcBlocker<Sample>,
}

impl DSPBlock<1, 1> for Dsp {
type Sample = Sample;

fn process_block(&mut self, inputs: &[[Self::Sample; 1]], outputs: &mut [[Self::Sample; 1]]) {
self.inner.process_block(inputs, outputs);

for o in outputs {
*o = self.dc_blocker.process(*o);
}
}

fn set_samplerate(&mut self, samplerate: f32) {
DSPBlock::set_samplerate(&mut self.inner, samplerate);
DSPBlock::set_samplerate(&mut self.dc_blocker, samplerate);
}

fn max_block_size(&self) -> Option<usize> {
DSPBlock::max_block_size(&self.inner)
}

fn latency(&self) -> usize {
DSPBlock::latency(&self.inner) + DSPBlock::latency(&self.dc_blocker)
}

fn reset(&mut self) {
DSPBlock::reset(&mut self.inner);
DSPBlock::reset(&mut self.dc_blocker);
}
}

impl HasParameters for Dsp {
type Enum = DspParams;

fn get_parameter(&self, param: Self::Enum) -> &Parameter {
self.inner.get_parameter(param)
}
}

pub fn create_dsp(samplerate: f32, oversample: usize, max_block_size: usize) -> Dsp {
let mut inner =
Oversample::new(oversample, max_block_size).with_dsp(samplerate, DspInner::new(samplerate));
Dsp {
inner,
max_oversampling: oversample,
dc_blocker: DcBlocker::new(samplerate),
}
}
Loading

0 comments on commit 8c3c20a

Please sign in to comment.