Working on the first LFO node
This commit is contained in:
parent
80e9fa0d8e
commit
41441ccb29
4 changed files with 317 additions and 2 deletions
12
src/dsp/dattorro.rs
Normal file
12
src/dsp/dattorro.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
|
|
@ -1653,6 +1653,98 @@ impl VPSOscillator {
|
|||
}
|
||||
}
|
||||
|
||||
// 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: f64,
|
||||
/// The current oscillator phase.
|
||||
phase: f64,
|
||||
/// The point from where the falling edge will be used.
|
||||
rev: f64,
|
||||
/// Whether the LFO is currently rising
|
||||
rising: bool,
|
||||
/// The frequency.
|
||||
freq: f64,
|
||||
/// Precomputed rise/fall rate of the LFO.
|
||||
rise_r: f64,
|
||||
fall_r: f64,
|
||||
}
|
||||
|
||||
impl TriSawLFO {
|
||||
pub fn new() -> Self {
|
||||
let mut this = Self {
|
||||
israte: 1.0 / 44100.0,
|
||||
phase: 0.0,
|
||||
rev: 0.5,
|
||||
rising: true,
|
||||
freq: 1.0,
|
||||
fall_r: 0.0,
|
||||
rise_r: 0.0,
|
||||
};
|
||||
this.recalc();
|
||||
this
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn recalc(&mut self) {
|
||||
self.rev = self.rev.clamp(0.0001, 0.999);
|
||||
self.rise_r = 1.0 / self.rev;
|
||||
self.fall_r = -1.0 / (1.0 - self.rev);
|
||||
}
|
||||
|
||||
pub fn set_sample_rate(&mut self, srate: f32) {
|
||||
self.israte = 1.0 / (srate as f64);
|
||||
self.recalc();
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.phase = 0.0;
|
||||
self.rev = 0.5;
|
||||
self.rising = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(&mut self, freq: f32, rev: f32) {
|
||||
self.freq = freq as f64;
|
||||
self.rev = rev as f64;
|
||||
self.recalc();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_unipolar(&mut self) -> f64 {
|
||||
if self.phase >= 1.0 {
|
||||
self.phase -= 1.0;
|
||||
self.rising = true;
|
||||
}
|
||||
|
||||
if self.phase >= self.rev {
|
||||
self.rising = false;
|
||||
}
|
||||
|
||||
let s =
|
||||
if self.rising {
|
||||
self.phase * self.rise_r
|
||||
} else {
|
||||
self.phase * self.fall_r - self.fall_r
|
||||
};
|
||||
|
||||
self.phase += self.freq * self.israte;
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_bipolar(&mut self) -> f64 {
|
||||
(self.next_unipolar() * 2.0) - 1.0
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fa_distort { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
|
||||
let s =
|
||||
|
|
|
@ -40,6 +40,8 @@ mod node_vosc;
|
|||
mod node_biqfilt;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_comb;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_tslfo;
|
||||
|
||||
pub mod biquad;
|
||||
pub mod tracker;
|
||||
|
@ -97,6 +99,7 @@ use node_bosc::BOsc;
|
|||
use node_vosc::VOsc;
|
||||
use node_biqfilt::BiqFilt;
|
||||
use node_comb::Comb;
|
||||
use node_tslfo::TsLfo;
|
||||
|
||||
pub const MIDI_MAX_FREQ : f32 = 13289.75;
|
||||
|
||||
|
@ -302,7 +305,7 @@ macro_rules! define_exp {
|
|||
macro_rules! define_exp4 {
|
||||
($n_id: ident $d_id: ident $min: expr, $max: expr) => {
|
||||
macro_rules! $n_id { ($x: expr) => {
|
||||
(($x - $min) / ($max - $min)).abs().sqrt().sqrt()
|
||||
(($x - $min) / ($max - $min) as f32).abs().sqrt().sqrt()
|
||||
} }
|
||||
macro_rules! $d_id { ($x: expr) => {
|
||||
{ let x : f32 = $x * $x * $x * $x; $min * (1.0 - x) + $max * x }
|
||||
|
@ -310,6 +313,18 @@ macro_rules! define_exp4 {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! define_exp6 {
|
||||
($n_id: ident $d_id: ident $min: expr, $max: expr) => {
|
||||
macro_rules! $n_id { ($x: expr) => {
|
||||
(($x - $min) / ($max - $min) as f32).abs().powf(1.0 / 6.0)
|
||||
} }
|
||||
macro_rules! $d_id { ($x: expr) => {
|
||||
{ let x : f32 = ($x).powf(6.0); $min * (1.0 - x) + $max * x }
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro_rules! n_pit { ($x: expr) => {
|
||||
((($x as f32).max(0.01) / 440.0).log2() / 10.0)
|
||||
} }
|
||||
|
@ -423,6 +438,62 @@ macro_rules! r_vps { ($x: expr, $coarse: expr) => {
|
|||
}
|
||||
} }
|
||||
|
||||
/// The rounding function for LFO time knobs
|
||||
macro_rules! r_lfot { ($x: expr, $coarse: expr) => {
|
||||
if $coarse {
|
||||
let denv = d_lfot!($x);
|
||||
|
||||
if denv < 10.0 {
|
||||
let hz = 1000.0 / denv;
|
||||
let hz = (hz / 10.0).round() * 10.0;
|
||||
n_lfot!(1000.0 / hz)
|
||||
|
||||
} else if denv < 250.0 {
|
||||
n_lfot!((denv / 5.0).round() * 5.0)
|
||||
|
||||
} else if denv < 1500.0 {
|
||||
n_lfot!((denv / 50.0).round() * 50.0)
|
||||
|
||||
} else if denv < 5000.0 {
|
||||
n_lfot!((denv / 500.0).round() * 500.0)
|
||||
|
||||
} else if denv < 15000.0 {
|
||||
n_lfot!((denv / 1000.0).round() * 1000.0)
|
||||
|
||||
} else {
|
||||
n_lfot!((denv / 5000.0).round() * 5000.0)
|
||||
}
|
||||
} else {
|
||||
let denv = d_lfot!($x);
|
||||
|
||||
let o =
|
||||
if denv < 10.0 {
|
||||
let hz = 1000.0 / denv;
|
||||
let hz = hz.round();
|
||||
n_lfot!(1000.0 / hz)
|
||||
|
||||
} else if denv < 100.0 {
|
||||
n_lfot!(denv.round())
|
||||
|
||||
} else if denv < 1000.0 {
|
||||
n_lfot!((denv / 5.0).round() * 5.0)
|
||||
|
||||
} else if denv < 2500.0 {
|
||||
n_lfot!((denv / 10.0).round() * 10.0)
|
||||
|
||||
} else if denv < 25000.0 {
|
||||
n_lfot!((denv / 100.0).round() * 100.0)
|
||||
|
||||
} else {
|
||||
n_lfot!((denv / 500.0).round() * 500.0)
|
||||
};
|
||||
|
||||
println!("ROUND C {} => {}", d_lfot!($x), d_lfot!(o));
|
||||
|
||||
o
|
||||
}
|
||||
} }
|
||||
|
||||
/// The default steps function:
|
||||
macro_rules! stp_d { () => { (20.0, 100.0) } }
|
||||
/// The UI steps to control parameters with a finer fine control:
|
||||
|
@ -468,6 +539,22 @@ macro_rules! f_ms { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
|||
}
|
||||
} }
|
||||
|
||||
macro_rules! f_lfot { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||
if $denorm_v < 10.0 {
|
||||
write!($formatter, "{:5.1}Hz", 1000.0 / $denorm_v)
|
||||
|
||||
} else if $denorm_v < 500.0 {
|
||||
write!($formatter, "{:4.1}ms", $denorm_v)
|
||||
|
||||
} else if $denorm_v < 5000.0 {
|
||||
write!($formatter, "{:4.0}ms", $denorm_v)
|
||||
|
||||
} else {
|
||||
write!($formatter, "{:5.2}s", $denorm_v / 1000.0)
|
||||
}
|
||||
} }
|
||||
|
||||
|
||||
macro_rules! f_det { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||
{
|
||||
let sign = if $denorm_v < 0.0 { -1.0 } else { 1.0 };
|
||||
|
@ -492,6 +579,7 @@ define_exp!{n_declick d_declick 0.0, 50.0}
|
|||
|
||||
define_exp!{n_env d_env 0.0, 1000.0}
|
||||
|
||||
define_exp6!{n_lfot d_lfot 0.1,300000.0}
|
||||
define_exp!{n_time d_time 0.5, 5000.0}
|
||||
define_exp!{n_ftme d_ftme 0.1, 1000.0}
|
||||
|
||||
|
@ -622,7 +710,7 @@ macro_rules! node_list {
|
|||
fbrd => FbRd UIType::Generic UICategory::IOUtil
|
||||
(0 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
|
||||
[0 sig],
|
||||
ad => Ad UIType::Generic UICategory::CV
|
||||
ad => Ad UIType::Generic UICategory::Mod
|
||||
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
|
||||
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||
(2 atk n_env d_env r_ems f_ms stp_m 0.0, 1.0, 3.0)
|
||||
|
@ -632,6 +720,11 @@ macro_rules! node_list {
|
|||
{6 0 mult setting(0) fa_ad_mult 0 2}
|
||||
[0 sig]
|
||||
[1 eoet],
|
||||
tslfo => TsLfo UIType::Generic UICategory::Mod
|
||||
(0 time n_lfot d_lfot r_lfot f_lfot stp_f 0.0, 1.0, 1000.0)
|
||||
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||
(2 rev n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||
[0 sig],
|
||||
delay => Delay UIType::Generic UICategory::Signal
|
||||
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||
|
|
118
src/dsp/node_tslfo.rs
Normal file
118
src/dsp/node_tslfo.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
// 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.
|
||||
|
||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||
use crate::dsp::{
|
||||
NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals,
|
||||
GraphAtomData, GraphFun, NodeContext,
|
||||
};
|
||||
use super::helpers::{TriSawLFO, Trigger};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TsLfo {
|
||||
lfo: Box<TriSawLFO>,
|
||||
trig: Trigger,
|
||||
}
|
||||
|
||||
impl TsLfo {
|
||||
pub fn new(_nid: &NodeId) -> Self {
|
||||
Self {
|
||||
lfo: Box::new(TriSawLFO::new()),
|
||||
trig: Trigger::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const time : &'static str =
|
||||
"TsLfo time\nThe frequency or period time of the LFO, goes all the \
|
||||
way from 0.1ms up to 30s.\nRange: (0..1)\n";
|
||||
pub const trig : &'static str =
|
||||
"TsLfo trig\nTriggers a phase reset of the LFO.\nRange: (0..1)\n";
|
||||
pub const rev : &'static str =
|
||||
"TsLfo rev\nThe reverse point of the LFO waveform. At 0.5 the LFO \
|
||||
will follow a triangle waveform. At 0.0 or 1.0 the LFO waveform will \
|
||||
be (almost) a (reversed) saw tooth. Node: A perfect sawtooth can not be \
|
||||
achieved with this oscillator, as there will always be a minimal \
|
||||
rise/fall time.\nRange: (0..1)\n";
|
||||
pub const sig : &'static str =
|
||||
"TsLfo sig\nThe LFO output.\nRange: (0..1)";
|
||||
pub const DESC : &'static str =
|
||||
r#"TriSaw LFO
|
||||
|
||||
This simple LFO has a configurable waveform. You can blend between triangular to sawtooth waveforms using the 'rev' parameter.
|
||||
"#;
|
||||
pub const HELP : &'static str =
|
||||
r#"TsLfo - TriSaw LFO
|
||||
|
||||
This simple LFO has a configurable waveform. You can blend between
|
||||
triangular to sawtooth waveforms using the 'rev' parameter.
|
||||
|
||||
Using the 'trig' input you can reset the LFO phase, which allows to use it
|
||||
kind of like an envelope.
|
||||
"#;
|
||||
|
||||
}
|
||||
|
||||
impl DspNode for TsLfo {
|
||||
fn outputs() -> usize { 1 }
|
||||
|
||||
fn set_sample_rate(&mut self, srate: f32) {
|
||||
self.lfo.set_sample_rate(srate);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.lfo.reset();
|
||||
self.trig.reset();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process<T: NodeAudioContext>(
|
||||
&mut self, ctx: &mut T, _ectx: &mut NodeExecContext,
|
||||
_nctx: &NodeContext,
|
||||
atoms: &[SAtom], inputs: &[ProcBuf],
|
||||
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
||||
{
|
||||
use crate::dsp::{out, inp, denorm, at};
|
||||
|
||||
let time = inp::TsLfo::time(inputs);
|
||||
let trig = inp::TsLfo::trig(inputs);
|
||||
let rev = inp::TsLfo::rev(inputs);
|
||||
let out = out::TsLfo::sig(outputs);
|
||||
|
||||
let mut lfo = &mut *self.lfo;
|
||||
|
||||
for frame in 0..ctx.nframes() {
|
||||
if self.trig.check_trigger(denorm::TsLfo::trig(trig, frame)) {
|
||||
lfo.reset();
|
||||
}
|
||||
|
||||
let time_ms = denorm::TsLfo::time(time, frame).clamp(0.1, 300000.0);
|
||||
|
||||
lfo.set(
|
||||
1000.0 / time_ms,
|
||||
denorm::TsLfo::rev(rev, frame));
|
||||
|
||||
out.write(frame, lfo.next_unipolar() as f32);
|
||||
}
|
||||
|
||||
ctx_vals[0].set(out.read(ctx.nframes() - 1));
|
||||
}
|
||||
|
||||
fn graph_fun() -> Option<GraphFun> {
|
||||
let mut lfo = TriSawLFO::new();
|
||||
|
||||
Some(Box::new(move |gd: &dyn GraphAtomData, init: bool, _x: f32, xn: f32| -> f32 {
|
||||
if init {
|
||||
lfo.reset();
|
||||
let time_idx = NodeId::TsLfo(0).inp_param("time").unwrap().inp();
|
||||
let rev_idx = NodeId::TsLfo(0).inp_param("rev").unwrap().inp();
|
||||
|
||||
let time = gd.get_norm(time_idx as u32).sqrt();
|
||||
let rev = gd.get_norm(rev_idx as u32);
|
||||
lfo.set(0.2 * (1.0 - time) + time * 1.0, rev);
|
||||
}
|
||||
|
||||
lfo.next_unipolar() as f32
|
||||
}))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue