Refactored out SlewValue, fixed TsLFO test, added RndWk test. Documented RndWk.
This commit is contained in:
parent
369bc720b9
commit
99eead8402
4 changed files with 309 additions and 47 deletions
|
@ -705,6 +705,69 @@ impl TriggerSampleClock {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SlewValue<F: Flt> {
|
||||
slew_count: u64,
|
||||
current: F,
|
||||
target: F,
|
||||
inc: F,
|
||||
sr_ms: F,
|
||||
}
|
||||
|
||||
impl<F: Flt> SlewValue<F> {
|
||||
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;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// See README.md and COPYING for details.
|
||||
|
||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||
use crate::dsp::helpers::{Rng, Trigger};
|
||||
use crate::dsp::helpers::{Rng, Trigger, SlewValue};
|
||||
use crate::dsp::{
|
||||
NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals, NodeContext,
|
||||
GraphAtomData, GraphFun,
|
||||
|
@ -12,12 +12,8 @@ use crate::dsp::{
|
|||
/// A triggered random walker
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RndWk {
|
||||
sr_ms: f32,
|
||||
rng: Rng,
|
||||
target: f32,
|
||||
target_inc: f32,
|
||||
slew_count: u64,
|
||||
current: f32,
|
||||
slew_val: SlewValue<f32>,
|
||||
trig: Trigger,
|
||||
}
|
||||
|
||||
|
@ -30,34 +26,59 @@ impl RndWk {
|
|||
|
||||
Self {
|
||||
rng,
|
||||
sr_ms: 44100.0 / 1000.0,
|
||||
target: 0.0,
|
||||
target_inc: 0.0,
|
||||
slew_count: 0,
|
||||
current: 0.0,
|
||||
trig: Trigger::new(),
|
||||
trig: Trigger::new(),
|
||||
slew_val: SlewValue::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const trig : &'static str =
|
||||
"RndWk trig\n\n\nRange: (-1..1)";
|
||||
"RndWk trig\nThis trigger generates a new random number within \
|
||||
the current 'min'/'max' range.\nRange: (-1..1)";
|
||||
pub const step : &'static str =
|
||||
"RndWk step\n\nRange: (-1..1)";
|
||||
"RndWk step\nThis is the maximum possible step size of the \
|
||||
random number drawn upon 'trig'. Setting this to 0.0 will disable \
|
||||
the randomness.\nThe minimum step size can be defined \
|
||||
by the 'offs' parameter.\nRange: (0..1)";
|
||||
pub const offs : &'static str =
|
||||
"RndWk offs\n\nRange: (-1..1)";
|
||||
"RndWk offs\nThe minimum step size and direction that is done on each 'trig'.\
|
||||
Depending on the size of the 'offs' and the 'min'/'max' range, \
|
||||
this might result in the output value being close to the limits \
|
||||
of that range.\nRange: (-1..1)";
|
||||
pub const min : &'static str =
|
||||
"RndWk min\n\nRange: (0..1)";
|
||||
"RndWk min\nThe minimum of the new target value. If a value is drawn \
|
||||
that is outside of this range, it will be reflected back into it.\
|
||||
\nRange: (0..1)";
|
||||
pub const max : &'static str =
|
||||
"RndWk max\n\nRange: (0..1)";
|
||||
"RndWk max\nThe maximum of the new target value. If a value is drawn \
|
||||
that is outside of this range, it will be reflected back into it.\
|
||||
\nRange: (0..1)";
|
||||
pub const slewt : &'static str =
|
||||
"RndWk slewt\n\nRange: (0..1)";
|
||||
"RndWk slewt\nThe slew time, the time it takes to reach the \
|
||||
new target value. This can be used to smooth off rough transitions and \
|
||||
clicky noises.\nRange: (0..1)";
|
||||
pub const sig : &'static str =
|
||||
"RndWk sig\nOscillator output\nRange: (-1..1)\n";
|
||||
pub const DESC : &'static str =
|
||||
r#"Random Walker
|
||||
|
||||
This modulator generates a random number by walking a pre defined maximum random 'step' width. For smoother transitions a slew time is integrated.
|
||||
"#;
|
||||
pub const HELP : &'static str =
|
||||
r#"RndWk - Random Walker
|
||||
|
||||
This modulator generates a random number by walking a pre defined
|
||||
maximum random 'step' width. The newly generated target value will always
|
||||
be folded within the defined 'min'/'max' range. The 'offs' parameter defines a
|
||||
minimal step width each 'trig' has to change the target value.
|
||||
|
||||
For smoother transitions, if you want to modulate an audio signal with this,
|
||||
a slew time ('slewt') is integrated.
|
||||
|
||||
You can disable all randomness by setting 'step' to 0.0.
|
||||
|
||||
Tip: Interesting and smooth results can be achieved if you set 'slewt'
|
||||
to a longer time than the interval in that you trigger 'trig'. It will smooth
|
||||
off the step widths and the overall motion even more.
|
||||
"#;
|
||||
|
||||
}
|
||||
|
@ -66,14 +87,11 @@ impl DspNode for RndWk {
|
|||
fn outputs() -> usize { 1 }
|
||||
|
||||
fn set_sample_rate(&mut self, srate: f32) {
|
||||
self.sr_ms = srate / 1000.0;
|
||||
self.slew_val.set_sample_rate(srate);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.target = 0.0;
|
||||
self.current = 0.0;
|
||||
self.target_inc = 0.0;
|
||||
self.slew_count = 0;
|
||||
self.slew_val.reset();
|
||||
self.trig.reset();
|
||||
}
|
||||
|
||||
|
@ -81,7 +99,7 @@ impl DspNode for RndWk {
|
|||
fn process<T: NodeAudioContext>(
|
||||
&mut self, ctx: &mut T, _ectx: &mut NodeExecContext,
|
||||
_nctx: &NodeContext,
|
||||
atoms: &[SAtom], inputs: &[ProcBuf],
|
||||
_atoms: &[SAtom], inputs: &[ProcBuf],
|
||||
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
||||
{
|
||||
use crate::dsp::{out, inp, denorm, denorm_offs, at};
|
||||
|
@ -106,34 +124,18 @@ impl DspNode for RndWk {
|
|||
let step = denorm::RndWk::step(step, frame).clamp(-1.0, 1.0);
|
||||
let offs = denorm::RndWk::offs(offs, frame).clamp(-1.0, 1.0);
|
||||
|
||||
self.target =
|
||||
self.current
|
||||
let target =
|
||||
self.slew_val.value()
|
||||
+ ((self.rng.next() * 2.0 * step) - step)
|
||||
+ offs;
|
||||
self.target = ((self.target - min) % delta).abs() + min;
|
||||
let target = ((target - min) % delta).abs() + min;
|
||||
|
||||
let slew_time_ms = denorm::RndWk::slewt(slewt, frame);
|
||||
|
||||
if slew_time_ms < 0.01 {
|
||||
self.current = self.target;
|
||||
self.slew_count = 0;
|
||||
|
||||
} else {
|
||||
let slew_samples = slew_time_ms * self.sr_ms;
|
||||
self.slew_count = slew_samples as u64;
|
||||
self.target_inc = (self.target - self.current) / slew_samples;
|
||||
}
|
||||
self.slew_val.set_target(target, slew_time_ms);
|
||||
}
|
||||
|
||||
if self.slew_count > 0 {
|
||||
self.current += self.target_inc;
|
||||
self.slew_count -= 1;
|
||||
} else {
|
||||
self.target_inc = 0.0;
|
||||
self.current = self.target;
|
||||
}
|
||||
|
||||
out.write(frame, self.current);
|
||||
out.write(frame, self.slew_val.next());
|
||||
}
|
||||
|
||||
ctx_vals[0].set(out.read(ctx.nframes() - 1));
|
||||
|
|
197
tests/node_rndwk.rs
Normal file
197
tests/node_rndwk.rs
Normal file
|
@ -0,0 +1,197 @@
|
|||
// 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.
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
#[test]
|
||||
fn check_node_rndwk_def_trig() {
|
||||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let rwk = NodeId::RndWk(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(rwk)
|
||||
.out(None, None, rwk.out("sig")));
|
||||
matrix.place(0, 1, Cell::empty(out)
|
||||
.input(out.inp("ch1"), None, None));
|
||||
matrix.sync().unwrap();
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 40, vec![
|
||||
0.0, // start value
|
||||
// 10ms ramp:
|
||||
0.0049022376, 0.015222744, 0.025543215, 0.035863716, 0.04618426,
|
||||
0.056504805, 0.066825345, 0.07714589, 0.08746643, 0.09778698,
|
||||
0.10810752,
|
||||
// end value:
|
||||
0.11378352, 0.11378352, 0.11378352, 0.11378352, 0.11378352,
|
||||
]);
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 0.0);
|
||||
pset_d_wait(&mut matrix, &mut node_exec, rwk, "slewt", 1.0);
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 15, vec![
|
||||
0.11378352, 0.11378352, // last value
|
||||
0.1436584, 0.19344981, 0.24324122, // 1ms ramp 15 * 3 => ~44.1 samples
|
||||
0.26017055, 0.26017055, // end value
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_node_rndwk_step() {
|
||||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let rwk = NodeId::RndWk(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(rwk)
|
||||
.out(None, None, rwk.out("sig")));
|
||||
matrix.place(0, 1, Cell::empty(out)
|
||||
.input(out.inp("ch1"), None, None));
|
||||
pset_d(&mut matrix, rwk, "step", 1.0);
|
||||
matrix.sync().unwrap();
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 60, vec![
|
||||
0.0, // start value
|
||||
// 10ms ramp:
|
||||
0.050312463, 0.12771615, 0.20512024, 0.28252393, 0.35992712,
|
||||
0.4373303, 0.51473385,
|
||||
// end value
|
||||
// which is 5.0 * 0.11378352
|
||||
// (the first random sample, see previous test)
|
||||
0.56891763, 0.56891763,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_node_rndwk_offs() {
|
||||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let rwk = NodeId::RndWk(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(rwk)
|
||||
.out(None, None, rwk.out("sig")));
|
||||
matrix.place(0, 1, Cell::empty(out)
|
||||
.input(out.inp("ch1"), None, None));
|
||||
pset_d(&mut matrix, rwk, "offs", 0.3);
|
||||
matrix.sync().unwrap();
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 60, vec![
|
||||
0.0, // start value
|
||||
// 10ms ramp:
|
||||
0.03659311, 0.0928901, 0.14918698, 0.20548387,
|
||||
0.26178095, 0.31807873, 0.3743765,
|
||||
// end value
|
||||
// which is 0.11378352 + 0.3
|
||||
// (the first random sample, see previous test)
|
||||
0.41378355,
|
||||
0.41378355,
|
||||
0.41378355,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_node_rndwk_offs_neg() {
|
||||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let rwk = NodeId::RndWk(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(rwk)
|
||||
.out(None, None, rwk.out("sig")));
|
||||
matrix.place(0, 1, Cell::empty(out)
|
||||
.input(out.inp("ch1"), None, None));
|
||||
pset_d(&mut matrix, rwk, "offs", -0.2);
|
||||
matrix.sync().unwrap();
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 60, vec![
|
||||
0.0, // start value
|
||||
// 10ms ramp:
|
||||
0.007624589, 0.019354708, 0.03108479, 0.042814985, 0.05454518,
|
||||
0.06627537, 0.07800557,
|
||||
// end value
|
||||
// which is (0.11378352 - 0.2).abs()
|
||||
0.08621648, 0.08621648,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_node_rndwk_max() {
|
||||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let rwk = NodeId::RndWk(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(rwk)
|
||||
.out(None, None, rwk.out("sig")));
|
||||
matrix.place(0, 1, Cell::empty(out)
|
||||
.input(out.inp("ch1"), None, None));
|
||||
pset_d(&mut matrix, rwk, "step", 1.0); // => first sample is 0.56891763
|
||||
pset_d(&mut matrix, rwk, "max", 0.5);
|
||||
matrix.sync().unwrap();
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 60, vec![
|
||||
0.0, // start value
|
||||
// 10ms ramp:
|
||||
0.006094757, 0.015471312, 0.024847867, 0.03422442, 0.043600976,
|
||||
0.052977532, 0.062354088,
|
||||
// end value
|
||||
// which is 0.5 - 0.56891763
|
||||
0.06891763, 0.06891763,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_node_rndwk_min() {
|
||||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let rwk = NodeId::RndWk(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(rwk)
|
||||
.out(None, None, rwk.out("sig")));
|
||||
matrix.place(0, 1, Cell::empty(out)
|
||||
.input(out.inp("ch1"), None, None));
|
||||
pset_d(&mut matrix, rwk, "step", 1.0); // => first sample is 0.56891763
|
||||
pset_d(&mut matrix, rwk, "max", 1.0);
|
||||
pset_d(&mut matrix, rwk, "min", 0.75); // wraps first sample to 0.93108237
|
||||
matrix.sync().unwrap();
|
||||
|
||||
pset_n(&mut matrix, rwk, "trig", 1.0);
|
||||
run_for_ms(&mut node_exec, 7.0); // wait for trigger...
|
||||
|
||||
let (out_l, _) = run_for_ms(&mut node_exec, 20.0);
|
||||
assert_decimated_feq!(out_l, 60, vec![
|
||||
0.0, // start value
|
||||
// 10ms ramp:
|
||||
0.08234063, 0.20901868, 0.33569613, 0.4623733, 0.5890517,
|
||||
0.71573067, 0.8424096,
|
||||
// end value
|
||||
0.93108237, 0.93108237, 0.93108237, 0.93108237, 0.93108237, 0.93108237,
|
||||
]);
|
||||
}
|
|
@ -10,7 +10,7 @@ fn check_node_tslfo_1() {
|
|||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let tsl = NodeId::TsLfo(0);
|
||||
let tsl = NodeId::TsLFO(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(tsl)
|
||||
.out(None, None, tsl.out("sig")));
|
||||
|
@ -62,7 +62,7 @@ fn check_node_tslfo_trig_slopes() {
|
|||
let (node_conf, mut node_exec) = new_node_engine();
|
||||
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||
|
||||
let tsl = NodeId::TsLfo(0);
|
||||
let tsl = NodeId::TsLFO(0);
|
||||
let out = NodeId::Out(0);
|
||||
matrix.place(0, 0, Cell::empty(tsl)
|
||||
.out(None, None, tsl.out("sig")));
|
||||
|
|
Loading…
Reference in a new issue