Refactored out SlewValue, fixed TsLFO test, added RndWk test. Documented RndWk.

This commit is contained in:
Weird Constructor 2021-08-13 05:48:20 +02:00
parent 369bc720b9
commit 99eead8402
4 changed files with 309 additions and 47 deletions

View file

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

View file

@ -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(),
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
View 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,
]);
}

View file

@ -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")));