Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIH-Plug integration #12

Merged
merged 21 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6d14b44
refactor: move diodeclipper/svfmixer to nih-plug integration
SolarLiner Feb 16, 2024
f77e6f3
fix: cascade lowpass cutoff compensation
SolarLiner Feb 16, 2024
aa1a5e5
fix: oversampling?
SolarLiner Feb 16, 2024
262bcf8
refactor: interpolation module
SolarLiner Feb 17, 2024
89aa042
refactor: oversample module to use interpolation primitives instead o…
SolarLiner Feb 17, 2024
d142288
feat: lanczos interpolator
SolarLiner Feb 17, 2024
acc333c
Revert "refactor: oversample module to use interpolation primitives i…
SolarLiner Feb 17, 2024
9b139cc
wip: biquad design mod with polynom struct
SolarLiner Feb 18, 2024
bc677f6
wip: butterworth + bilinear transform in design module
SolarLiner Feb 18, 2024
ac2b01a
fix(filters): readd biquad/mod.rs that went missing
SolarLiner Feb 22, 2024
8c6c8e6
test(filters): move existing biquad snapshots to new folder + add ana…
SolarLiner Feb 22, 2024
c82dcbb
Revert using oversampling with butterworth filters
SolarLiner Feb 23, 2024
bfc99f4
feat(nih-plug): provide AnyParam to allow bool/int/float param types …
SolarLiner Feb 23, 2024
530fba2
feat(nih-plug): use AnyParam in diode clipper example
SolarLiner Feb 23, 2024
99fc3ff
feat(examples): use new nih-plug integration for dirty-biquad
SolarLiner Feb 23, 2024
8fda9ca
feat(nih-plug): add parameter to add default enum value
SolarLiner Feb 23, 2024
1ce7076
feat(examples): make ladder example use nih-plug integration
SolarLiner Feb 23, 2024
480322a
feat(examples): update svfmixer example
SolarLiner Feb 23, 2024
d173466
fix(filters): deactivate designer module
SolarLiner Feb 23, 2024
3709c35
test(oversample): fix snapshots after changing to 16 cascades of biqu…
SolarLiner Feb 23, 2024
0d1fe6c
fix(biquad): merge conflicting biquad modules
SolarLiner Mar 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading