Made sure synfx-dsp-jit is optional

This commit is contained in:
Weird Constructor 2022-08-05 06:50:38 +02:00
commit a0478f64e9
35 changed files with 88 additions and 3723 deletions

View file

@ -18,8 +18,9 @@ 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-jit = { path = "../synfx-dsp-jit", optional = true }
synfx-dsp = "0.5.1"
#synfx-dsp = { git = "https://github.com/WeirdConstructor/synfx-dsp" }
[dev-dependencies]
num-complex = "0.2"

View file

@ -7,6 +7,7 @@ use std::collections::HashMap;
use std::rc::Rc;
use crate::blocklang::*;
#[cfg(feature = "synfx-dsp-jit")]
use synfx_dsp_jit::{ASTNode, JITCompileError};
#[derive(Debug)]
@ -176,6 +177,7 @@ impl BlkASTNode {
#[derive(Debug, Clone)]
pub enum BlkJITCompileError {
UnknownError,
NoSynfxDSPJit,
BadTree(ASTNodeRef),
NoOutputAtIdx(String, usize),
ASTMissingOutputLabel(usize),
@ -186,6 +188,7 @@ pub enum BlkJITCompileError {
TooManyInputs(String, usize),
WrongNumberOfChilds(String, usize, usize),
UnassignedInput(String, usize, String),
#[cfg(feature = "synfx-dsp-jit")]
JITCompileError(JITCompileError),
}
@ -200,6 +203,11 @@ pub struct Block2JITCompiler {
// - make references where IDs go
// - add a use count to each node, so that we know when to make temporary variables
#[cfg(not(feature = "synfx-dsp-jit"))]
enum ASTNode {
NoSynfxDSPJit
}
impl Block2JITCompiler {
pub fn new(lang: Rc<RefCell<BlockLanguage>>) -> Self {
Self { id_node_map: HashMap::new(), idout_var_map: HashMap::new(), lang, tmpvar_counter: 0 }
@ -255,10 +263,8 @@ impl Block2JITCompiler {
"<r>" => {
if let Some((_in, out, first)) = node.first_child() {
let out = if out.len() > 0 { Some(out) } else { None };
let childs = vec![
self.trans2bjit(&first, out)?,
BlkASTNode::new_get(0, "_res_")
];
let childs =
vec![self.trans2bjit(&first, out)?, BlkASTNode::new_get(0, "_res_")];
Ok(BlkASTNode::new_area(childs))
} else {
Err(BlkJITCompileError::BadTree(node.clone()))
@ -385,6 +391,7 @@ impl Block2JITCompiler {
}
}
#[cfg(feature = "synfx-dsp-jit")]
pub fn bjit2jit(&mut self, ast: &BlkASTRef) -> Result<Box<ASTNode>, BlkJITCompileError> {
use synfx_dsp_jit::build::*;
@ -487,16 +494,24 @@ impl Block2JITCompiler {
}
pub fn compile(&mut self, fun: &BlockFun) -> Result<Box<ASTNode>, BlkJITCompileError> {
let tree = fun.generate_tree::<ASTNodeRef>("zero").unwrap();
println!("{}", tree.walk_dump("", "", 0));
#[cfg(feature = "synfx-dsp-jit")]
{
let tree = fun.generate_tree::<ASTNodeRef>("zero").unwrap();
println!("{}", tree.walk_dump("", "", 0));
let blkast = self.trans2bjit(&tree, None)?;
println!("R: {}", blkast.dump(0, None));
let blkast = self.trans2bjit(&tree, None)?;
println!("R: {}", blkast.dump(0, None));
self.bjit2jit(&blkast)
self.bjit2jit(&blkast)
}
#[cfg(not(feature = "synfx-dsp-jit"))]
{
Err(BlkJITCompileError::NoSynfxDSPJit)
}
}
}
#[cfg(feature = "synfx-dsp-jit")]
#[cfg(test)]
mod test {
use super::*;

View file

@ -1808,6 +1808,7 @@ impl BlockCodeView for BlockFun {
}
}
#[cfg(feature = "synfx-dsp-jit")]
#[cfg(test)]
mod test {
use super::*;

View file

@ -5,8 +5,10 @@
use crate::blocklang::{BlockLanguage, BlockType, BlockUserInput};
use std::cell::RefCell;
use std::rc::Rc;
#[cfg(feature = "synfx-dsp-jit")]
use synfx_dsp_jit::DSPNodeTypeLibrary;
#[cfg(feature = "synfx-dsp-jit")]
pub fn setup_hxdsp_block_language(
dsp_lib: Rc<RefCell<DSPNodeTypeLibrary>>,
) -> Rc<RefCell<BlockLanguage>> {

View file

@ -1,272 +0,0 @@
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// 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<const N: usize> {
filters: [Biquad; 4],
buffer: [f32; N],
}
impl<const N: usize> Oversampling<N> {
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
}
}

View file

@ -1,443 +0,0 @@
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
// 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<f64>; 2],
out_dc_block: [DCBlockFilter<f64>; 2],
lfos: [TriSawLFO<f64>; 4],
input_hpf: OnePoleHPF<f64>,
input_lpf: OnePoleLPF<f64>,
pre_delay: DelayBuffer<f64>,
input_apfs: [(AllPass<f64>, f64, f64); 4],
apf1: [(AllPass<f64>, f64, f64); 2],
hpf: [OnePoleHPF<f64>; 2],
lpf: [OnePoleLPF<f64>; 2],
apf2: [(AllPass<f64>, f64, f64); 2],
delay1: [(DelayBuffer<f64>, f64); 2],
delay2: [(DelayBuffer<f64>, 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)
}
}

File diff suppressed because it is too large Load diff

View file

@ -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
@ -540,9 +541,6 @@ mod node_vosc;
#[allow(non_upper_case_globals)]
mod node_code;
pub mod biquad;
pub mod dattorro;
pub mod helpers;
mod satom;
pub mod tracker;
@ -566,7 +564,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;
@ -1599,7 +1597,7 @@ fn rand_node_satisfies_spec(nid: NodeId, sel: RandNodeSelector) -> bool {
}
pub fn get_rand_node_id(count: usize, sel: RandNodeSelector) -> Vec<NodeId> {
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;
@ -1979,7 +1977,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
}
}

View file

@ -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,
};

View file

@ -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};

View file

@ -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};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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<helpers::Comb>,
comb: Box<synfx_dsp::Comb>,
}
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)";

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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,

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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,

View file

@ -90,7 +90,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();