From 808547dc16de49d2e791a38ecfd3ca9bac98125c Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Fri, 5 Aug 2022 06:45:06 +0200 Subject: [PATCH] Refactored out the DSP helper stuff into the synfx-dsp crate --- Cargo.toml | 3 +- src/dsp/biquad.rs | 272 ---- src/dsp/dattorro.rs | 443 ------ src/dsp/helpers.rs | 2697 ---------------------------------- src/dsp/mod.rs | 18 +- src/dsp/node_ad.rs | 2 +- src/dsp/node_allp.rs | 2 +- src/dsp/node_biqfilt.rs | 2 +- src/dsp/node_bosc.rs | 2 +- src/dsp/node_bowstri.rs | 3 +- src/dsp/node_comb.rs | 6 +- src/dsp/node_cqnt.rs | 2 +- src/dsp/node_delay.rs | 2 +- src/dsp/node_mux9.rs | 2 +- src/dsp/node_noise.rs | 2 +- src/dsp/node_pverb.rs | 3 +- src/dsp/node_quant.rs | 2 +- src/dsp/node_rndwk.rs | 2 +- src/dsp/node_sampl.rs | 2 +- src/dsp/node_scope.rs | 2 +- src/dsp/node_sfilter.rs | 2 +- src/dsp/node_sin.rs | 2 +- src/dsp/node_test.rs | 2 +- src/dsp/node_tseq.rs | 2 +- src/dsp/node_tslfo.rs | 2 +- src/dsp/node_vosc.rs | 3 +- src/dsp/tracker/sequencer.rs | 2 +- src/nodes/mod.rs | 2 +- tests/delay_buffer.rs | 255 ---- tests/quant.rs | 2 +- 30 files changed, 36 insertions(+), 3707 deletions(-) delete mode 100644 src/dsp/biquad.rs delete mode 100644 src/dsp/dattorro.rs delete mode 100644 src/dsp/helpers.rs delete mode 100644 tests/delay_buffer.rs diff --git a/Cargo.toml b/Cargo.toml index 9746475..d3ffbb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ ringbuf = "0.2.2" triple_buffer = "5.0.6" lazy_static = "1.4.0" hound = "3.4.0" -num-traits = "0.2.14" +synfx-dsp = "0.5.1" +#synfx-dsp = { git = "https://github.com/WeirdConstructor/synfx-dsp" } [dev-dependencies] num-complex = "0.2" diff --git a/src/dsp/biquad.rs b/src/dsp/biquad.rs deleted file mode 100644 index fdc3738..0000000 --- a/src/dsp/biquad.rs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2021 Weird Constructor -// This file is a part of HexoDSP. Released under GPL-3.0-or-later. -// See README.md and COPYING for details. -// -// The implementation of this Biquad Filter has been adapted from -// SamiPerttu, Copyright (c) 2020, under the MIT License. -// See also: https://github.com/SamiPerttu/fundsp/blob/master/src/filter.rs -// -// You will find a float type agnostic version in SamiPerttu's code. -// I converted this to pure f32 for no good reason, other than making -// the code more readable (for me). - -use std::f32::consts::*; - -#[derive(Copy, Clone, Debug, Default)] -pub struct BiquadCoefs { - pub a1: f32, - pub a2: f32, - pub b0: f32, - pub b1: f32, - pub b2: f32, -} - -// TODO: -// https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp#L339 -// more coeffs from there ^^^^^^^^^^^^^ ? -impl BiquadCoefs { - #[inline] - pub fn new(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self { - Self { b0, b1, b2, a1, a2 } - } - - /// Returns settings for a Butterworth lowpass filter. - /// Cutoff is the -3 dB point of the filter in Hz. - #[inline] - pub fn butter_lowpass(sample_rate: f32, cutoff: f32) -> BiquadCoefs { - let f = (cutoff * PI / sample_rate).tan(); - let a0r = 1.0 / (1.0 + SQRT_2 * f + f * f); - let a1 = (2.0 * f * f - 2.0) * a0r; - let a2 = (1.0 - SQRT_2 * f + f * f) * a0r; - let b0 = f * f * a0r; - let b1 = 2.0 * b0; - let b2 = b0; - BiquadCoefs { a1, a2, b0, b1, b2 } - } - - /// Returns the Q for cascading a butterworth filter: - fn calc_cascaded_butter_q(order: usize, casc_idx: usize) -> f32 { - let order = order as f32; - let casc_idx = casc_idx as f32; - - let b = -2.0 * ((2.0 * casc_idx + order - 1.0) * PI / (2.0 * order)).cos(); - - 1.0 / b - } - - /// Returns settings for a lowpass filter with a specific q - #[inline] - pub fn lowpass(sample_rate: f32, q: f32, cutoff: f32) -> BiquadCoefs { - let f = (cutoff * PI / sample_rate).tan(); - let a0r = 1.0 / (1.0 + f / q + f * f); - - /* - float norm = 1.f / (1.f + K / Q + K * K); - this->b[0] = K * K * norm; - this->b[1] = 2.f * this->b[0]; - this->b[2] = this->b[0]; - this->a[1] = 2.f * (K * K - 1.f) * norm; - this->a[2] = (1.f - K / Q + K * K) * norm; - */ - - let b0 = f * f * a0r; - let b1 = 2.0 * b0; - let b2 = b0; - let a1 = 2.0 * (f * f - 1.0) * a0r; - let a2 = (1.0 - f / q + f * f) * a0r; - - BiquadCoefs { a1, a2, b0, b1, b2 } - } - - /// Returns settings for a constant-gain bandpass resonator. - /// The center frequency is given in Hz. - /// Bandwidth is the difference in Hz between -3 dB points of the filter response. - /// The overall gain of the filter is independent of bandwidth. - pub fn resonator(sample_rate: f32, center: f32, bandwidth: f32) -> BiquadCoefs { - let r = (-PI * bandwidth / sample_rate).exp(); - let a1 = -2.0 * r * (TAU * center / sample_rate).cos(); - let a2 = r * r; - let b0 = (1.0 - r * r).sqrt() * 0.5; - let b1 = 0.0; - let b2 = -b0; - BiquadCoefs { a1, a2, b0, b1, b2 } - } - - // /// Frequency response at frequency `omega` expressed as fraction of sampling rate. - // pub fn response(&self, omega: f64) -> Complex64 { - // let z1 = Complex64::from_polar(1.0, -TAU * omega); - // let z2 = Complex64::from_polar(1.0, -2.0 * TAU * omega); - // (re(self.b0) + re(self.b1) * z1 + re(self.b2) * z2) - // / (re(1.0) + re(self.a1) * z1 + re(self.a2) * z2) - // } -} - -/// 2nd order IIR filter implemented in normalized Direct Form I. -#[derive(Debug, Copy, Clone, Default)] -pub struct Biquad { - coefs: BiquadCoefs, - x1: f32, - x2: f32, - y1: f32, - y2: f32, -} - -impl Biquad { - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn new_with(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self { - let mut s = Self::new(); - s.set_coefs(BiquadCoefs::new(b0, b1, b2, a1, a2)); - s - } - - #[inline] - pub fn coefs(&self) -> &BiquadCoefs { - &self.coefs - } - - #[inline] - pub fn set_coefs(&mut self, coefs: BiquadCoefs) { - self.coefs = coefs; - } - - pub fn reset(&mut self) { - self.x1 = 0.0; - self.x2 = 0.0; - self.y1 = 0.0; - self.y2 = 0.0; - } - - #[inline] - pub fn tick(&mut self, input: f32) -> f32 { - let x0 = input; - let y0 = self.coefs.b0 * x0 + self.coefs.b1 * self.x1 + self.coefs.b2 * self.x2 - - self.coefs.a1 * self.y1 - - self.coefs.a2 * self.y2; - self.x2 = self.x1; - self.x1 = x0; - self.y2 = self.y1; - self.y1 = y0; - y0 - - // Transposed Direct Form II would be: - // y0 = b0 * x0 + s1 - // s1 = s2 + b1 * x0 - a1 * y0 - // s2 = b2 * x0 - a2 * y0 - } -} - -#[derive(Copy, Clone)] -pub struct ButterLowpass { - biquad: Biquad, - sample_rate: f32, - cutoff: f32, -} - -#[allow(dead_code)] -impl ButterLowpass { - pub fn new(sample_rate: f32, cutoff: f32) -> Self { - let mut this = ButterLowpass { biquad: Biquad::new(), sample_rate, cutoff: 0.0 }; - this.set_cutoff(cutoff); - this - } - - pub fn set_cutoff(&mut self, cutoff: f32) { - self.biquad.set_coefs(BiquadCoefs::butter_lowpass(self.sample_rate, cutoff)); - self.cutoff = cutoff; - } - - fn set_sample_rate(&mut self, srate: f32) { - self.sample_rate = srate; - self.reset(); - self.biquad.reset(); - self.set_cutoff(self.cutoff); - } - - fn reset(&mut self) { - self.biquad.reset(); - self.set_cutoff(self.cutoff); - } - - #[inline] - fn tick(&mut self, input: f32) -> f32 { - self.biquad.tick(input) - } -} - -// Loosely adapted from https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp -// Copyright (c) 2019-2020 Andrew Belt and Befaco contributors -// Under GPLv-3.0-or-later -// -// Which was originally taken from https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp -// Copyright (c) 2020 jatinchowdhury18 -/// Implements oversampling with a ratio of N and a 4 times cascade -/// of Butterworth lowpass filters (~48dB?). -#[derive(Debug, Copy, Clone)] -pub struct Oversampling { - filters: [Biquad; 4], - buffer: [f32; N], -} - -impl Oversampling { - pub fn new() -> Self { - let mut this = Self { filters: [Biquad::new(); 4], buffer: [0.0; N] }; - - this.set_sample_rate(44100.0); - - this - } - - pub fn reset(&mut self) { - self.buffer = [0.0; N]; - for filt in &mut self.filters { - filt.reset(); - } - } - - pub fn set_sample_rate(&mut self, srate: f32) { - let cutoff = 0.98 * (0.5 * srate); - - let ovr_srate = (N as f32) * srate; - let filters_len = self.filters.len(); - - for (i, filt) in self.filters.iter_mut().enumerate() { - let q = BiquadCoefs::calc_cascaded_butter_q(2 * 4, filters_len - i); - - filt.set_coefs(BiquadCoefs::lowpass(ovr_srate, q, cutoff)); - } - } - - #[inline] - pub fn upsample(&mut self, v: f32) { - self.buffer.fill(0.0); - self.buffer[0] = (N as f32) * v; - - for s in &mut self.buffer { - for filt in &mut self.filters { - *s = filt.tick(*s); - } - } - } - - #[inline] - pub fn resample_buffer(&mut self) -> &mut [f32; N] { - &mut self.buffer - } - - #[inline] - pub fn downsample(&mut self) -> f32 { - let mut ret = 0.0; - for s in &mut self.buffer { - ret = *s; - for filt in &mut self.filters { - ret = filt.tick(ret); - } - } - - ret - } -} diff --git a/src/dsp/dattorro.rs b/src/dsp/dattorro.rs deleted file mode 100644 index 8dd70ed..0000000 --- a/src/dsp/dattorro.rs +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright (c) 2021 Weird Constructor -// This file is a part of HexoDSP. Released under GPL-3.0-or-later. -// See README.md and COPYING for details. - -// This file contains a reverb implementation that is based -// on Jon Dattorro's 1997 reverb algorithm. It's also largely -// based on the C++ implementation from ValleyAudio / ValleyRackFree -// -// ValleyRackFree Copyright (C) 2020, Valley Audio Soft, Dale Johnson -// Adapted under the GPL-3.0-or-later License. -// -// See also: https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Plateau/Dattorro.cpp -// and: https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Plateau/Dattorro.hpp -// -// And: https://ccrma.stanford.edu/~dattorro/music.html -// And: https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf - -use crate::dsp::helpers::crossfade; - -const DAT_SAMPLE_RATE: f64 = 29761.0; -const DAT_SAMPLES_PER_MS: f64 = DAT_SAMPLE_RATE / 1000.0; - -const DAT_INPUT_APF_TIMES_MS: [f64; 4] = [ - 141.0 / DAT_SAMPLES_PER_MS, - 107.0 / DAT_SAMPLES_PER_MS, - 379.0 / DAT_SAMPLES_PER_MS, - 277.0 / DAT_SAMPLES_PER_MS, -]; - -const DAT_LEFT_APF1_TIME_MS: f64 = 672.0 / DAT_SAMPLES_PER_MS; -const DAT_LEFT_APF2_TIME_MS: f64 = 1800.0 / DAT_SAMPLES_PER_MS; - -const DAT_RIGHT_APF1_TIME_MS: f64 = 908.0 / DAT_SAMPLES_PER_MS; -const DAT_RIGHT_APF2_TIME_MS: f64 = 2656.0 / DAT_SAMPLES_PER_MS; - -const DAT_LEFT_DELAY1_TIME_MS: f64 = 4453.0 / DAT_SAMPLES_PER_MS; -const DAT_LEFT_DELAY2_TIME_MS: f64 = 3720.0 / DAT_SAMPLES_PER_MS; - -const DAT_RIGHT_DELAY1_TIME_MS: f64 = 4217.0 / DAT_SAMPLES_PER_MS; -const DAT_RIGHT_DELAY2_TIME_MS: f64 = 3163.0 / DAT_SAMPLES_PER_MS; - -const DAT_LEFT_TAPS_TIME_MS: [f64; 7] = [ - 266.0 / DAT_SAMPLES_PER_MS, - 2974.0 / DAT_SAMPLES_PER_MS, - 1913.0 / DAT_SAMPLES_PER_MS, - 1996.0 / DAT_SAMPLES_PER_MS, - 1990.0 / DAT_SAMPLES_PER_MS, - 187.0 / DAT_SAMPLES_PER_MS, - 1066.0 / DAT_SAMPLES_PER_MS, -]; - -const DAT_RIGHT_TAPS_TIME_MS: [f64; 7] = [ - 353.0 / DAT_SAMPLES_PER_MS, - 3627.0 / DAT_SAMPLES_PER_MS, - 1228.0 / DAT_SAMPLES_PER_MS, - 2673.0 / DAT_SAMPLES_PER_MS, - 2111.0 / DAT_SAMPLES_PER_MS, - 335.0 / DAT_SAMPLES_PER_MS, - 121.0 / DAT_SAMPLES_PER_MS, -]; - -const DAT_LFO_FREQS_HZ: [f64; 4] = [0.1, 0.15, 0.12, 0.18]; - -const DAT_INPUT_DIFFUSION1: f64 = 0.75; -const DAT_INPUT_DIFFUSION2: f64 = 0.625; -const DAT_PLATE_DIFFUSION1: f64 = 0.7; -const DAT_PLATE_DIFFUSION2: f64 = 0.5; - -const DAT_LFO_EXCURSION_MS: f64 = 16.0 / DAT_SAMPLES_PER_MS; -const DAT_LFO_EXCURSION_MOD_MAX: f64 = 16.0; - -use crate::dsp::helpers::{AllPass, DCBlockFilter, DelayBuffer, OnePoleHPF, OnePoleLPF, TriSawLFO}; - -#[derive(Debug, Clone)] -pub struct DattorroReverb { - last_scale: f64, - - inp_dc_block: [DCBlockFilter; 2], - out_dc_block: [DCBlockFilter; 2], - - lfos: [TriSawLFO; 4], - - input_hpf: OnePoleHPF, - input_lpf: OnePoleLPF, - - pre_delay: DelayBuffer, - input_apfs: [(AllPass, f64, f64); 4], - - apf1: [(AllPass, f64, f64); 2], - hpf: [OnePoleHPF; 2], - lpf: [OnePoleLPF; 2], - apf2: [(AllPass, f64, f64); 2], - delay1: [(DelayBuffer, f64); 2], - delay2: [(DelayBuffer, f64); 2], - - left_sum: f64, - right_sum: f64, - - dbg_count: usize, -} - -pub trait DattorroReverbParams { - /// Time for the pre-delay of the reverb. Any sensible `ms` that fits - /// into a delay buffer of 5 seconds. - fn pre_delay_time_ms(&self) -> f64; - /// The size of the reverb, values go from 0.0 to 1.0. - fn time_scale(&self) -> f64; - /// High-pass input filter cutoff freq in Hz, range: 0.0 to 22000.0 - fn input_high_cutoff_hz(&self) -> f64; - /// Low-pass input filter cutoff freq in Hz, range: 0.0 to 22000.0 - fn input_low_cutoff_hz(&self) -> f64; - /// High-pass reverb filter cutoff freq in Hz, range: 0.0 to 22000.0 - fn reverb_high_cutoff_hz(&self) -> f64; - /// Low-pass reverb filter cutoff freq in Hz, range: 0.0 to 22000.0 - fn reverb_low_cutoff_hz(&self) -> f64; - /// Modulation speed factor, range: 0.0 to 1.0 - fn mod_speed(&self) -> f64; - /// Modulation depth from the LFOs, range: 0.0 to 1.0 - fn mod_depth(&self) -> f64; - /// Modulation shape (from saw to tri to saw), range: 0.0 to 1.0 - fn mod_shape(&self) -> f64; - /// The mix between output from the pre-delay and the input diffusion. - /// range: 0.0 to 1.0. Default should be 1.0 - fn input_diffusion_mix(&self) -> f64; - /// The amount of plate diffusion going on, range: 0.0 to 1.0 - fn diffusion(&self) -> f64; - /// Internal tank decay time, range: 0.0 to 1.0 - fn decay(&self) -> f64; -} - -impl DattorroReverb { - pub fn new() -> Self { - let mut this = Self { - last_scale: 1.0, - - inp_dc_block: [DCBlockFilter::new(); 2], - out_dc_block: [DCBlockFilter::new(); 2], - - lfos: [TriSawLFO::new(); 4], - - input_hpf: OnePoleHPF::new(), - input_lpf: OnePoleLPF::new(), - - pre_delay: DelayBuffer::new(), - input_apfs: Default::default(), - - apf1: Default::default(), - hpf: [OnePoleHPF::new(); 2], - lpf: [OnePoleLPF::new(); 2], - apf2: Default::default(), - delay1: Default::default(), - delay2: Default::default(), - - left_sum: 0.0, - right_sum: 0.0, - - dbg_count: 0, - }; - - this.reset(); - - this - } - - pub fn reset(&mut self) { - self.input_lpf.reset(); - self.input_hpf.reset(); - - self.input_lpf.set_freq(22000.0); - self.input_hpf.set_freq(0.0); - - self.input_apfs[0] = (AllPass::new(), DAT_INPUT_APF_TIMES_MS[0], DAT_INPUT_DIFFUSION1); - self.input_apfs[1] = (AllPass::new(), DAT_INPUT_APF_TIMES_MS[1], DAT_INPUT_DIFFUSION1); - self.input_apfs[2] = (AllPass::new(), DAT_INPUT_APF_TIMES_MS[2], DAT_INPUT_DIFFUSION2); - self.input_apfs[3] = (AllPass::new(), DAT_INPUT_APF_TIMES_MS[3], DAT_INPUT_DIFFUSION2); - - self.apf1[0] = (AllPass::new(), DAT_LEFT_APF1_TIME_MS, -DAT_PLATE_DIFFUSION1); - self.apf1[1] = (AllPass::new(), DAT_RIGHT_APF1_TIME_MS, -DAT_PLATE_DIFFUSION1); - self.apf2[0] = (AllPass::new(), DAT_LEFT_APF2_TIME_MS, -DAT_PLATE_DIFFUSION2); - self.apf2[1] = (AllPass::new(), DAT_RIGHT_APF2_TIME_MS, -DAT_PLATE_DIFFUSION2); - - self.delay1[0] = (DelayBuffer::new(), DAT_LEFT_DELAY1_TIME_MS); - self.delay1[1] = (DelayBuffer::new(), DAT_RIGHT_DELAY1_TIME_MS); - self.delay2[0] = (DelayBuffer::new(), DAT_LEFT_DELAY2_TIME_MS); - self.delay2[1] = (DelayBuffer::new(), DAT_RIGHT_DELAY2_TIME_MS); - - self.lpf[0].reset(); - self.lpf[1].reset(); - self.lpf[0].set_freq(10000.0); - self.lpf[1].set_freq(10000.0); - - self.hpf[0].reset(); - self.hpf[1].reset(); - self.hpf[0].set_freq(0.0); - self.hpf[1].set_freq(0.0); - - self.lfos[0].set(DAT_LFO_FREQS_HZ[0], 0.5); - self.lfos[0].set_phase_offs(0.0); - self.lfos[0].reset(); - self.lfos[1].set(DAT_LFO_FREQS_HZ[1], 0.5); - self.lfos[1].set_phase_offs(0.25); - self.lfos[1].reset(); - self.lfos[2].set(DAT_LFO_FREQS_HZ[2], 0.5); - self.lfos[2].set_phase_offs(0.5); - self.lfos[2].reset(); - self.lfos[3].set(DAT_LFO_FREQS_HZ[3], 0.5); - self.lfos[3].set_phase_offs(0.75); - self.lfos[3].reset(); - - self.inp_dc_block[0].reset(); - self.inp_dc_block[1].reset(); - self.out_dc_block[0].reset(); - self.out_dc_block[1].reset(); - - self.pre_delay.reset(); - - self.left_sum = 0.0; - self.right_sum = 0.0; - - self.set_time_scale(1.0); - } - - #[inline] - pub fn set_time_scale(&mut self, scale: f64) { - if (self.last_scale - scale).abs() > std::f64::EPSILON { - let scale = scale.max(0.1); - self.last_scale = scale; - - self.apf1[0].1 = DAT_LEFT_APF1_TIME_MS * scale; - self.apf1[1].1 = DAT_RIGHT_APF1_TIME_MS * scale; - self.apf2[0].1 = DAT_LEFT_APF2_TIME_MS * scale; - self.apf2[1].1 = DAT_RIGHT_APF2_TIME_MS * scale; - - self.delay1[0].1 = DAT_LEFT_DELAY1_TIME_MS * scale; - self.delay1[1].1 = DAT_RIGHT_DELAY1_TIME_MS * scale; - self.delay2[0].1 = DAT_LEFT_DELAY2_TIME_MS * scale; - self.delay2[1].1 = DAT_RIGHT_DELAY2_TIME_MS * scale; - } - } - - pub fn set_sample_rate(&mut self, srate: f64) { - self.inp_dc_block[0].set_sample_rate(srate); - self.inp_dc_block[1].set_sample_rate(srate); - self.out_dc_block[0].set_sample_rate(srate); - self.out_dc_block[1].set_sample_rate(srate); - - self.lfos[0].set_sample_rate(srate); - self.lfos[1].set_sample_rate(srate); - self.lfos[2].set_sample_rate(srate); - self.lfos[3].set_sample_rate(srate); - - self.input_hpf.set_sample_rate(srate); - self.input_lpf.set_sample_rate(srate); - - self.pre_delay.set_sample_rate(srate); - - self.input_apfs[0].0.set_sample_rate(srate); - self.input_apfs[1].0.set_sample_rate(srate); - self.input_apfs[2].0.set_sample_rate(srate); - self.input_apfs[3].0.set_sample_rate(srate); - - self.apf1[0].0.set_sample_rate(srate); - self.apf1[1].0.set_sample_rate(srate); - self.apf2[0].0.set_sample_rate(srate); - self.apf2[1].0.set_sample_rate(srate); - - self.hpf[0].set_sample_rate(srate); - self.hpf[1].set_sample_rate(srate); - self.lpf[0].set_sample_rate(srate); - self.lpf[1].set_sample_rate(srate); - - self.delay1[0].0.set_sample_rate(srate); - self.delay1[1].0.set_sample_rate(srate); - self.delay2[0].0.set_sample_rate(srate); - self.delay2[1].0.set_sample_rate(srate); - } - - #[inline] - fn calc_apf_delay_times( - &mut self, - params: &mut dyn DattorroReverbParams, - ) -> (f64, f64, f64, f64) { - let left_apf1_delay_ms = self.apf1[0].1 - + (self.lfos[0].next_bipolar() as f64 - * DAT_LFO_EXCURSION_MS - * DAT_LFO_EXCURSION_MOD_MAX - * params.mod_depth()); - let right_apf1_delay_ms = self.apf1[1].1 - + (self.lfos[1].next_bipolar() as f64 - * DAT_LFO_EXCURSION_MS - * DAT_LFO_EXCURSION_MOD_MAX - * params.mod_depth()); - let left_apf2_delay_ms = self.apf2[0].1 - + (self.lfos[2].next_bipolar() as f64 - * DAT_LFO_EXCURSION_MS - * DAT_LFO_EXCURSION_MOD_MAX - * params.mod_depth()); - let right_apf2_delay_ms = self.apf2[1].1 - + (self.lfos[3].next_bipolar() as f64 - * DAT_LFO_EXCURSION_MS - * DAT_LFO_EXCURSION_MOD_MAX - * params.mod_depth()); - - (left_apf1_delay_ms, right_apf1_delay_ms, left_apf2_delay_ms, right_apf2_delay_ms) - } - - pub fn process( - &mut self, - params: &mut dyn DattorroReverbParams, - input_l: f64, - input_r: f64, - ) -> (f64, f64) { - // Some parameter setup... - let timescale = 0.1 + (4.0 - 0.1) * params.time_scale(); - self.set_time_scale(timescale); - - self.hpf[0].set_freq(params.reverb_high_cutoff_hz()); - self.hpf[1].set_freq(params.reverb_high_cutoff_hz()); - self.lpf[0].set_freq(params.reverb_low_cutoff_hz()); - self.lpf[1].set_freq(params.reverb_low_cutoff_hz()); - - let mod_speed = params.mod_speed(); - let mod_speed = mod_speed * mod_speed; - let mod_speed = mod_speed * 99.0 + 1.0; - - self.lfos[0].set(DAT_LFO_FREQS_HZ[0] * mod_speed, params.mod_shape()); - self.lfos[1].set(DAT_LFO_FREQS_HZ[1] * mod_speed, params.mod_shape()); - self.lfos[2].set(DAT_LFO_FREQS_HZ[2] * mod_speed, params.mod_shape()); - self.lfos[3].set(DAT_LFO_FREQS_HZ[3] * mod_speed, params.mod_shape()); - - self.apf1[0].2 = -DAT_PLATE_DIFFUSION1 * params.diffusion(); - self.apf1[1].2 = -DAT_PLATE_DIFFUSION1 * params.diffusion(); - self.apf2[0].2 = DAT_PLATE_DIFFUSION2 * params.diffusion(); - self.apf2[1].2 = DAT_PLATE_DIFFUSION2 * params.diffusion(); - - let (left_apf1_delay_ms, right_apf1_delay_ms, left_apf2_delay_ms, right_apf2_delay_ms) = - self.calc_apf_delay_times(params); - - // Parameter setup done! - - // Input into their corresponding DC blockers - let input_r = self.inp_dc_block[0].next(input_r); - let input_l = self.inp_dc_block[1].next(input_l); - - // Sum of DC outputs => LPF => HPF - self.input_lpf.set_freq(params.input_low_cutoff_hz()); - self.input_hpf.set_freq(params.input_high_cutoff_hz()); - let out_lpf = self.input_lpf.process(input_r + input_l); - let out_hpf = self.input_hpf.process(out_lpf); - - // HPF => Pre-Delay - let out_pre_delay = if params.pre_delay_time_ms() < 0.1 { - out_hpf - } else { - self.pre_delay.next_cubic(params.pre_delay_time_ms(), out_hpf) - }; - - // Pre-Delay => 4 All-Pass filters - let mut diffused = out_pre_delay; - for (apf, time, g) in &mut self.input_apfs { - diffused = apf.next(*time, *g, diffused); - } - - // Mix between diffused and pre-delayed intput for further processing - let tank_feed = crossfade(out_pre_delay, diffused, params.input_diffusion_mix()); - - // First tap for the output - self.left_sum += tank_feed; - self.right_sum += tank_feed; - - // Calculate tank decay of the left/right signal channels. - let decay = 1.0 - params.decay().clamp(0.1, 0.9999); - let decay = 1.0 - (decay * decay); - - // Left Sum => APF1 => Delay1 => LPF => HPF => APF2 => Delay2 - // And then send this over to the right sum. - let left = self.left_sum; - let left = self.apf1[0].0.next(left_apf1_delay_ms, self.apf1[0].2, left); - let left_apf_tap = left; - let left = self.delay1[0].0.next_cubic(self.delay1[0].1, left); - let left = self.lpf[0].process(left); - let left = self.hpf[0].process(left); - let left = left * decay; - let left = self.apf2[0].0.next(left_apf2_delay_ms, self.apf2[0].2, left); - let left = self.delay2[0].0.next_cubic(self.delay2[0].1, left); - - // if self.dbg_count % 48 == 0 { - // println!("APFS dcy={:8.6}; {:8.6} {:8.6} {:8.6} {:8.6} | {:8.6} {:8.6} {:8.6} {:8.6}", - // decay, - // self.apf1[0].2, - // self.apf1[1].2, - // self.apf2[0].2, - // self.apf2[1].2, - // left_apf1_delay_ms, right_apf1_delay_ms, - // left_apf2_delay_ms, right_apf2_delay_ms); - // println!("DELY1/2 {:8.6} / {:8.6} | {:8.6} / {:8.6}", - // self.delay1[0].1, - // self.delay2[0].1, - // self.delay1[1].1, - // self.delay2[1].1); - // } - - // Right Sum => APF1 => Delay1 => LPF => HPF => APF2 => Delay2 - // And then send this over to the left sum. - let right = self.right_sum; - let right = self.apf1[1].0.next(right_apf1_delay_ms, self.apf1[1].2, right); - let right_apf_tap = right; - let right = self.delay1[1].0.next_cubic(self.delay1[1].1, right); - let right = self.lpf[1].process(right); - let right = self.hpf[1].process(right); - let right = right * decay; - let right = self.apf2[1].0.next(right_apf2_delay_ms, self.apf2[1].2, right); - let right = self.delay2[1].0.next_cubic(self.delay2[1].1, right); - - self.right_sum = left * decay; - self.left_sum = right * decay; - - let mut left_accum = left_apf_tap; - left_accum += self.delay1[0].0.tap_n(DAT_LEFT_TAPS_TIME_MS[0]); - left_accum += self.delay1[0].0.tap_n(DAT_LEFT_TAPS_TIME_MS[1]); - left_accum -= self.apf2[0].0.delay_tap_n(DAT_LEFT_TAPS_TIME_MS[2]); - left_accum += self.delay2[0].0.tap_n(DAT_LEFT_TAPS_TIME_MS[3]); - left_accum -= self.delay1[1].0.tap_n(DAT_LEFT_TAPS_TIME_MS[4]); - left_accum -= self.apf2[1].0.delay_tap_n(DAT_LEFT_TAPS_TIME_MS[5]); - left_accum -= self.delay2[1].0.tap_n(DAT_LEFT_TAPS_TIME_MS[6]); - - let mut right_accum = right_apf_tap; - right_accum += self.delay1[1].0.tap_n(DAT_RIGHT_TAPS_TIME_MS[0]); - right_accum += self.delay1[1].0.tap_n(DAT_RIGHT_TAPS_TIME_MS[1]); - right_accum -= self.apf2[1].0.delay_tap_n(DAT_RIGHT_TAPS_TIME_MS[2]); - right_accum += self.delay2[1].0.tap_n(DAT_RIGHT_TAPS_TIME_MS[3]); - right_accum -= self.delay1[0].0.tap_n(DAT_RIGHT_TAPS_TIME_MS[4]); - right_accum -= self.apf2[0].0.delay_tap_n(DAT_RIGHT_TAPS_TIME_MS[5]); - right_accum -= self.delay2[0].0.tap_n(DAT_RIGHT_TAPS_TIME_MS[6]); - - let left_out = self.out_dc_block[0].next(left_accum); - let right_out = self.out_dc_block[1].next(right_accum); - - self.dbg_count += 1; - - (left_out * 0.5, right_out * 0.5) - } -} diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs deleted file mode 100644 index 6ce44b5..0000000 --- a/src/dsp/helpers.rs +++ /dev/null @@ -1,2697 +0,0 @@ -// Copyright (c) 2021 Weird Constructor -// This file is a part of HexoDSP. Released under GPL-3.0-or-later. -// See README.md and COPYING for details. - -use num_traits::{cast::FromPrimitive, cast::ToPrimitive, Float, FloatConst}; -use std::cell::RefCell; - -macro_rules! trait_alias { - ($name:ident = $base1:ident + $($base2:ident +)+) => { - pub trait $name: $base1 $(+ $base2)+ { } - impl $name for T { } - }; -} - -trait_alias!(Flt = Float + FloatConst + ToPrimitive + FromPrimitive +); - -/// Logarithmic table size of the table in [fast_cos] / [fast_sin]. -static FAST_COS_TAB_LOG2_SIZE: usize = 9; -/// Table size of the table in [fast_cos] / [fast_sin]. -static FAST_COS_TAB_SIZE: usize = 1 << FAST_COS_TAB_LOG2_SIZE; // =512 -/// The wave table of [fast_cos] / [fast_sin]. -static mut FAST_COS_TAB: [f32; 513] = [0.0; 513]; - -/// Initializes the cosine wave table for [fast_cos] and [fast_sin]. -pub fn init_cos_tab() { - for i in 0..(FAST_COS_TAB_SIZE + 1) { - let phase: f32 = (i as f32) * ((std::f32::consts::TAU) / (FAST_COS_TAB_SIZE as f32)); - unsafe { - // XXX: note: mutable statics can be mutated by multiple - // threads: aliasing violations or data races - // will cause undefined behavior - FAST_COS_TAB[i] = phase.cos(); - } - } -} - -/// Internal phase increment/scaling for [fast_cos]. -const PHASE_SCALE: f32 = 1.0_f32 / (std::f32::consts::TAU); - -/// A faster implementation of cosine. It's not that much faster than -/// Rust's built in cosine function. But YMMV. -/// -/// Don't forget to call [init_cos_tab] before using this! -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// init_cos_tab(); // Once on process initialization. -/// -/// // ... -/// assert!((fast_cos(std::f32::consts::PI) - -1.0).abs() < 0.001); -///``` -pub fn fast_cos(mut x: f32) -> f32 { - x = x.abs(); // cosine is symmetrical around 0, let's get rid of negative values - - // normalize range from 0..2PI to 1..2 - let phase = x * PHASE_SCALE; - - let index = FAST_COS_TAB_SIZE as f32 * phase; - - let fract = index.fract(); - let index = index.floor() as usize; - - unsafe { - // XXX: note: mutable statics can be mutated by multiple - // threads: aliasing violations or data races - // will cause undefined behavior - let left = FAST_COS_TAB[index as usize]; - let right = FAST_COS_TAB[index as usize + 1]; - - return left + (right - left) * fract; - } -} - -/// A faster implementation of sine. It's not that much faster than -/// Rust's built in sine function. But YMMV. -/// -/// Don't forget to call [init_cos_tab] before using this! -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// init_cos_tab(); // Once on process initialization. -/// -/// // ... -/// assert!((fast_sin(0.5 * std::f32::consts::PI) - 1.0).abs() < 0.001); -///``` -pub fn fast_sin(x: f32) -> f32 { - fast_cos(x - (std::f32::consts::PI / 2.0)) -} - -/// A wavetable filled entirely with white noise. -/// Don't forget to call [init_white_noise_tab] before using it. -static mut WHITE_NOISE_TAB: [f64; 1024] = [0.0; 1024]; - -#[allow(rustdoc::private_intra_doc_links)] -/// Initializes [WHITE_NOISE_TAB]. -pub fn init_white_noise_tab() { - let mut rng = RandGen::new(); - unsafe { - for i in 0..WHITE_NOISE_TAB.len() { - WHITE_NOISE_TAB[i as usize] = rng.next_open01(); - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq)] -/// Random number generator based on xoroshiro128. -/// Requires two internal state variables. You may prefer [SplitMix64] or [Rng]. -pub struct RandGen { - r: [u64; 2], -} - -// Taken from xoroshiro128 crate under MIT License -// Implemented by Matthew Scharley (Copyright 2016) -// https://github.com/mscharley/rust-xoroshiro128 -/// Given the mutable `state` generates the next pseudo random number. -pub fn next_xoroshiro128(state: &mut [u64; 2]) -> u64 { - let s0: u64 = state[0]; - let mut s1: u64 = state[1]; - let result: u64 = s0.wrapping_add(s1); - - s1 ^= s0; - state[0] = s0.rotate_left(55) ^ s1 ^ (s1 << 14); // a, b - state[1] = s1.rotate_left(36); // c - - result -} - -// Taken from rand::distributions -// Licensed under the Apache License, Version 2.0 -// Copyright 2018 Developers of the Rand project. -/// Maps any `u64` to a `f64` in the open interval `[0.0, 1.0)`. -pub fn u64_to_open01(u: u64) -> f64 { - use core::f64::EPSILON; - let float_size = std::mem::size_of::() as u32 * 8; - let fraction = u >> (float_size - 52); - let exponent_bits: u64 = (1023 as u64) << 52; - f64::from_bits(fraction | exponent_bits) - (1.0 - EPSILON / 2.0) -} - -impl RandGen { - pub fn new() -> Self { - RandGen { r: [0x193a6754a8a7d469, 0x97830e05113ba7bb] } - } - - /// Next random unsigned 64bit integer. - pub fn next(&mut self) -> u64 { - next_xoroshiro128(&mut self.r) - } - - /// Next random float between `[0.0, 1.0)`. - pub fn next_open01(&mut self) -> f64 { - u64_to_open01(self.next()) - } -} - -#[derive(Debug, Copy, Clone)] -/// Random number generator based on [SplitMix64]. -/// Requires two internal state variables. You may prefer [SplitMix64] or [Rng]. -pub struct Rng { - sm: SplitMix64, -} - -impl Rng { - pub fn new() -> Self { - Self { sm: SplitMix64::new(0x193a67f4a8a6d769) } - } - - pub fn seed(&mut self, seed: u64) { - self.sm = SplitMix64::new(seed); - } - - #[inline] - pub fn next(&mut self) -> f32 { - self.sm.next_open01() as f32 - } - - #[inline] - pub fn next_u64(&mut self) -> u64 { - self.sm.next_u64() - } -} - -thread_local! { - static GLOBAL_RNG: RefCell = RefCell::new(Rng::new()); -} - -#[inline] -pub fn rand_01() -> f32 { - GLOBAL_RNG.with(|r| r.borrow_mut().next()) -} - -#[inline] -pub fn rand_u64() -> u64 { - GLOBAL_RNG.with(|r| r.borrow_mut().next_u64()) -} - -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -//- splitmix64 (http://xoroshiro.di.unimi.it/splitmix64.c) -// -/// A splitmix64 random number generator. -/// -/// The splitmix algorithm is not suitable for cryptographic purposes, but is -/// very fast and has a 64 bit state. -/// -/// The algorithm used here is translated from [the `splitmix64.c` -/// reference source code](http://xoshiro.di.unimi.it/splitmix64.c) by -/// Sebastiano Vigna. For `next_u32`, a more efficient mixing function taken -/// from [`dsiutils`](http://dsiutils.di.unimi.it/) is used. -#[derive(Debug, Copy, Clone)] -pub struct SplitMix64(pub u64); - -/// Internal random constant for [SplitMix64]. -const PHI: u64 = 0x9e3779b97f4a7c15; - -impl SplitMix64 { - pub fn new(seed: u64) -> Self { - Self(seed) - } - pub fn new_from_i64(seed: i64) -> Self { - Self::new(u64::from_be_bytes(seed.to_be_bytes())) - } - - pub fn new_time_seed() -> Self { - use std::time::SystemTime; - - match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { - Ok(n) => Self::new(n.as_secs() as u64), - Err(_) => Self::new(123456789), - } - } - - #[inline] - pub fn next_u64(&mut self) -> u64 { - self.0 = self.0.wrapping_add(PHI); - let mut z = self.0; - z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); - z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); - z ^ (z >> 31) - } - - #[inline] - pub fn next_i64(&mut self) -> i64 { - i64::from_be_bytes(self.next_u64().to_be_bytes()) - } - - #[inline] - pub fn next_open01(&mut self) -> f64 { - u64_to_open01(self.next_u64()) - } -} - -/// Linear crossfade. -/// -/// * `v1` - signal 1, range -1.0 to 1.0 -/// * `v2` - signal 2, range -1.0 to 1.0 -/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5 -#[inline] -pub fn crossfade(v1: F, v2: F, mix: F) -> F { - v1 * (f::(1.0) - mix) + v2 * mix -} - -/// Linear crossfade with clipping the `v2` result. -/// -/// This crossfade actually does clip the `v2` signal to the -1.0 to 1.0 -/// range. This is useful for Dry/Wet of plugins that might go beyond the -/// normal signal range. -/// -/// * `v1` - signal 1, range -1.0 to 1.0 -/// * `v2` - signal 2, range -1.0 to 1.0 -/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5 -#[inline] -pub fn crossfade_clip(v1: F, v2: F, mix: F) -> F { - v1 * (f::(1.0) - mix) + (v2 * mix).min(f::(1.0)).max(f::(-1.0)) -} - -/// Linear (f32) crossfade with driving the `v2` result through a tanh(). -/// -/// * `v1` - signal 1, range -1.0 to 1.0 -/// * `v2` - signal 2, range -1.0 to 1.0 -/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5 -#[inline] -pub fn crossfade_drive_tanh(v1: f32, v2: f32, mix: f32) -> f32 { - v1 * (1.0 - mix) + tanh_approx_drive(v2 * mix * 0.111, 0.95) * 0.9999 -} - -/// Constant power crossfade. -/// -/// * `v1` - signal 1, range -1.0 to 1.0 -/// * `v2` - signal 2, range -1.0 to 1.0 -/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5 -#[inline] -pub fn crossfade_cpow(v1: f32, v2: f32, mix: f32) -> f32 { - let s1 = (mix * std::f32::consts::FRAC_PI_2).sin(); - let s2 = ((1.0 - mix) * std::f32::consts::FRAC_PI_2).sin(); - v1 * s2 + v2 * s1 -} - -const CROSS_LOG_MIN: f32 = -13.815510557964274; // (0.000001_f32).ln(); -const CROSS_LOG_MAX: f32 = 0.0; // (1.0_f32).ln(); - -/// Logarithmic crossfade. -/// -/// * `v1` - signal 1, range -1.0 to 1.0 -/// * `v2` - signal 2, range -1.0 to 1.0 -/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5 -#[inline] -pub fn crossfade_log(v1: f32, v2: f32, mix: f32) -> f32 { - let x = (mix * (CROSS_LOG_MAX - CROSS_LOG_MIN) + CROSS_LOG_MIN).exp(); - crossfade(v1, v2, x) -} - -/// Exponential crossfade. -/// -/// * `v1` - signal 1, range -1.0 to 1.0 -/// * `v2` - signal 2, range -1.0 to 1.0 -/// * `mix` - mix position, range 0.0 to 1.0, mid is at 0.5 -#[inline] -pub fn crossfade_exp(v1: f32, v2: f32, mix: f32) -> f32 { - crossfade(v1, v2, mix * mix) -} - -#[inline] -pub fn clamp(f: f32, min: f32, max: f32) -> f32 { - if f < min { - min - } else if f > max { - max - } else { - f - } -} - -pub fn square_135(phase: f32) -> f32 { - fast_sin(phase) + fast_sin(phase * 3.0) / 3.0 + fast_sin(phase * 5.0) / 5.0 -} - -pub fn square_35(phase: f32) -> f32 { - fast_sin(phase * 3.0) / 3.0 + fast_sin(phase * 5.0) / 5.0 -} - -// note: MIDI note value? -pub fn note_to_freq(note: f32) -> f32 { - 440.0 * (2.0_f32).powf((note - 69.0) / 12.0) -} - -// Ported from LMMS under GPLv2 -// * DspEffectLibrary.h - library with template-based inline-effects -// * Copyright (c) 2006-2014 Tobias Doerffel -// -// Original source seems to be musicdsp.org, Author: Bram de Jong -// see also: https://www.musicdsp.org/en/latest/Effects/41-waveshaper.html -// Notes: -// where x (in [-1..1] will be distorted and a is a distortion parameter -// that goes from 1 to infinity. The equation is valid for positive and -// negativ values. If a is 1, it results in a slight distortion and with -// bigger a's the signal get's more funky. -// A good thing about the shaper is that feeding it with bigger-than-one -// values, doesn't create strange fx. The maximum this function will reach -// is 1.2 for a=1. -// -// f(x,a) = x*(abs(x) + a)/(x^2 + (a-1)*abs(x) + 1) -/// Signal distortion by Bram de Jong. -/// ```text -/// gain: 0.1 - 5.0 default = 1.0 -/// threshold: 0.0 - 100.0 default = 0.8 -/// i: signal -/// ``` -#[inline] -pub fn f_distort(gain: f32, threshold: f32, i: f32) -> f32 { - gain * (i * (i.abs() + threshold) / (i * i + (threshold - 1.0) * i.abs() + 1.0)) -} - -// Ported from LMMS under GPLv2 -// * DspEffectLibrary.h - library with template-based inline-effects -// * Copyright (c) 2006-2014 Tobias Doerffel -// -/// Foldback Signal distortion -/// ```text -/// gain: 0.1 - 5.0 default = 1.0 -/// threshold: 0.0 - 100.0 default = 0.8 -/// i: signal -/// ``` -#[inline] -pub fn f_fold_distort(gain: f32, threshold: f32, i: f32) -> f32 { - if i >= threshold || i < -threshold { - gain * ((((i - threshold) % threshold * 4.0).abs() - threshold * 2.0).abs() - threshold) - } else { - gain * i - } -} - -/// Apply linear interpolation between the value a and b. -/// -/// * `a` - value at x=0.0 -/// * `b` - value at x=1.0 -/// * `x` - value between 0.0 and 1.0 to blend between `a` and `b`. -#[inline] -pub fn lerp(x: f32, a: f32, b: f32) -> f32 { - (a * (1.0 - x)) + (b * x) -} - -/// Apply 64bit linear interpolation between the value a and b. -/// -/// * `a` - value at x=0.0 -/// * `b` - value at x=1.0 -/// * `x` - value between 0.0 and 1.0 to blend between `a` and `b`. -pub fn lerp64(x: f64, a: f64, b: f64) -> f64 { - (a * (1.0 - x)) + (b * x) -} - -pub fn p2range(x: f32, a: f32, b: f32) -> f32 { - lerp(x, a, b) -} - -pub fn p2range_exp(x: f32, a: f32, b: f32) -> f32 { - let x = x * x; - (a * (1.0 - x)) + (b * x) -} - -pub fn p2range_exp4(x: f32, a: f32, b: f32) -> f32 { - let x = x * x * x * x; - (a * (1.0 - x)) + (b * x) -} - -pub fn range2p(v: f32, a: f32, b: f32) -> f32 { - ((v - a) / (b - a)).abs() -} - -pub fn range2p_exp(v: f32, a: f32, b: f32) -> f32 { - (((v - a) / (b - a)).abs()).sqrt() -} - -pub fn range2p_exp4(v: f32, a: f32, b: f32) -> f32 { - (((v - a) / (b - a)).abs()).sqrt().sqrt() -} - -/// ```text -/// gain: 24.0 - -90.0 default = 0.0 -/// ``` -pub fn gain2coef(gain: f32) -> f32 { - if gain > -90.0 { - 10.0_f32.powf(gain * 0.05) - } else { - 0.0 - } -} - -// quickerTanh / quickerTanh64 credits to mopo synthesis library: -// Under GPLv3 or any later. -// Little IO -// Matt Tytel -pub fn quicker_tanh64(v: f64) -> f64 { - let square = v * v; - v / (1.0 + square / (3.0 + square / 5.0)) -} - -#[inline] -pub fn quicker_tanh(v: f32) -> f32 { - let square = v * v; - v / (1.0 + square / (3.0 + square / 5.0)) -} - -// quickTanh / quickTanh64 credits to mopo synthesis library: -// Under GPLv3 or any later. -// Little IO -// Matt Tytel -pub fn quick_tanh64(v: f64) -> f64 { - let abs_v = v.abs(); - let square = v * v; - let num = v - * (2.45550750702956 - + 2.45550750702956 * abs_v - + square * (0.893229853513558 + 0.821226666969744 * abs_v)); - let den = - 2.44506634652299 + (2.44506634652299 + square) * (v + 0.814642734961073 * v * abs_v).abs(); - - num / den -} - -pub fn quick_tanh(v: f32) -> f32 { - let abs_v = v.abs(); - let square = v * v; - let num = v - * (2.45550750702956 - + 2.45550750702956 * abs_v - + square * (0.893229853513558 + 0.821226666969744 * abs_v)); - let den = - 2.44506634652299 + (2.44506634652299 + square) * (v + 0.814642734961073 * v * abs_v).abs(); - - num / den -} - -// Taken from ValleyAudio -// Copyright Dale Johnson -// https://github.dev/ValleyAudio/ValleyRackFree/tree/v2.0 -// Under GPLv3 license -pub fn tanh_approx_drive(v: f32, drive: f32) -> f32 { - let x = v * drive; - - if x < -1.25 { - -1.0 - } else if x < -0.75 { - 1.0 - (x * (-2.5 - x) - 0.5625) - 1.0 - } else if x > 1.25 { - 1.0 - } else if x > 0.75 { - x * (2.5 - x) - 0.5625 - } else { - x - } -} - -/// A helper function for exponential envelopes. -/// It's a bit faster than calling the `pow` function of Rust. -/// -/// * `x` the input value -/// * `v' the shape value. -/// Which is linear at `0.5`, the forth root of `x` at `1.0` and x to the power -/// of 4 at `0.0`. You can vary `v` as you like. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// assert!(((sqrt4_to_pow4(0.25, 0.0) - 0.25_f32 * 0.25 * 0.25 * 0.25) -/// .abs() - 1.0) -/// < 0.0001); -/// -/// assert!(((sqrt4_to_pow4(0.25, 1.0) - (0.25_f32).sqrt().sqrt()) -/// .abs() - 1.0) -/// < 0.0001); -/// -/// assert!(((sqrt4_to_pow4(0.25, 0.5) - 0.25_f32).abs() - 1.0) < 0.0001); -///``` -#[inline] -pub fn sqrt4_to_pow4(x: f32, v: f32) -> f32 { - if v > 0.75 { - let xsq1 = x.sqrt(); - let xsq = xsq1.sqrt(); - let v = (v - 0.75) * 4.0; - xsq1 * (1.0 - v) + xsq * v - } else if v > 0.5 { - let xsq = x.sqrt(); - let v = (v - 0.5) * 4.0; - x * (1.0 - v) + xsq * v - } else if v > 0.25 { - let xx = x * x; - let v = (v - 0.25) * 4.0; - x * v + xx * (1.0 - v) - } else { - let xx = x * x; - let xxxx = xx * xx; - let v = v * 4.0; - xx * v + xxxx * (1.0 - v) - } -} - -/// A-100 Eurorack states, that a trigger is usually 2-10 milliseconds. -pub const TRIG_SIGNAL_LENGTH_MS: f32 = 2.0; - -/// The lower threshold for the schmidt trigger to reset. -pub const TRIG_LOW_THRES: f32 = 0.25; -/// The threshold, once reached, will cause a trigger event and signals -/// a logical '1'. Anything below this is a logical '0'. -pub const TRIG_HIGH_THRES: f32 = 0.5; - -/// Trigger signal generator for HexoDSP nodes. -/// -/// A trigger in HexoSynth and HexoDSP is commonly 2.0 milliseconds. -/// This generator generates a trigger signal when [TrigSignal::trigger] is called. -#[derive(Debug, Clone, Copy)] -pub struct TrigSignal { - length: u32, - scount: u32, -} - -impl TrigSignal { - /// Create a new trigger generator - pub fn new() -> Self { - Self { length: ((44100.0 * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32, scount: 0 } - } - - /// Reset the trigger generator. - pub fn reset(&mut self) { - self.scount = 0; - } - - /// Set the sample rate to calculate the amount of samples for the trigger signal. - pub fn set_sample_rate(&mut self, srate: f32) { - self.length = ((srate * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32; - self.scount = 0; - } - - /// Enable sending a trigger impulse the next time [TrigSignal::next] is called. - #[inline] - pub fn trigger(&mut self) { - self.scount = self.length; - } - - /// Trigger signal output. - #[inline] - pub fn next(&mut self) -> f32 { - if self.scount > 0 { - self.scount -= 1; - 1.0 - } else { - 0.0 - } - } -} - -impl Default for TrigSignal { - fn default() -> Self { - Self::new() - } -} - -/// Signal change detector that emits a trigger when the input signal changed. -/// -/// This is commonly used for control signals. It has not much use for audio signals. -#[derive(Debug, Clone, Copy)] -pub struct ChangeTrig { - ts: TrigSignal, - last: f32, -} - -impl ChangeTrig { - /// Create a new change detector - pub fn new() -> Self { - Self { - ts: TrigSignal::new(), - last: -100.0, // some random value :-) - } - } - - /// Reset internal state. - pub fn reset(&mut self) { - self.ts.reset(); - self.last = -100.0; - } - - /// Set the sample rate for the trigger signal generator - pub fn set_sample_rate(&mut self, srate: f32) { - self.ts.set_sample_rate(srate); - } - - /// Feed a new input signal sample. - /// - /// The return value is the trigger signal. - #[inline] - pub fn next(&mut self, inp: f32) -> f32 { - if (inp - self.last).abs() > std::f32::EPSILON { - self.ts.trigger(); - self.last = inp; - } - - self.ts.next() - } -} - -impl Default for ChangeTrig { - fn default() -> Self { - Self::new() - } -} - -/// Trigger signal detector for HexoDSP. -/// -/// Whenever you need to detect a trigger on an input you can use this component. -/// A trigger in HexoDSP is any signal over [TRIG_HIGH_THRES]. The internal state is -/// resetted when the signal drops below [TRIG_LOW_THRES]. -#[derive(Debug, Clone, Copy)] -pub struct Trigger { - triggered: bool, -} - -impl Trigger { - /// Create a new trigger detector. - pub fn new() -> Self { - Self { triggered: false } - } - - /// Reset the internal state of the trigger detector. - #[inline] - pub fn reset(&mut self) { - self.triggered = false; - } - - /// Checks the input signal for a trigger and returns true when the signal - /// surpassed [TRIG_HIGH_THRES] and has not fallen below [TRIG_LOW_THRES] yet. - #[inline] - pub fn check_trigger(&mut self, input: f32) -> bool { - if self.triggered { - if input <= TRIG_LOW_THRES { - self.triggered = false; - } - - false - } else if input > TRIG_HIGH_THRES { - self.triggered = true; - true - } else { - false - } - } -} - -/// Trigger signal detector with custom range. -/// -/// Whenever you need to detect a trigger with a custom threshold. -#[derive(Debug, Clone, Copy)] -pub struct CustomTrigger { - triggered: bool, - low_thres: f32, - high_thres: f32, -} - -impl CustomTrigger { - /// Create a new trigger detector. - pub fn new(low_thres: f32, high_thres: f32) -> Self { - Self { triggered: false, low_thres, high_thres } - } - - pub fn set_threshold(&mut self, low_thres: f32, high_thres: f32) { - self.low_thres = low_thres; - self.high_thres = high_thres; - } - - /// Reset the internal state of the trigger detector. - #[inline] - pub fn reset(&mut self) { - self.triggered = false; - } - - /// Checks the input signal for a trigger and returns true when the signal - /// surpassed the high threshold and has not fallen below low threshold yet. - #[inline] - pub fn check_trigger(&mut self, input: f32) -> bool { - // println!("TRIG CHECK: {} <> {}", input, self.high_thres); - if self.triggered { - if input <= self.low_thres { - self.triggered = false; - } - - false - } else if input > self.high_thres { - self.triggered = true; - true - } else { - false - } - } -} - -/// Generates a phase signal from a trigger/gate input signal. -/// -/// This helper allows you to measure the distance between trigger or gate pulses -/// and generates a phase signal for you that increases from 0.0 to 1.0. -#[derive(Debug, Clone, Copy)] -pub struct TriggerPhaseClock { - clock_phase: f64, - clock_inc: f64, - prev_trigger: bool, - clock_samples: u32, -} - -impl TriggerPhaseClock { - /// Create a new phase clock. - pub fn new() -> Self { - Self { clock_phase: 0.0, clock_inc: 0.0, prev_trigger: true, clock_samples: 0 } - } - - /// Reset the phase clock. - #[inline] - pub fn reset(&mut self) { - self.clock_samples = 0; - self.clock_inc = 0.0; - self.prev_trigger = true; - self.clock_samples = 0; - } - - /// Restart the phase clock. It will count up from 0.0 again on [TriggerPhaseClock::next_phase]. - #[inline] - pub fn sync(&mut self) { - self.clock_phase = 0.0; - } - - /// Generate the phase signal of this clock. - /// - /// * `clock_limit` - The maximum number of samples to detect two trigger signals in. - /// * `trigger_in` - Trigger signal input. - #[inline] - pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 { - if self.prev_trigger { - if trigger_in <= TRIG_LOW_THRES { - self.prev_trigger = false; - } - } else if trigger_in > TRIG_HIGH_THRES { - self.prev_trigger = true; - - if self.clock_samples > 0 { - self.clock_inc = 1.0 / (self.clock_samples as f64); - } - - self.clock_samples = 0; - } - - self.clock_samples += 1; - - self.clock_phase += self.clock_inc; - self.clock_phase = self.clock_phase % clock_limit; - - self.clock_phase - } -} - -#[derive(Debug, Clone, Copy)] -pub struct TriggerSampleClock { - prev_trigger: bool, - clock_samples: u32, - counter: u32, -} - -impl TriggerSampleClock { - pub fn new() -> Self { - Self { prev_trigger: true, clock_samples: 0, counter: 0 } - } - - #[inline] - pub fn reset(&mut self) { - self.clock_samples = 0; - self.counter = 0; - } - - #[inline] - pub fn next(&mut self, trigger_in: f32) -> u32 { - if self.prev_trigger { - if trigger_in <= TRIG_LOW_THRES { - self.prev_trigger = false; - } - } else if trigger_in > TRIG_HIGH_THRES { - self.prev_trigger = true; - self.clock_samples = self.counter; - self.counter = 0; - } - - self.counter += 1; - - self.clock_samples - } -} - -/// A slew rate limiter, with a configurable time per 1.0 increase. -#[derive(Debug, Clone, Copy)] -pub struct SlewValue { - current: F, - slew_per_ms: F, -} - -impl SlewValue { - pub fn new() -> Self { - Self { current: f(0.0), slew_per_ms: f(1000.0 / 44100.0) } - } - - pub fn reset(&mut self) { - self.current = f(0.0); - } - - pub fn set_sample_rate(&mut self, srate: F) { - self.slew_per_ms = f::(1000.0) / srate; - } - - #[inline] - pub fn value(&self) -> F { - self.current - } - - /// * `slew_ms_per_1` - The time (in milliseconds) it should take - /// to get to 1.0 from 0.0. - #[inline] - pub fn next(&mut self, target: F, slew_ms_per_1: F) -> F { - // at 0.11ms, there are barely enough samples for proper slew. - if slew_ms_per_1 < f(0.11) { - self.current = target; - } else { - let max_delta = self.slew_per_ms / slew_ms_per_1; - self.current = target.min(self.current + max_delta).max(self.current - max_delta); - } - - self.current - } -} - -/// A ramped value changer, with a configurable time to reach the target value. -#[derive(Debug, Clone, Copy)] -pub struct RampValue { - slew_count: u64, - current: F, - target: F, - inc: F, - sr_ms: F, -} - -impl RampValue { - pub fn new() -> Self { - Self { - slew_count: 0, - current: f(0.0), - target: f(0.0), - inc: f(0.0), - sr_ms: f(44100.0 / 1000.0), - } - } - - pub fn reset(&mut self) { - self.slew_count = 0; - self.current = f(0.0); - self.target = f(0.0); - self.inc = f(0.0); - } - - pub fn set_sample_rate(&mut self, srate: F) { - self.sr_ms = srate / f(1000.0); - } - - #[inline] - pub fn set_target(&mut self, target: F, slew_time_ms: F) { - self.target = target; - - // 0.02ms, thats a fraction of a sample at 44.1kHz - if slew_time_ms < f(0.02) { - self.current = self.target; - self.slew_count = 0; - } else { - let slew_samples = slew_time_ms * self.sr_ms; - self.slew_count = slew_samples.to_u64().unwrap_or(0); - self.inc = (self.target - self.current) / slew_samples; - } - } - - #[inline] - pub fn value(&self) -> F { - self.current - } - - #[inline] - pub fn next(&mut self) -> F { - if self.slew_count > 0 { - self.current = self.current + self.inc; - self.slew_count -= 1; - } else { - self.current = self.target; - } - - self.current - } -} - -/// Default size of the delay buffer: 5 seconds at 8 times 48kHz -const DEFAULT_DELAY_BUFFER_SAMPLES: usize = 8 * 48000 * 5; - -macro_rules! fc { - ($F: ident, $e: expr) => { - F::from_f64($e).unwrap() - }; -} - -#[allow(dead_code)] -#[inline] -fn f(x: f64) -> F { - F::from_f64(x).unwrap() -} - -#[allow(dead_code)] -#[inline] -fn fclamp(x: F, mi: F, mx: F) -> F { - x.max(mi).min(mx) -} - -#[allow(dead_code)] -#[inline] -fn fclampc(x: F, mi: f64, mx: f64) -> F { - x.max(f(mi)).min(f(mx)) -} - -/// Hermite / Cubic interpolation of a buffer full of samples at the given _index_. -/// _len_ is the buffer length to consider and wrap the index into. And _fract_ is the -/// fractional part of the index. -/// -/// This function is generic over f32 and f64. That means you can use your preferred float size. -/// -/// Commonly used like this: -/// -///``` -/// use hexodsp::dsp::helpers::cubic_interpolate; -/// -/// let buf : [f32; 9] = [1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2]; -/// let pos = 3.3_f32; -/// -/// let i = pos.floor() as usize; -/// let f = pos.fract(); -/// -/// let res = cubic_interpolate(&buf[..], buf.len(), i, f); -/// assert!((res - 0.67).abs() < 0.2_f32); -///``` -#[inline] -pub fn cubic_interpolate(data: &[F], len: usize, index: usize, fract: F) -> F { - let index = index + len; - // Hermite interpolation, take from - // https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52 - // - // Thanks go to Eric Wood! - // - // For the interpolation code: - // MIT License, Copyright (c) 2021 Eric Wood - let xm1 = data[(index - 1) % len]; - let x0 = data[index % len]; - let x1 = data[(index + 1) % len]; - let x2 = data[(index + 2) % len]; - - let c = (x1 - xm1) * f(0.5); - let v = x0 - x1; - let w = c + v; - let a = w + v + (x2 - x0) * f(0.5); - let b_neg = w + a; - - let res = (((a * fract) - b_neg) * fract + c) * fract + x0; - - // let rr2 = - // x0 + f::(0.5) * fract * ( - // x1 - xm1 + fract * ( - // f::(4.0) * x1 - // + f::(2.0) * xm1 - // - f::(5.0) * x0 - // - x2 - // + fract * (f::(3.0) * (x0 - x1) - xm1 + x2))); - - // eprintln!( - // "index={} fract={:6.4} xm1={:6.4} x0={:6.4} x1={:6.4} x2={:6.4} = {:6.4} <> {:6.4}", - // index, fract.to_f64().unwrap(), xm1.to_f64().unwrap(), x0.to_f64().unwrap(), x1.to_f64().unwrap(), x2.to_f64().unwrap(), - // res.to_f64().unwrap(), - // rr2.to_f64().unwrap() - // ); - - // eprintln!( - // "index={} fract={:6.4} xm1={:6.4} x0={:6.4} x1={:6.4} x2={:6.4} = {:6.4}", - // index, fract.to_f64().unwrap(), xm1.to_f64().unwrap(), x0.to_f64().unwrap(), x1.to_f64().unwrap(), x2.to_f64().unwrap(), - // res.to_f64().unwrap(), - // ); - - res -} - -/// This is a delay buffer/line with linear and cubic interpolation. -/// -/// It's the basic building block underneath the all-pass filter, comb filters and delay effects. -/// You can use linear and cubic and no interpolation to access samples in the past. Either -/// by sample offset or time (millisecond) based. -#[derive(Debug, Clone, Default)] -pub struct DelayBuffer { - data: Vec, - wr: usize, - srate: F, -} - -impl DelayBuffer { - /// Creates a delay buffer with about 5 seconds of capacity at 8*48000Hz sample rate. - pub fn new() -> Self { - Self { data: vec![f(0.0); DEFAULT_DELAY_BUFFER_SAMPLES], wr: 0, srate: f(44100.0) } - } - - /// Creates a delay buffer with the given amount of samples capacity. - pub fn new_with_size(size: usize) -> Self { - Self { data: vec![f(0.0); size], wr: 0, srate: f(44100.0) } - } - - /// Sets the sample rate that is used for milliseconds => sample conversion. - pub fn set_sample_rate(&mut self, srate: F) { - self.srate = srate; - } - - /// Reset the delay buffer contents and write position. - pub fn reset(&mut self) { - self.data.fill(f(0.0)); - self.wr = 0; - } - - /// Feed one sample into the delay line and increment the write pointer. - /// Please note: For sample accurate feedback you need to retrieve the - /// output of the delay line before feeding in a new signal. - #[inline] - pub fn feed(&mut self, input: F) { - self.data[self.wr] = input; - self.wr = (self.wr + 1) % self.data.len(); - } - - /// Combines [DelayBuffer::cubic_interpolate_at] and [DelayBuffer::feed] - /// into one convenient function. - #[inline] - pub fn next_cubic(&mut self, delay_time_ms: F, input: F) -> F { - let res = self.cubic_interpolate_at(delay_time_ms); - self.feed(input); - res - } - - /// Combines [DelayBuffer::linear_interpolate_at] and [DelayBuffer::feed] - /// into one convenient function. - #[inline] - pub fn next_linear(&mut self, delay_time_ms: F, input: F) -> F { - let res = self.linear_interpolate_at(delay_time_ms); - self.feed(input); - res - } - - /// Combines [DelayBuffer::nearest_at] and [DelayBuffer::feed] - /// into one convenient function. - #[inline] - pub fn next_nearest(&mut self, delay_time_ms: F, input: F) -> F { - let res = self.nearest_at(delay_time_ms); - self.feed(input); - res - } - - /// Shorthand for [DelayBuffer::cubic_interpolate_at]. - #[inline] - pub fn tap_c(&self, delay_time_ms: F) -> F { - self.cubic_interpolate_at(delay_time_ms) - } - - /// Shorthand for [DelayBuffer::cubic_interpolate_at]. - #[inline] - pub fn tap_n(&self, delay_time_ms: F) -> F { - self.nearest_at(delay_time_ms) - } - - /// Shorthand for [DelayBuffer::cubic_interpolate_at]. - #[inline] - pub fn tap_l(&self, delay_time_ms: F) -> F { - self.linear_interpolate_at(delay_time_ms) - } - - /// Fetch a sample from the delay buffer at the given tim with linear interpolation. - /// - /// * `delay_time_ms` - Delay time in milliseconds. - #[inline] - pub fn linear_interpolate_at(&self, delay_time_ms: F) -> F { - self.linear_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0)) - } - - /// Fetch a sample from the delay buffer at the given offset with linear interpolation. - /// - /// * `s_offs` - Sample offset in samples. - #[inline] - pub fn linear_interpolate_at_s(&self, s_offs: F) -> F { - let data = &self.data[..]; - let len = data.len(); - let offs = s_offs.floor().to_usize().unwrap_or(0) % len; - let fract = s_offs.fract(); - - // one extra offset, because feed() advances self.wr to the next writing position! - let i = (self.wr + len) - (offs + 1); - let x0 = data[i % len]; - let x1 = data[(i - 1) % len]; - - let res = x0 + fract * (x1 - x0); - //d// eprintln!( - //d// "INTERP: {:6.4} x0={:6.4} x1={:6.4} fract={:6.4} => {:6.4}", - //d// s_offs.to_f64().unwrap_or(0.0), - //d// x0.to_f64().unwrap(), - //d// x1.to_f64().unwrap(), - //d// fract.to_f64().unwrap(), - //d// res.to_f64().unwrap(), - //d// ); - res - } - - /// Fetch a sample from the delay buffer at the given time with cubic interpolation. - /// - /// * `delay_time_ms` - Delay time in milliseconds. - #[inline] - pub fn cubic_interpolate_at(&self, delay_time_ms: F) -> F { - self.cubic_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0)) - } - - /// Fetch a sample from the delay buffer at the given offset with cubic interpolation. - /// - /// * `s_offs` - Sample offset in samples into the past of the [DelayBuffer] - /// from the current write (or the "now") position. - #[inline] - pub fn cubic_interpolate_at_s(&self, s_offs: F) -> F { - let data = &self.data[..]; - let len = data.len(); - let offs = s_offs.floor().to_usize().unwrap_or(0) % len; - let fract = s_offs.fract(); - - // (offs + 1) offset for compensating that self.wr points to the next - // unwritten position. - // Additional (offs + 1 + 1) offset for cubic_interpolate, which - // interpolates into the past through the delay buffer. - let i = (self.wr + len) - (offs + 2); - let res = cubic_interpolate(data, len, i, f::(1.0) - fract); - // eprintln!( - // "cubic at={} ({:6.4}) res={:6.4}", - // i % len, - // s_offs.to_f64().unwrap(), - // res.to_f64().unwrap() - // ); - res - } - - /// Fetch a sample from the delay buffer at the given time without any interpolation. - /// - /// * `delay_time_ms` - Delay time in milliseconds. - #[inline] - pub fn nearest_at(&self, delay_time_ms: F) -> F { - let len = self.data.len(); - let offs = ((delay_time_ms * self.srate) / f(1000.0)).floor().to_usize().unwrap_or(0) % len; - // (offs + 1) one extra offset, because feed() advances - // self.wr to the next writing position! - let idx = ((self.wr + len) - (offs + 1)) % len; - self.data[idx] - } - - /// Fetch a sample from the delay buffer at the given number of samples in the past. - #[inline] - pub fn at(&self, delay_sample_count: usize) -> F { - let len = self.data.len(); - // (delay_sample_count + 1) one extra offset, because feed() advances self.wr to - // the next writing position! - let idx = ((self.wr + len) - (delay_sample_count + 1)) % len; - self.data[idx] - } -} - -/// Default size of the delay buffer: 1 seconds at 8 times 48kHz -const DEFAULT_ALLPASS_COMB_SAMPLES: usize = 8 * 48000; - -/// An all-pass filter based on a delay line. -#[derive(Debug, Clone, Default)] -pub struct AllPass { - delay: DelayBuffer, -} - -impl AllPass { - /// Creates a new all-pass filter with about 1 seconds space for samples. - pub fn new() -> Self { - Self { delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES) } - } - - /// Set the sample rate for millisecond based access. - pub fn set_sample_rate(&mut self, srate: F) { - self.delay.set_sample_rate(srate); - } - - /// Reset the internal delay buffer. - pub fn reset(&mut self) { - self.delay.reset(); - } - - /// Access the internal delay at the given amount of milliseconds in the past. - #[inline] - pub fn delay_tap_n(&self, time_ms: F) -> F { - self.delay.tap_n(time_ms) - } - - /// Retrieve the next (cubic interpolated) sample from the all-pass - /// filter while feeding in the next. - /// - /// * `time_ms` - Delay time in milliseconds. - /// * `g` - Feedback factor (usually something around 0.7 is common) - /// * `v` - The new input sample to feed the filter. - #[inline] - pub fn next(&mut self, time_ms: F, g: F, v: F) -> F { - let s = self.delay.cubic_interpolate_at(time_ms); - let input = v + -g * s; - self.delay.feed(input); - input * g + s - } - - /// Retrieve the next (linear interpolated) sample from the all-pass - /// filter while feeding in the next. - /// - /// * `time_ms` - Delay time in milliseconds. - /// * `g` - Feedback factor (usually something around 0.7 is common) - /// * `v` - The new input sample to feed the filter. - #[inline] - pub fn next_linear(&mut self, time_ms: F, g: F, v: F) -> F { - let s = self.delay.linear_interpolate_at(time_ms); - let input = v + -g * s; - self.delay.feed(input); - input * g + s - } -} - -#[derive(Debug, Clone)] -pub struct Comb { - delay: DelayBuffer, -} - -impl Comb { - pub fn new() -> Self { - Self { delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES) } - } - - pub fn set_sample_rate(&mut self, srate: f32) { - self.delay.set_sample_rate(srate); - } - - pub fn reset(&mut self) { - self.delay.reset(); - } - - #[inline] - pub fn delay_tap_c(&self, time_ms: f32) -> f32 { - self.delay.tap_c(time_ms) - } - - #[inline] - pub fn delay_tap_n(&self, time_ms: f32) -> f32 { - self.delay.tap_n(time_ms) - } - - #[inline] - pub fn next_feedback(&mut self, time: f32, g: f32, v: f32) -> f32 { - let s = self.delay.cubic_interpolate_at(time); - let v = v + s * g; - self.delay.feed(v); - v - } - - #[inline] - pub fn next_feedforward(&mut self, time: f32, g: f32, v: f32) -> f32 { - let s = self.delay.next_cubic(time, v); - v + s * g - } -} - -// one pole lp from valley rack free: -// https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp -#[inline] -/// Process a very simple one pole 6dB low pass filter. -/// Useful in various applications, from usage in a synthesizer to -/// damping stuff in a reverb/delay. -/// -/// * `input` - Input sample -/// * `freq` - Frequency between 1.0 and 22000.0Hz -/// * `israte` - 1.0 / samplerate -/// * `z` - The internal one sample buffer of the filter. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut z = 0.0; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let s_out = -/// process_1pole_lowpass(*s, freq, 1.0 / 44100.0, &mut z); -/// // ... do something with the result here. -/// } -///``` -pub fn process_1pole_lowpass(input: f32, freq: f32, israte: f32, z: &mut f32) -> f32 { - let b = (-std::f32::consts::TAU * freq * israte).exp(); - let a = 1.0 - b; - *z = a * input + *z * b; - *z -} - -#[derive(Debug, Clone, Copy, Default)] -pub struct OnePoleLPF { - israte: F, - a: F, - b: F, - freq: F, - z: F, -} - -impl OnePoleLPF { - pub fn new() -> Self { - Self { - israte: f::(1.0) / f(44100.0), - a: f::(0.0), - b: f::(0.0), - freq: f::(1000.0), - z: f::(0.0), - } - } - - pub fn reset(&mut self) { - self.z = f(0.0); - } - - #[inline] - fn recalc(&mut self) { - self.b = (f::(-1.0) * F::TAU() * self.freq * self.israte).exp(); - self.a = f::(1.0) - self.b; - } - - #[inline] - pub fn set_sample_rate(&mut self, srate: F) { - self.israte = f::(1.0) / srate; - self.recalc(); - } - - #[inline] - pub fn set_freq(&mut self, freq: F) { - if freq != self.freq { - self.freq = freq; - self.recalc(); - } - } - - #[inline] - pub fn process(&mut self, input: F) -> F { - self.z = self.a * input + self.z * self.b; - self.z - } -} - -// Fixed one pole with setable pole and gain. -// Implementation taken from tubonitaub / alec-deason -// from https://github.com/alec-deason/virtual_modular/blob/4025f1ef343c2eb9cd74eac07b5350c1e7ec9c09/src/simd_graph.rs#L4292 -// under MIT License -#[derive(Debug, Copy, Clone, Default)] -pub struct FixedOnePole { - b0: f32, - a1: f32, - y1: f32, - gain: f32, -} - -impl FixedOnePole { - pub fn new(pole: f32, gain: f32) -> Self { - let b0 = if pole > 0.0 { 1.0 - pole } else { 1.0 + pole }; - - Self { b0, a1: -pole, y1: 0.0, gain } - } - - pub fn reset(&mut self) { - self.y1 = 0.0; - } - - pub fn set_gain(&mut self, gain: f32) { - self.gain = gain; - } - - pub fn process(&mut self, input: f32) -> f32 { - let output = self.b0 * self.gain * input - self.a1 * self.y1; - self.y1 = output; - output - } -} - -// one pole hp from valley rack free: -// https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp -#[inline] -/// Process a very simple one pole 6dB high pass filter. -/// Useful in various applications. -/// -/// * `input` - Input sample -/// * `freq` - Frequency between 1.0 and 22000.0Hz -/// * `israte` - 1.0 / samplerate -/// * `z` - The first internal buffer of the filter. -/// * `y` - The second internal buffer of the filter. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut z = 0.0; -/// let mut y = 0.0; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let s_out = -/// process_1pole_highpass(*s, freq, 1.0 / 44100.0, &mut z, &mut y); -/// // ... do something with the result here. -/// } -///``` -pub fn process_1pole_highpass(input: f32, freq: f32, israte: f32, z: &mut f32, y: &mut f32) -> f32 { - let b = (-std::f32::consts::TAU * freq * israte).exp(); - let a = (1.0 + b) / 2.0; - - let v = a * input - a * *z + b * *y; - *y = v; - *z = input; - v -} - -#[derive(Debug, Clone, Copy, Default)] -pub struct OnePoleHPF { - israte: F, - a: F, - b: F, - freq: F, - z: F, - y: F, -} - -impl OnePoleHPF { - pub fn new() -> Self { - Self { - israte: f(1.0 / 44100.0), - a: f(0.0), - b: f(0.0), - freq: f(1000.0), - z: f(0.0), - y: f(0.0), - } - } - - pub fn reset(&mut self) { - self.z = f(0.0); - self.y = f(0.0); - } - - #[inline] - fn recalc(&mut self) { - self.b = (f::(-1.0) * F::TAU() * self.freq * self.israte).exp(); - self.a = (f::(1.0) + self.b) / f(2.0); - } - - pub fn set_sample_rate(&mut self, srate: F) { - self.israte = f::(1.0) / srate; - self.recalc(); - } - - #[inline] - pub fn set_freq(&mut self, freq: F) { - if freq != self.freq { - self.freq = freq; - self.recalc(); - } - } - - #[inline] - pub fn process(&mut self, input: F) -> F { - let v = self.a * input - self.a * self.z + self.b * self.y; - - self.y = v; - self.z = input; - - v - } -} - -// one pole from: -// http://www.willpirkle.com/Downloads/AN-4VirtualAnalogFilters.pdf -// (page 5) -#[inline] -/// Process a very simple one pole 6dB low pass filter in TPT form. -/// Useful in various applications, from usage in a synthesizer to -/// damping stuff in a reverb/delay. -/// -/// * `input` - Input sample -/// * `freq` - Frequency between 1.0 and 22000.0Hz -/// * `israte` - 1.0 / samplerate -/// * `z` - The internal one sample buffer of the filter. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut z = 0.0; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let s_out = -/// process_1pole_tpt_highpass(*s, freq, 1.0 / 44100.0, &mut z); -/// // ... do something with the result here. -/// } -///``` -pub fn process_1pole_tpt_lowpass(input: f32, freq: f32, israte: f32, z: &mut f32) -> f32 { - let g = (std::f32::consts::PI * freq * israte).tan(); - let a = g / (1.0 + g); - - let v1 = a * (input - *z); - let v2 = v1 + *z; - *z = v2 + v1; - - // let (m0, m1) = (0.0, 1.0); - // (m0 * input + m1 * v2) as f32); - v2 -} - -// one pole from: -// http://www.willpirkle.com/Downloads/AN-4VirtualAnalogFilters.pdf -// (page 5) -#[inline] -/// Process a very simple one pole 6dB high pass filter in TPT form. -/// Useful in various applications. -/// -/// * `input` - Input sample -/// * `freq` - Frequency between 1.0 and 22000.0Hz -/// * `israte` - 1.0 / samplerate -/// * `z` - The internal one sample buffer of the filter. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut z = 0.0; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let s_out = -/// process_1pole_tpt_lowpass(*s, freq, 1.0 / 44100.0, &mut z); -/// // ... do something with the result here. -/// } -///``` -pub fn process_1pole_tpt_highpass(input: f32, freq: f32, israte: f32, z: &mut f32) -> f32 { - let g = (std::f32::consts::PI * freq * israte).tan(); - let a1 = g / (1.0 + g); - - let v1 = a1 * (input - *z); - let v2 = v1 + *z; - *z = v2 + v1; - - input - v2 -} - -/// The internal oversampling factor of [process_hal_chamberlin_svf]. -const FILTER_OVERSAMPLE_HAL_CHAMBERLIN: usize = 2; -// Hal Chamberlin's State Variable (12dB/oct) filter -// https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/ -// Inspired by SynthV1 by Rui Nuno Capela, under the terms of -// GPLv2 or any later: -/// Process a HAL Chamberlin filter with two delays/state variables that is 12dB. -/// The filter does internal oversampling with very simple decimation to -/// rise the stability for cutoff frequency up to 16kHz. -/// -/// * `input` - Input sample. -/// * `freq` - Frequency in Hz. Please keep it inside 0.0 to 16000.0 Hz! -/// otherwise the filter becomes unstable. -/// * `res` - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended, -/// as the filter will then oscillate itself out of control. -/// * `israte` - 1.0 divided by the sampling rate (eg. 1.0 / 44100.0). -/// * `band` - First state variable, containing the band pass result -/// after processing. -/// * `low` - Second state variable, containing the low pass result -/// after processing. -/// -/// Returned are the results of the high and notch filter. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut band = 0.0; -/// let mut low = 0.0; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let (high, notch) = -/// process_hal_chamberlin_svf( -/// *s, freq, 0.5, 1.0 / 44100.0, &mut band, &mut low); -/// // ... do something with the result here. -/// } -///``` -#[inline] -pub fn process_hal_chamberlin_svf( - input: f32, - freq: f32, - res: f32, - israte: f32, - band: &mut f32, - low: &mut f32, -) -> (f32, f32) { - let q = 1.0 - res; - let cutoff = 2.0 * (std::f32::consts::PI * freq * 0.5 * israte).sin(); - - let mut high = 0.0; - let mut notch = 0.0; - - for _ in 0..FILTER_OVERSAMPLE_HAL_CHAMBERLIN { - *low += cutoff * *band; - high = input - *low - q * *band; - *band += cutoff * high; - notch = high + *low; - } - - //d// println!("q={:4.2} cut={:8.3} freq={:8.1} LP={:8.3} HP={:8.3} BP={:8.3} N={:8.3}", - //d// q, cutoff, freq, *low, high, *band, notch); - - (high, notch) -} - -/// This function processes a Simper SVF with 12dB. It's a much newer algorithm -/// for filtering and provides easy to calculate multiple outputs. -/// -/// * `input` - Input sample. -/// * `freq` - Frequency in Hz. -/// otherwise the filter becomes unstable. -/// * `res` - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended, -/// as the filter will then oscillate itself out of control. -/// * `israte` - 1.0 divided by the sampling rate (eg. 1.0 / 44100.0). -/// * `band` - First state variable, containing the band pass result -/// after processing. -/// * `low` - Second state variable, containing the low pass result -/// after processing. -/// -/// This function returns the low pass, band pass and high pass signal. -/// For a notch or peak filter signal, please consult the following example: -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut ic1eq = 0.0; -/// let mut ic2eq = 0.0; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let (low, band, high) = -/// process_simper_svf( -/// *s, freq, 0.5, 1.0 / 44100.0, &mut ic1eq, &mut ic2eq); -/// -/// // You can easily calculate the notch and peak results too: -/// let notch = low + high; -/// let peak = low - high; -/// // ... do something with the result here. -/// } -///``` -// Simper SVF implemented from -// https://cytomic.com/files/dsp/SvfLinearTrapezoidalSin.pdf -// Big thanks go to Andrew Simper @ Cytomic for developing and publishing -// the paper. -#[inline] -pub fn process_simper_svf( - input: f32, - freq: f32, - res: f32, - israte: f32, - ic1eq: &mut f32, - ic2eq: &mut f32, -) -> (f32, f32, f32) { - // XXX: the 1.989 were tuned by hand, so the resonance is more audible. - let k = 2f32 - (1.989f32 * res); - let w = std::f32::consts::PI * freq * israte; - - let s1 = w.sin(); - let s2 = (2.0 * w).sin(); - let nrm = 1.0 / (2.0 + k * s2); - - let g0 = s2 * nrm; - let g1 = (-2.0 * s1 * s1 - k * s2) * nrm; - let g2 = (2.0 * s1 * s1) * nrm; - - let t0 = input - *ic2eq; - let t1 = g0 * t0 + g1 * *ic1eq; - let t2 = g2 * t0 + g0 * *ic1eq; - - let v1 = t1 + *ic1eq; - let v2 = t2 + *ic2eq; - - *ic1eq += 2.0 * t1; - *ic2eq += 2.0 * t2; - - // low = v2 - // band = v1 - // high = input - k * v1 - v2 - // notch = low + high = input - k * v1 - // peak = low - high = 2 * v2 - input + k * v1 - // all = low + high - k * band = input - 2 * k * v1 - - (v2, v1, input - k * v1 - v2) -} - -/// This function implements a simple Stilson/Moog low pass filter with 24dB. -/// It provides only a low pass output. -/// -/// * `input` - Input sample. -/// * `freq` - Frequency in Hz. -/// otherwise the filter becomes unstable. -/// * `res` - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended, -/// as the filter will then oscillate itself out of control. -/// * `israte` - 1.0 divided by the sampling rate (`1.0 / 44100.0`). -/// * `b0` to `b3` - Internal values used for filtering. -/// * `delay` - A buffer holding other delayed samples. -/// -///``` -/// use hexodsp::dsp::helpers::*; -/// -/// let samples = vec![0.0; 44100]; -/// let mut b0 = 0.0; -/// let mut b1 = 0.0; -/// let mut b2 = 0.0; -/// let mut b3 = 0.0; -/// let mut delay = [0.0; 4]; -/// let mut freq = 1000.0; -/// -/// for s in samples.iter() { -/// let low = -/// process_stilson_moog( -/// *s, freq, 0.5, 1.0 / 44100.0, -/// &mut b0, &mut b1, &mut b2, &mut b3, -/// &mut delay); -/// -/// // ... do something with the result here. -/// } -///``` -// Stilson/Moog implementation partly translated from here: -// https://github.com/ddiakopoulos/MoogLadders/blob/master/src/MusicDSPModel.h -// without any copyright as found on musicdsp.org -// (http://www.musicdsp.org/showone.php?id=24). -// -// It's also found on MusicDSP and has probably no proper license anyways. -// See also: https://github.com/ddiakopoulos/MoogLadders -// and https://github.com/rncbc/synthv1/blob/master/src/synthv1_filter.h#L103 -// and https://github.com/ddiakopoulos/MoogLadders/blob/master/src/MusicDSPModel.h -#[inline] -pub fn process_stilson_moog( - input: f32, - freq: f32, - res: f32, - israte: f32, - b0: &mut f32, - b1: &mut f32, - b2: &mut f32, - b3: &mut f32, - delay: &mut [f32; 4], -) -> f32 { - let cutoff = 2.0 * freq * israte; - - let p = cutoff * (1.8 - 0.8 * cutoff); - let k = 2.0 * (cutoff * std::f32::consts::PI * 0.5).sin() - 1.0; - - let t1 = (1.0 - p) * 1.386249; - let t2 = 12.0 + t1 * t1; - - let res = res * (t2 + 6.0 * t1) / (t2 - 6.0 * t1); - - let x = input - res * *b3; - - // Four cascaded one-pole filters (bilinear transform) - *b0 = x * p + delay[0] * p - k * *b0; - *b1 = *b0 * p + delay[1] * p - k * *b1; - *b2 = *b1 * p + delay[2] * p - k * *b2; - *b3 = *b2 * p + delay[3] * p - k * *b3; - - // Clipping band-limited sigmoid - *b3 -= (*b3 * *b3 * *b3) * 0.166667; - - delay[0] = x; - delay[1] = *b0; - delay[2] = *b1; - delay[3] = *b2; - - *b3 -} - -// translated from Odin 2 Synthesizer Plugin -// Copyright (C) 2020 TheWaveWarden -// under GPLv3 or any later -#[derive(Debug, Clone, Copy)] -pub struct DCBlockFilter { - xm1: F, - ym1: F, - r: F, -} - -impl DCBlockFilter { - pub fn new() -> Self { - Self { xm1: f(0.0), ym1: f(0.0), r: f(0.995) } - } - - pub fn reset(&mut self) { - self.xm1 = f(0.0); - self.ym1 = f(0.0); - } - - pub fn set_sample_rate(&mut self, srate: F) { - self.r = f(0.995); - if srate > f(90000.0) { - self.r = f(0.9965); - } else if srate > f(120000.0) { - self.r = f(0.997); - } - } - - pub fn next(&mut self, input: F) -> F { - let y = input - self.xm1 + self.r * self.ym1; - self.xm1 = input; - self.ym1 = y; - y as F - } -} - -// PolyBLEP by Tale -// (slightly modified) -// http://www.kvraudio.com/forum/viewtopic.php?t=375517 -// from http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/ -// -// default for `pw' should be 1.0, it's the pulse width -// for the square wave. -#[allow(dead_code)] -fn poly_blep_64(t: f64, dt: f64) -> f64 { - if t < dt { - let t = t / dt; - 2. * t - (t * t) - 1. - } else if t > (1.0 - dt) { - let t = (t - 1.0) / dt; - (t * t) + 2. * t + 1. - } else { - 0. - } -} - -fn poly_blep(t: f32, dt: f32) -> f32 { - if t < dt { - let t = t / dt; - 2. * t - (t * t) - 1. - } else if t > (1.0 - dt) { - let t = (t - 1.0) / dt; - (t * t) + 2. * t + 1. - } else { - 0. - } -} - -/// This is a band-limited oscillator based on the PolyBlep technique. -/// Here is a quick example on how to use it: -/// -///``` -/// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; -/// -/// // Randomize the initial phase to make cancellation on summing less -/// // likely: -/// let mut osc = -/// PolyBlepOscillator::new(rand_01() * 0.25); -/// -/// -/// let freq = 440.0; // Hz -/// let israte = 1.0 / 44100.0; // Seconds per Sample -/// let pw = 0.2; // Pulse-Width for the next_pulse() -/// let waveform = 0; // 0 being pulse in this example, 1 being sawtooth -/// -/// let mut block_of_samples = [0.0; 128]; -/// // in your process function: -/// for output_sample in block_of_samples.iter_mut() { -/// *output_sample = -/// if waveform == 1 { -/// osc.next_saw(freq, israte) -/// } else { -/// osc.next_pulse(freq, israte, pw) -/// } -/// } -///``` -#[derive(Debug, Clone)] -pub struct PolyBlepOscillator { - phase: f32, - init_phase: f32, - last_output: f32, -} - -impl PolyBlepOscillator { - /// Create a new instance of [PolyBlepOscillator]. - /// - /// * `init_phase` - Initial phase of the oscillator. - /// Range of this parameter is from 0.0 to 1.0. Passing a random - /// value is advised for preventing phase cancellation when summing multiple - /// oscillators. - /// - ///``` - /// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; - /// - /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25); - ///``` - pub fn new(init_phase: f32) -> Self { - Self { phase: 0.0, last_output: 0.0, init_phase } - } - - /// Reset the internal state of the oscillator as if you just called - /// [PolyBlepOscillator::new]. - #[inline] - pub fn reset(&mut self) { - self.phase = self.init_phase; - self.last_output = 0.0; - } - - /// Creates the next sample of a sine wave. - /// - /// * `freq` - The frequency in Hz. - /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`. - ///``` - /// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; - /// - /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25); - /// - /// let freq = 440.0; // Hz - /// let israte = 1.0 / 44100.0; // Seconds per Sample - /// - /// // ... - /// let sample = osc.next_sin(freq, israte); - /// // ... - ///``` - #[inline] - pub fn next_sin(&mut self, freq: f32, israte: f32) -> f32 { - let phase_inc = freq * israte; - - let s = fast_sin(self.phase * 2.0 * std::f32::consts::PI); - - self.phase += phase_inc; - self.phase = self.phase.fract(); - - s as f32 - } - - /// Creates the next sample of a triangle wave. Please note that the - /// bandlimited waveform needs a few initial samples to swing in. - /// - /// * `freq` - The frequency in Hz. - /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`. - ///``` - /// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; - /// - /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25); - /// - /// let freq = 440.0; // Hz - /// let israte = 1.0 / 44100.0; // Seconds per Sample - /// - /// // ... - /// let sample = osc.next_tri(freq, israte); - /// // ... - ///``` - #[inline] - pub fn next_tri(&mut self, freq: f32, israte: f32) -> f32 { - let phase_inc = freq * israte; - - let mut s = if self.phase < 0.5 { 1.0 } else { -1.0 }; - - s += poly_blep(self.phase, phase_inc); - s -= poly_blep((self.phase + 0.5).fract(), phase_inc); - - // leaky integrator: y[n] = A * x[n] + (1 - A) * y[n-1] - s = phase_inc * s + (1.0 - phase_inc) * self.last_output; - self.last_output = s; - - self.phase += phase_inc; - self.phase = self.phase.fract(); - - // the signal is a bit too weak, we need to amplify it - // or else the volume diff between the different waveforms - // is too big: - s * 4.0 - } - - /// Creates the next sample of a sawtooth wave. - /// - /// * `freq` - The frequency in Hz. - /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`. - ///``` - /// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; - /// - /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25); - /// - /// let freq = 440.0; // Hz - /// let israte = 1.0 / 44100.0; // Seconds per Sample - /// - /// // ... - /// let sample = osc.next_saw(freq, israte); - /// // ... - ///``` - #[inline] - pub fn next_saw(&mut self, freq: f32, israte: f32) -> f32 { - let phase_inc = freq * israte; - - let mut s = (2.0 * self.phase) - 1.0; - s -= poly_blep(self.phase, phase_inc); - - self.phase += phase_inc; - self.phase = self.phase.fract(); - - s - } - - /// Creates the next sample of a pulse wave. - /// In comparison to [PolyBlepOscillator::next_pulse_no_dc] this - /// version is DC compensated, so that you may add multiple different - /// pulse oscillators for a unison effect without too big DC issues. - /// - /// * `freq` - The frequency in Hz. - /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`. - /// * `pw` - The pulse width. Use the value 0.0 for a square wave. - ///``` - /// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; - /// - /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25); - /// - /// let freq = 440.0; // Hz - /// let israte = 1.0 / 44100.0; // Seconds per Sample - /// let pw = 0.0; // 0.0 is a square wave. - /// - /// // ... - /// let sample = osc.next_pulse(freq, israte, pw); - /// // ... - ///``` - #[inline] - pub fn next_pulse(&mut self, freq: f32, israte: f32, pw: f32) -> f32 { - let phase_inc = freq * israte; - - let pw = (0.1 * pw) + ((1.0 - pw) * 0.5); // some scaling - let dc_compensation = (0.5 - pw) * 2.0; - - let mut s = if self.phase < pw { 1.0 } else { -1.0 }; - - s += poly_blep(self.phase, phase_inc); - s -= poly_blep((self.phase + (1.0 - pw)).fract(), phase_inc); - - s += dc_compensation; - - self.phase += phase_inc; - self.phase = self.phase.fract(); - - s - } - - /// Creates the next sample of a pulse wave. - /// In comparison to [PolyBlepOscillator::next_pulse] this - /// version is not DC compensated. So be careful when adding multiple - /// of this or generally using it in an audio context. - /// - /// * `freq` - The frequency in Hz. - /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`. - /// * `pw` - The pulse width. Use the value 0.0 for a square wave. - ///``` - /// use hexodsp::dsp::helpers::{PolyBlepOscillator, rand_01}; - /// - /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25); - /// - /// let freq = 440.0; // Hz - /// let israte = 1.0 / 44100.0; // Seconds per Sample - /// let pw = 0.0; // 0.0 is a square wave. - /// - /// // ... - /// let sample = osc.next_pulse_no_dc(freq, israte, pw); - /// // ... - ///``` - #[inline] - pub fn next_pulse_no_dc(&mut self, freq: f32, israte: f32, pw: f32) -> f32 { - let phase_inc = freq * israte; - - let pw = (0.1 * pw) + ((1.0 - pw) * 0.5); // some scaling - - let mut s = if self.phase < pw { 1.0 } else { -1.0 }; - - s += poly_blep(self.phase, phase_inc); - s -= poly_blep((self.phase + (1.0 - pw)).fract(), phase_inc); - - self.phase += phase_inc; - self.phase = self.phase.fract(); - - s - } -} - -// This oscillator is based on the work "VECTOR PHASESHAPING SYNTHESIS" -// by: Jari Kleimola*, Victor Lazzarini†, Joseph Timoney†, Vesa Välimäki* -// *Aalto University School of Electrical Engineering Espoo, Finland; -// †National University of Ireland, Maynooth Ireland -// -// See also this PDF: http://recherche.ircam.fr/pub/dafx11/Papers/55_e.pdf -/// Vector Phase Shaping Oscillator. -/// The parameters `d` and `v` control the shape of the sinus -/// wave. This leads to interesting modulation properties of those -/// control values. -/// -///``` -/// use hexodsp::dsp::helpers::{VPSOscillator, rand_01}; -/// -/// // Randomize the initial phase to make cancellation on summing less -/// // likely: -/// let mut osc = -/// VPSOscillator::new(rand_01() * 0.25); -/// -/// -/// let freq = 440.0; // Hz -/// let israte = 1.0 / 44100.0; // Seconds per Sample -/// let d = 0.5; // Range: 0.0 to 1.0 -/// let v = 0.75; // Range: 0.0 to 1.0 -/// -/// let mut block_of_samples = [0.0; 128]; -/// // in your process function: -/// for output_sample in block_of_samples.iter_mut() { -/// // It is advised to limit the `v` value, because with certain -/// // `d` values the combination creates just a DC offset. -/// let v = VPSOscillator::limit_v(d, v); -/// *output_sample = osc.next(freq, israte, d, v); -/// } -///``` -/// -/// It can be beneficial to apply distortion and oversampling. -/// Especially oversampling can be important for some `d` and `v` -/// combinations, even without distortion. -/// -///``` -/// use hexodsp::dsp::helpers::{VPSOscillator, rand_01, apply_distortion}; -/// use hexodsp::dsp::biquad::Oversampling; -/// -/// let mut osc = VPSOscillator::new(rand_01() * 0.25); -/// let mut ovr : Oversampling<4> = Oversampling::new(); -/// -/// let freq = 440.0; // Hz -/// let israte = 1.0 / 44100.0; // Seconds per Sample -/// let d = 0.5; // Range: 0.0 to 1.0 -/// let v = 0.75; // Range: 0.0 to 1.0 -/// -/// let mut block_of_samples = [0.0; 128]; -/// // in your process function: -/// for output_sample in block_of_samples.iter_mut() { -/// // It is advised to limit the `v` value, because with certain -/// // `d` values the combination creates just a DC offset. -/// let v = VPSOscillator::limit_v(d, v); -/// -/// let overbuf = ovr.resample_buffer(); -/// for b in overbuf { -/// *b = apply_distortion(osc.next(freq, israte, d, v), 0.9, 1); -/// } -/// *output_sample = ovr.downsample(); -/// } -///``` -#[derive(Debug, Clone)] -pub struct VPSOscillator { - phase: f32, - init_phase: f32, -} - -impl VPSOscillator { - /// Create a new instance of [VPSOscillator]. - /// - /// * `init_phase` - The initial phase of the oscillator. - pub fn new(init_phase: f32) -> Self { - Self { phase: 0.0, init_phase } - } - - /// Reset the phase of the oscillator to the initial phase. - #[inline] - pub fn reset(&mut self) { - self.phase = self.init_phase; - } - - #[inline] - fn s(p: f32) -> f32 { - -(std::f32::consts::TAU * p).cos() - } - - #[inline] - fn phi_vps(x: f32, v: f32, d: f32) -> f32 { - if x < d { - (v * x) / d - } else { - v + ((1.0 - v) * (x - d)) / (1.0 - d) - } - } - - /// This rather complicated function blends out some - /// combinations of 'd' and 'v' that just lead to a constant DC - /// offset. Which is not very useful in an audio oscillator - /// context. - /// - /// Call this before passing `v` to [VPSOscillator::next]. - #[inline] - pub fn limit_v(d: f32, v: f32) -> f32 { - let delta = 0.5 - (d - 0.5).abs(); - if delta < 0.05 { - let x = (0.05 - delta) * 19.99; - if d < 0.5 { - let mm = x * 0.5; - let max = 1.0 - mm; - if v > max && v < 1.0 { - max - } else if v >= 1.0 && v < (1.0 + mm) { - 1.0 + mm - } else { - v - } - } else { - if v < 1.0 { - v.clamp(x * 0.5, 1.0) - } else { - v - } - } - } else { - v - } - } - - /// Creates the next sample of this oscillator. - /// - /// * `freq` - The frequency in Hz. - /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`. - /// * `d` - The phase distortion parameter `d` which must be in the range `0.0` to `1.0`. - /// * `v` - The phase distortion parameter `v` which must be in the range `0.0` to `1.0`. - /// - /// It is advised to limit the `v` using the [VPSOscillator::limit_v] function - /// before calling this function. To prevent DC offsets when modulating the parameters. - pub fn next(&mut self, freq: f32, israte: f32, d: f32, v: f32) -> f32 { - let s = Self::s(Self::phi_vps(self.phase, v, d)); - - self.phase += freq * israte; - self.phase = self.phase.fract(); - - s - } -} - -// Adapted from https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/LFO.hpp -// -// ValleyRackFree Copyright (C) 2020, Valley Audio Soft, Dale Johnson -// Adapted under the GPL-3.0-or-later License. -/// An LFO with a variable reverse point, which can go from reverse Saw, to Tri -/// and to Saw, depending on the reverse point. -#[derive(Debug, Clone, Copy)] -pub struct TriSawLFO { - /// The (inverse) sample rate. Eg. 1.0 / 44100.0. - israte: F, - /// The current oscillator phase. - phase: F, - /// The point from where the falling edge will be used. - rev: F, - /// The frequency. - freq: F, - /// Precomputed rise/fall rate of the LFO. - rise_r: F, - fall_r: F, - /// Initial phase offset. - init_phase: F, -} - -impl TriSawLFO { - pub fn new() -> Self { - let mut this = Self { - israte: f(1.0 / 44100.0), - phase: f(0.0), - rev: f(0.5), - freq: f(1.0), - fall_r: f(0.0), - rise_r: f(0.0), - init_phase: f(0.0), - }; - this.recalc(); - this - } - - pub fn set_phase_offs(&mut self, phase: F) { - self.init_phase = phase; - self.phase = phase; - } - - #[inline] - fn recalc(&mut self) { - self.rev = fclampc(self.rev, 0.0001, 0.999); - self.rise_r = f::(1.0) / self.rev; - self.fall_r = f::(-1.0) / (f::(1.0) - self.rev); - } - - pub fn set_sample_rate(&mut self, srate: F) { - self.israte = f::(1.0) / (srate as F); - self.recalc(); - } - - pub fn reset(&mut self) { - self.phase = self.init_phase; - self.rev = f(0.5); - } - - #[inline] - pub fn set(&mut self, freq: F, rev: F) { - self.freq = freq as F; - self.rev = rev as F; - self.recalc(); - } - - #[inline] - pub fn next_unipolar(&mut self) -> F { - if self.phase >= f(1.0) { - self.phase = self.phase - f(1.0); - } - - let s = if self.phase < self.rev { - self.phase * self.rise_r - } else { - self.phase * self.fall_r - self.fall_r - }; - - self.phase = self.phase + self.freq * self.israte; - - s - } - - #[inline] - pub fn next_bipolar(&mut self) -> F { - (self.next_unipolar() * f(2.0)) - f(1.0) - } -} - -#[derive(Debug, Clone)] -pub struct Quantizer { - old_mask: i64, - lkup_tbl: [(f32, f32); 24], - last_key: f32, -} - -impl Quantizer { - pub fn new() -> Self { - Self { old_mask: 0xFFFF_FFFF, lkup_tbl: [(0.0, 0.0); 24], last_key: 0.0 } - } - - #[inline] - pub fn set_keys(&mut self, keys_mask: i64) { - if keys_mask == self.old_mask { - return; - } - self.old_mask = keys_mask; - - self.setup_lookup_table(); - } - - #[inline] - fn setup_lookup_table(&mut self) { - let mask = self.old_mask; - let any_enabled = mask > 0x0; - - for i in 0..24 { - let mut min_d_note_idx = 0; - let mut min_dist = 1000000000; - - for note in -12..=24 { - let dist = ((i + 1_i64) / 2 - note).abs(); - let note_idx = note.rem_euclid(12); - - // XXX: We add 9 here for the mask lookup, - // to shift the keyboard, which starts at C! - // And first bit in the mask is the C note. 10th is the A note. - if any_enabled && (mask & (0x1 << ((note_idx + 9) % 12))) == 0x0 { - continue; - } - - //d// println!("I={:3} NOTE={:3} (IDX={:3} => bitset {}) DIST={:3}", - //d// i, note, note_idx, - //d// if (mask & (0x1 << ((note_idx + 9) % 12))) > 0x0 { 1 } else { 0 }, - //d// dist); - - if dist < min_dist { - min_d_note_idx = note; - min_dist = dist; - } else { - break; - } - } - - self.lkup_tbl[i as usize] = ( - (min_d_note_idx + 9).rem_euclid(12) as f32 * (0.1 / 12.0), - min_d_note_idx.rem_euclid(12) as f32 * (0.1 / 12.0) - + (if min_d_note_idx < 0 { - -0.1 - } else if min_d_note_idx > 11 { - 0.1 - } else { - 0.0 - }), - ); - } - //d// println!("TBL: {:?}", self.lkup_tbl); - } - - #[inline] - pub fn last_key_pitch(&self) -> f32 { - self.last_key - } - - #[inline] - pub fn process(&mut self, inp: f32) -> f32 { - let note_num = (inp * 240.0).round() as i64; - let octave = note_num.div_euclid(24); - let note_idx = note_num - octave * 24; - - // println!( - // "INP {:7.4} => octave={:3}, note_idx={:3} note_num={:3} inp={:9.6}", - // inp, octave, note_idx, note_num, inp * 240.0); - //d// println!("TBL: {:?}", self.lkup_tbl); - - let (ui_key_pitch, note_pitch) = self.lkup_tbl[note_idx as usize % 24]; - self.last_key = ui_key_pitch; - note_pitch + octave as f32 * 0.1 - } -} - -#[derive(Debug, Clone)] -pub struct CtrlPitchQuantizer { - /// All keys, containing the min/max octave! - keys: Vec, - /// Only the used keys with their pitches from the UI - used_keys: [f32; 12], - /// A value combination of the arguments to `update_keys`. - input_params: u64, - /// The number of used keys from the mask. - mask_key_count: u16, - /// The last key for the pitch that was returned by `process`. - last_key: u8, -} - -const QUANT_TUNE_TO_A4: f32 = (9.0 / 12.0) * 0.1; - -impl CtrlPitchQuantizer { - pub fn new() -> Self { - Self { - keys: vec![0.0; 12 * 10], - used_keys: [0.0; 12], - mask_key_count: 0, - input_params: 0xFFFFFFFFFF, - last_key: 0, - } - } - - #[inline] - pub fn last_key_pitch(&self) -> f32 { - self.used_keys[self.last_key as usize % (self.mask_key_count as usize)] + QUANT_TUNE_TO_A4 - } - - #[inline] - pub fn update_keys(&mut self, mut mask: i64, min_oct: i64, max_oct: i64) { - let inp_params = (mask as u64) | ((min_oct as u64) << 12) | ((max_oct as u64) << 20); - - if self.input_params == inp_params { - return; - } - - self.input_params = inp_params; - - let mut mask_count = 0; - - // set all keys, if none are set! - if mask == 0x0 { - mask = 0xFFFF; - } - - for i in 0..12 { - if mask & (0x1 << i) > 0 { - self.used_keys[mask_count] = (i as f32 / 12.0) * 0.1 - QUANT_TUNE_TO_A4; - mask_count += 1; - } - } - - self.keys.clear(); - - let min_oct = min_oct as usize; - for o in 0..min_oct { - let o = min_oct - o; - - for i in 0..mask_count { - self.keys.push(self.used_keys[i] - (o as f32) * 0.1); - } - } - - for i in 0..mask_count { - self.keys.push(self.used_keys[i]); - } - - let max_oct = max_oct as usize; - for o in 1..=max_oct { - for i in 0..mask_count { - self.keys.push(self.used_keys[i] + (o as f32) * 0.1); - } - } - - self.mask_key_count = mask_count as u16; - } - - #[inline] - pub fn signal_to_pitch(&mut self, inp: f32) -> f32 { - let len = self.keys.len(); - let key = (inp.clamp(0.0, 0.9999) * (len as f32)).floor(); - let key = key as usize % len; - self.last_key = key as u8; - self.keys[key] - } -} - -#[macro_export] -macro_rules! fa_distort { - ($formatter: expr, $v: expr, $denorm_v: expr) => {{ - let s = match ($v.round() as usize) { - 0 => "Off", - 1 => "TanH", - 2 => "B.D.Jong", - 3 => "Fold", - _ => "?", - }; - write!($formatter, "{}", s) - }}; -} - -#[inline] -pub fn apply_distortion(s: f32, damt: f32, dist_type: u8) -> f32 { - match dist_type { - 1 => (damt.clamp(0.01, 1.0) * 100.0 * s).tanh(), - 2 => f_distort(1.0, damt * damt * damt * 1000.0, s), - 3 => { - let damt = damt.clamp(0.0, 0.99); - let damt = 1.0 - damt * damt; - f_fold_distort(1.0, damt, s) * (1.0 / damt) - } - _ => s, - } -} - -//pub struct UnisonBlep { -// oscs: Vec, -//// dc_block: crate::filter::DCBlockFilter, -//} -// -//impl UnisonBlep { -// pub fn new(max_unison: usize) -> Self { -// let mut oscs = vec![]; -// let mut rng = RandGen::new(); -// -// let dis_init_phase = 0.05; -// for i in 0..(max_unison + 1) { -// // randomize phases so we fatten the unison, get -// // less DC and not an amplified signal until the -// // detune desyncs the waves. -// // But no random phase for first, so we reduce the click -// let init_phase = -// if i == 0 { 0.0 } else { rng.next_open01() }; -// oscs.push(PolyBlepOscillator::new(init_phase)); -// } -// -// Self { -// oscs, -//// dc_block: crate::filter::DCBlockFilter::new(), -// } -// } -// -// pub fn set_sample_rate(&mut self, srate: f32) { -//// self.dc_block.set_sample_rate(srate); -// for o in self.oscs.iter_mut() { -// o.set_sample_rate(srate); -// } -// } -// -// pub fn reset(&mut self) { -//// self.dc_block.reset(); -// for o in self.oscs.iter_mut() { -// o.reset(); -// } -// } -// -// pub fn next(&mut self, params: &P) -> f32 { -// let unison = -// (params.unison().floor() as usize) -// .min(self.oscs.len() - 1); -// let detune = params.detune() as f64; -// -// let mix = (1.0 / ((unison + 1) as f32)).sqrt(); -// -// let mut s = mix * self.oscs[0].next(params, 0.0); -// -// for u in 0..unison { -// let detune_factor = -// detune * (((u / 2) + 1) as f64 -// * if (u % 2) == 0 { 1.0 } else { -1.0 }); -// s += mix * self.oscs[u + 1].next(params, detune_factor * 0.01); -// } -// -//// self.dc_block.next(s) -// s -// } -//} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_range2p_exp() { - let a = p2range_exp(0.5, 1.0, 100.0); - let x = range2p_exp(a, 1.0, 100.0); - - assert!((x - 0.5).abs() < std::f32::EPSILON); - } - - #[test] - fn check_range2p() { - let a = p2range(0.5, 1.0, 100.0); - let x = range2p(a, 1.0, 100.0); - - assert!((x - 0.5).abs() < std::f32::EPSILON); - } -} diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 219c707..f77e6b8 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -200,14 +200,15 @@ the help documentation: For non trivial DSP nodes, the DSP code itself should be separate from it's `dsp/node_*.rs` file. That file should only care about interfacing the DSP code with HexoDSP, but not implement all the complicated DSP code. It's good practice to factor out the DSP code into -a separate module or file. +a separate module or file. It is preferable to add your custom DSP code to the `synfx-dsp` +crate [synfx-dsp](https://github.com/WeirdConstructor/synfx-dsp). -Look at `node_tslfo.rs` for instance. It wires up the `TriSawLFO` from `dsp/helpers.rs` +Look at `node_tslfo.rs` for instance. It wires up the `TriSawLFO` from `synfx-dsp` to the HexoDSP node interface. ```ignore // node_tslfo.rs - use super::helpers::{TriSawLFO, Trigger}; + use synfx_dsp::{TriSawLFO, Trigger}; #[derive(Debug, Clone)] pub struct TsLFO { @@ -233,7 +234,7 @@ to the HexoDSP node interface. } ``` -The code for `TriSawLFO` in `dsp/helpers.rs` is then independent and reusable else where. +The code for `TriSawLFO` in `synfx-dsp` is then independent and reusable else where. ### Node Parameter/Inputs @@ -538,9 +539,6 @@ mod node_tslfo; #[allow(non_upper_case_globals)] mod node_vosc; -pub mod biquad; -pub mod dattorro; -pub mod helpers; mod satom; pub mod tracker; @@ -564,7 +562,7 @@ use crate::fa_cqnt; use crate::fa_cqnt_omax; use crate::fa_cqnt_omin; use crate::fa_delay_mode; -use crate::fa_distort; +use synfx_dsp::fa_distort; use crate::fa_map_clip; use crate::fa_mux9_in_cnt; use crate::fa_noise_mode; @@ -1586,7 +1584,7 @@ fn rand_node_satisfies_spec(nid: NodeId, sel: RandNodeSelector) -> bool { } pub fn get_rand_node_id(count: usize, sel: RandNodeSelector) -> Vec { - let mut sm = crate::dsp::helpers::SplitMix64::new_time_seed(); + let mut sm = synfx_dsp::SplitMix64::new_time_seed(); let mut out = vec![]; let mut cnt = 0; @@ -1966,7 +1964,7 @@ macro_rules! make_node_info_enum { 1 => 0.05, 2 => 0.1, // 0.25 just to protect against sine cancellation - _ => crate::dsp::helpers::rand_01() * 0.25 + _ => synfx_dsp::rand_01() * 0.25 } } diff --git a/src/dsp/node_ad.rs b/src/dsp/node_ad.rs index 55c26f5..ef69e5a 100644 --- a/src/dsp/node_ad.rs +++ b/src/dsp/node_ad.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; +use synfx_dsp::{sqrt4_to_pow4, TrigSignal, Trigger}; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/node_allp.rs b/src/dsp/node_allp.rs index 901293d..adf3cbd 100644 --- a/src/dsp/node_allp.rs +++ b/src/dsp/node_allp.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::AllPass; +use synfx_dsp::AllPass; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_biqfilt.rs b/src/dsp/node_biqfilt.rs index 43a9d4a..8d3c373 100644 --- a/src/dsp/node_biqfilt.rs +++ b/src/dsp/node_biqfilt.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::biquad::*; +use synfx_dsp::*; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_bosc.rs b/src/dsp/node_bosc.rs index b965a41..e4eb603 100644 --- a/src/dsp/node_bosc.rs +++ b/src/dsp/node_bosc.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::PolyBlepOscillator; +use synfx_dsp::PolyBlepOscillator; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/node_bowstri.rs b/src/dsp/node_bowstri.rs index a806118..f16ea01 100644 --- a/src/dsp/node_bowstri.rs +++ b/src/dsp/node_bowstri.rs @@ -2,8 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::biquad::Biquad; -use crate::dsp::helpers::{DelayBuffer, FixedOnePole}; +use synfx_dsp::{DelayBuffer, FixedOnePole, Biquad}; use crate::dsp::{ denorm, denorm_offs, inp, out, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/node_comb.rs b/src/dsp/node_comb.rs index 36db6a4..285368b 100644 --- a/src/dsp/node_comb.rs +++ b/src/dsp/node_comb.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers; +use synfx_dsp; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; @@ -21,12 +21,12 @@ macro_rules! fa_comb_mode { /// A simple amplifier #[derive(Debug, Clone)] pub struct Comb { - comb: Box, + comb: Box, } impl Comb { pub fn new(_nid: &NodeId) -> Self { - Self { comb: Box::new(helpers::Comb::new()) } + Self { comb: Box::new(synfx_dsp::Comb::new()) } } pub const inp: &'static str = "Comb inp\nThe signal input for the comb filter.\nRange: (-1..1)"; diff --git a/src/dsp/node_cqnt.rs b/src/dsp/node_cqnt.rs index 8d607a4..3f36f3a 100644 --- a/src/dsp/node_cqnt.rs +++ b/src/dsp/node_cqnt.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::{ChangeTrig, CtrlPitchQuantizer}; +use synfx_dsp::{ChangeTrig, CtrlPitchQuantizer}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_delay.rs b/src/dsp/node_delay.rs index cd8c821..2e2e826 100644 --- a/src/dsp/node_delay.rs +++ b/src/dsp/node_delay.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::{crossfade, DelayBuffer, TriggerSampleClock}; +use synfx_dsp::{crossfade, DelayBuffer, TriggerSampleClock}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_mux9.rs b/src/dsp/node_mux9.rs index c637f64..ff8aa58 100644 --- a/src/dsp/node_mux9.rs +++ b/src/dsp/node_mux9.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::Trigger; +use synfx_dsp::Trigger; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_noise.rs b/src/dsp/node_noise.rs index 8f4b548..ce3d5ca 100644 --- a/src/dsp/node_noise.rs +++ b/src/dsp/node_noise.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::Rng; +use synfx_dsp::Rng; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_pverb.rs b/src/dsp/node_pverb.rs index cdb8529..20853c1 100644 --- a/src/dsp/node_pverb.rs +++ b/src/dsp/node_pverb.rs @@ -2,8 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use super::dattorro::{DattorroReverb, DattorroReverbParams}; -use super::helpers::crossfade; +use synfx_dsp::{DattorroReverb, DattorroReverbParams, crossfade}; use crate::dsp::{denorm, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_quant.rs b/src/dsp/node_quant.rs index d18ae51..6955fd6 100644 --- a/src/dsp/node_quant.rs +++ b/src/dsp/node_quant.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::{ChangeTrig, Quantizer}; +use synfx_dsp::{ChangeTrig, Quantizer}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_rndwk.rs b/src/dsp/node_rndwk.rs index 748cc42..3996944 100644 --- a/src/dsp/node_rndwk.rs +++ b/src/dsp/node_rndwk.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::{Rng, SlewValue, Trigger}; +use synfx_dsp::{Rng, SlewValue, Trigger}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_sampl.rs b/src/dsp/node_sampl.rs index ce51506..0e7e6ae 100644 --- a/src/dsp/node_sampl.rs +++ b/src/dsp/node_sampl.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use super::helpers::{cubic_interpolate, Trigger}; +use synfx_dsp::{cubic_interpolate, Trigger}; use crate::dsp::{at, denorm, denorm_offs, inp, out}; //, inp, denorm, denorm_v, inp_dir, at}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_scope.rs b/src/dsp/node_scope.rs index 39bbe48..76a6f46 100644 --- a/src/dsp/node_scope.rs +++ b/src/dsp/node_scope.rs @@ -8,7 +8,7 @@ // Copyright by Andrew Belt, 2021 //use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; -use crate::dsp::helpers::CustomTrigger; +use synfx_dsp::CustomTrigger; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::SCOPE_SAMPLES; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_sfilter.rs b/src/dsp/node_sfilter.rs index 20c3f08..ad6f519 100644 --- a/src/dsp/node_sfilter.rs +++ b/src/dsp/node_sfilter.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::{ +use synfx_dsp::{ process_1pole_highpass, process_1pole_lowpass, process_1pole_tpt_highpass, process_1pole_tpt_lowpass, process_hal_chamberlin_svf, process_simper_svf, process_stilson_moog, diff --git a/src/dsp/node_sin.rs b/src/dsp/node_sin.rs index 9fe362a..05afe05 100644 --- a/src/dsp/node_sin.rs +++ b/src/dsp/node_sin.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::fast_sin; +use synfx_dsp::fast_sin; use crate::dsp::{ denorm_offs, inp, out, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/node_test.rs b/src/dsp/node_test.rs index 5b36563..3eb0b32 100644 --- a/src/dsp/node_test.rs +++ b/src/dsp/node_test.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::TrigSignal; +use synfx_dsp::TrigSignal; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/node_tseq.rs b/src/dsp/node_tseq.rs index 215cee1..3397568 100644 --- a/src/dsp/node_tseq.rs +++ b/src/dsp/node_tseq.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::helpers::{Trigger, TriggerPhaseClock}; +use synfx_dsp::{Trigger, TriggerPhaseClock}; use crate::dsp::tracker::TrackerBackend; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; diff --git a/src/dsp/node_tslfo.rs b/src/dsp/node_tslfo.rs index eff09d5..00355d6 100644 --- a/src/dsp/node_tslfo.rs +++ b/src/dsp/node_tslfo.rs @@ -2,7 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use super::helpers::{TriSawLFO, Trigger}; +use synfx_dsp::{TriSawLFO, Trigger}; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/node_vosc.rs b/src/dsp/node_vosc.rs index 1f04bdc..a35a371 100644 --- a/src/dsp/node_vosc.rs +++ b/src/dsp/node_vosc.rs @@ -2,8 +2,7 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use crate::dsp::biquad::Oversampling; -use crate::dsp::helpers::{apply_distortion, VPSOscillator}; +use synfx_dsp::{Oversampling, apply_distortion, VPSOscillator}; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; diff --git a/src/dsp/tracker/sequencer.rs b/src/dsp/tracker/sequencer.rs index 194ffa1..6ea6a7e 100644 --- a/src/dsp/tracker/sequencer.rs +++ b/src/dsp/tracker/sequencer.rs @@ -4,7 +4,7 @@ use super::MAX_COLS; use super::MAX_PATTERN_LEN; -use crate::dsp::helpers::SplitMix64; +use synfx_dsp::SplitMix64; pub struct PatternSequencer { rows: usize, diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 06e6e55..8023adc 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -89,7 +89,7 @@ pub fn new_node_engine() -> (NodeConfigurator, NodeExecutor) { // XXX: This is one of the earliest and most consistent points // in runtime to do this kind of initialization: - crate::dsp::helpers::init_cos_tab(); + synfx_dsp::init_cos_tab(); (nc, ne) } diff --git a/tests/delay_buffer.rs b/tests/delay_buffer.rs deleted file mode 100644 index d27a1d5..0000000 --- a/tests/delay_buffer.rs +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) 2021 Weird Constructor -// This file is a part of HexoDSP. Released under GPL-3.0-or-later. -// See README.md and COPYING for details. - -mod common; -use common::*; - -#[test] -fn check_delaybuffer_linear_interpolation() { - let mut buf = crate::helpers::DelayBuffer::new(); - - buf.feed(0.0); - buf.feed(0.1); - buf.feed(0.2); - buf.feed(0.3); - buf.feed(0.4); - buf.feed(0.5); - buf.feed(0.6); - buf.feed(0.7); - buf.feed(0.8); - buf.feed(0.9); - buf.feed(1.0); - - let mut samples_out = vec![]; - let mut pos = 0.0; - let pos_inc = 0.5; - for _ in 0..20 { - samples_out.push(buf.linear_interpolate_at_s(pos)); - pos += pos_inc; - } - - assert_vec_feq!( - samples_out, - vec![ - 1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.5, 0.45, 0.4, 0.35000002, 0.3, - 0.25, 0.2, 0.15, 0.1, 0.05 - ] - ); - - let mut samples_out = vec![]; - let mut pos = 0.0; - let pos_inc = 0.2; - for _ in 0..30 { - samples_out.push(buf.linear_interpolate_at_s(pos)); - pos += pos_inc; - } - - assert_vec_feq!( - samples_out, - vec![ - 1.0, 0.98, 0.96, 0.94, 0.91999996, 0.9, 0.88, 0.85999995, 0.84, 0.82, 0.8, 0.78, 0.76, - 0.73999995, 0.71999997, 0.6999999, 0.67999995, 0.65999997, 0.6399999, 0.61999995, - 0.59999996, 0.58, 0.56, 0.54, 0.52000004, 0.50000006, 0.48000008, 0.4600001, - 0.44000012, 0.42000014 - ] - ); -} - -#[test] -fn check_delaybuffer_nearest() { - let mut buf = crate::helpers::DelayBuffer::new(); - - buf.feed(0.0); - buf.feed(0.1); - buf.feed(0.2); - buf.feed(0.3); - buf.feed(0.4); - buf.feed(0.5); - buf.feed(0.6); - buf.feed(0.7); - buf.feed(0.8); - buf.feed(0.9); - buf.feed(1.0); - - let mut samples_out = vec![]; - let mut pos = 0.0; - let pos_inc = 0.5; - for _ in 0..20 { - samples_out.push(buf.at(pos as usize)); - pos += pos_inc; - } - - assert_vec_feq!( - samples_out, - vec![ - 1.0, 1.0, 0.9, 0.9, 0.8, 0.8, 0.7, 0.7, 0.6, 0.6, 0.5, 0.5, 0.4, 0.4, 0.3, 0.3, 0.2, - 0.2, 0.1, 0.1 - ] - ); - - let mut samples_out = vec![]; - let mut pos = 0.0; - let pos_inc = 0.2; - for _ in 0..30 { - samples_out.push(buf.at(pos as usize)); - pos += pos_inc; - } - - assert_vec_feq!( - samples_out, - vec![ - 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.8, 0.8, 0.8, 0.8, 0.7, 0.7, - 0.7, 0.7, 0.7, 0.6, 0.6, 0.6, 0.6, 0.6, 0.5, 0.5, 0.5, 0.5, 0.5 - ] - ); -} - -#[test] -fn check_cubic_interpolate() { - use crate::helpers::cubic_interpolate; - let data = [1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0]; - - let mut samples_out = vec![]; - let mut pos = 0.0_f32; - let pos_inc = 0.5_f32; - for _ in 0..30 { - let i = pos.floor() as usize; - let f = pos.fract(); - samples_out.push(cubic_interpolate(&data[..], data.len(), i, f)); - pos += pos_inc; - } - assert_vec_feq!( - samples_out, - vec![ - 1.0, - 1.01875, - 0.9, - 0.85, - 0.8, - 0.75, - 0.7, - 0.65, - 0.6, - 0.55, - 0.5, - 0.45, - 0.4, - 0.35000002, - 0.3, - 0.25, - 0.2, - 0.15, - 0.1, - -0.018750004, - 0.0, - 0.49999997, - 1.0, - 1.01875, - 0.9, - 0.85, - 0.8, - 0.75, - 0.7, - 0.65 - ] - ); - - let mut samples_out = vec![]; - let mut pos = 0.0_f32; - let pos_inc = 0.1_f32; - for _ in 0..30 { - let i = pos.floor() as usize; - let f = pos.fract(); - samples_out.push(cubic_interpolate(&data[..], data.len(), i, f)); - pos += pos_inc; - } - assert_vec_feq!( - samples_out, - vec![ - 1.0, 1.03455, 1.0504, 1.05085, 1.0392, 1.01875, 0.99279994, 0.9646499, 0.9375999, - 0.91494995, 0.9, 0.89, 0.87999994, 0.86999995, 0.85999995, 0.84999996, 0.84, 0.83, - 0.82, 0.80999994, 0.8, 0.79, 0.78000003, 0.77000004, 0.76, 0.75, 0.74, 0.73, 0.72, - 0.71000004 - ] - ); -} - -#[test] -fn check_delaybuffer_cubic_interpolation() { - let mut buf = crate::helpers::DelayBuffer::new(); - - buf.feed(0.0); - buf.feed(0.1); - buf.feed(0.2); - buf.feed(0.3); - buf.feed(0.4); - buf.feed(0.5); - buf.feed(0.6); - buf.feed(0.7); - buf.feed(0.8); - buf.feed(0.9); - buf.feed(1.0); - - let mut samples_out = vec![]; - let mut pos = 0.0; - let pos_inc = 0.1; - for _ in 0..30 { - samples_out.push(buf.cubic_interpolate_at_s(pos)); - pos += pos_inc; - } - - assert_vec_feq!( - samples_out, - vec![ - 1.0, 1.03455, 1.0504, 1.05085, 1.0392, 1.01875, 0.99279994, 0.9646499, 0.9375999, - 0.91494995, 0.9, 0.89, 0.87999994, 0.86999995, 0.85999995, 0.84999996, 0.84, 0.83, - 0.82, 0.80999994, 0.8, 0.79, 0.78000003, 0.77000004, 0.76, 0.75, 0.74, 0.73, 0.72, - 0.71000004 - ] - ); - - let mut samples_out = vec![]; - let mut pos = 0.0; - let pos_inc = 0.5; - for _ in 0..30 { - samples_out.push(buf.cubic_interpolate_at_s(pos)); - pos += pos_inc; - } - - assert_vec_feq!( - samples_out, - vec![ - 1.0, - 1.01875, - 0.9, - 0.85, - 0.8, - 0.75, - 0.7, - 0.65, - 0.6, - 0.55, - 0.5, - 0.45, - 0.4, - 0.35000002, - 0.3, - 0.25, - 0.2, - 0.15, - 0.1, - 0.043750003, - 0.0, - -0.00625, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0 - ] - ); -} diff --git a/tests/quant.rs b/tests/quant.rs index 2761e57..a5ee47c 100644 --- a/tests/quant.rs +++ b/tests/quant.rs @@ -6,7 +6,7 @@ mod common; //use common::*; use hexodsp::d_pit; -use hexodsp::dsp::helpers::Quantizer; +use synfx_dsp::Quantizer; #[test] fn check_quant_pos_neg_exact() {