implemente Ad node, still need to test

This commit is contained in:
Weird Constructor 2021-06-15 05:12:53 +02:00
parent 3fbc471ff4
commit 52ce2f26af
6 changed files with 219 additions and 103 deletions

View file

@ -347,6 +347,9 @@ define_exp!{n_declick d_declick 0.0, 50.0}
define_exp!{n_env d_env 0.0, 1000.0}
// Special linear gain factor for the Out node, to be able
// to reach more exact "1.0".
define_lin!{n_ogin d_ogin 0.0, 2.0}
// A note about the input-indicies:
//
@ -412,7 +415,7 @@ macro_rules! node_list {
out => Out UIType::Generic UICategory::IOUtil
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
(2 gain n_gain d_gain r_id f_def stp_d 0.0, 1.0, 1.0)
(2 gain n_ogin d_ogin r_id f_def stp_d 0.0, 1.0, 1.0)
// node_param_idx
// | atom_idx format fun
// | | name constructor| min max
@ -426,12 +429,14 @@ macro_rules! node_list {
[0 sig],
ad => Ad UIType::Generic UICategory::CV
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
(1 atk n_env d_env r_ems f_ms stp_m 0.0, 1.0, 3.0)
(2 dcy n_env d_env r_ems f_ms stp_m 0.0, 1.0, 10.0)
(3 ashp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
(4 dshp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
{5 0 mult setting(0) fa_ad_mult 0 2}
[0 sig],
(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)
(3 dcy n_env d_env r_ems f_ms stp_m 0.0, 1.0, 10.0)
(4 ashp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
(5 dshp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
{6 0 mult setting(0) fa_ad_mult 0 2}
[0 sig]
[1 eoet],
test => Test UIType::Generic UICategory::IOUtil
(0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
{1 0 s setting(0) fa_test_s 0 10},

View file

@ -4,6 +4,7 @@
use crate::nodes::{NodeAudioContext, NodeExecContext};
use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals};
use super::helpers::{Trigger, TrigSignal, sqrt4_to_pow4};
#[macro_export]
macro_rules! fa_ad_mult { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
@ -22,25 +23,33 @@ const AD_STAGES : i8 = 2;
/// A simple amplifier
#[derive(Debug, Clone)]
pub struct Ad {
srate: f64,
phase: f64,
last_value: f32,
stage: i8,
inc: f64,
stage: u8,
samples_ms: f64,
value: f64,
last_time: f32,
trig: Trigger,
trig_sig: TrigSignal,
}
impl Ad {
pub fn new(_nid: &NodeId) -> Self {
Self {
srate: 44100.0,
phase: 0.0,
last_value: 0.0,
stage: -1,
inc: 0.0,
stage: 0,
samples_ms: 44.1,
value: 0.0,
last_time: -1.0,
trig: Trigger::new(),
trig_sig: TrigSignal::new(),
}
}
pub const inp : &'static str =
"Ad inp\nSignal input. If you don't connect this, and set this to 1.0 \
this will act as envelope signal generator. But you can also just \
route a signal directly through this of course.\nRange: (-1..1)\n";
pub const trig : &'static str =
"Ad trig\nTrigger input that starts the attack phase.\nRange: (0..1)\n";
pub const atk : &'static str =
"Ad atk\nAttack time of the envelope. You can extend the maximum \
range of this with the 'mult' setting.\nRange: (0..1)\n";
@ -63,102 +72,49 @@ impl Ad {
you will receive an attenuated signal here. If you set 'inp' to a \
fixed value (for instance 1.0), this will output an envelope signal \
in the range 0.0 to 'inp' (1.0).\nRange: (-1..1)\n";
pub const eoet : &'static str =
"Ad eoet\nEnd of envelope trigger. This output sends a trigger once \
the end of the decay stage has been reached.\nRange: (0..1)";
pub const DESC : &'static str =
r#"Attack-Decay Envelope
This is a simple envelope offering an attack time and decay time with shape parameter.
This is a simple envelope offering an attack time and decay time with a shape parameter.
You can use it as envelope generator to modulate other inputs or process a signal with it directly.
"#;
pub const HELP : &'static str =
r#"Ad - Attack-Decay Envelope
This simple two stage envelope with attack and decay offers shape parameters
for each stage. The attack and decay times can be extended using the 'mult'
setting.
The 'inp' can either be used to process a signal, or set the target output
value of the envelope. In the latter case this node is just a simple
envelope generator, with which you can generate control signals to modulate
other inputs.
With the 'eoet' output you can either trigger other envelopes or via
'FbWr'/'FbRd' retrigger the envelope.
"#;
}
/*
struct {
srate_per_ms: f64,
value : f64 = 0.0;
inc : f64 = 0.0;
stage = 0;
last_time = 0.0;
target : f64 = 1.0;
shape = 0.5;
}
set_sample_rate(srate) { self.srate_per_ms = srate / 1000.0 }
// block start:
let mut shape_src =
match stage {
2 => dcy_shape,
_ => atk_shape,
};
let mut inc_time_src =
match stage {
2 => dcy,
_ => atk,
};
let mut mult : f64 =
if mult == 1 { 10.0 } else if mult == 2 {100.0 } else { 1.0};
// each frame:
if stage == 0 {
if trigger(trig_in) {
value = 0.0;
// transition to stage 1 (attack):
stage = 1;
target = 1.0;
shape_src = atk_shape;
inc_time_src = atk;
last_time = -1.0;
}
}
let cur_time = denorm(inc_time_src);
if last_time != cur_time {
inc =
(target - value)
/ ((cur_time as f64) * mult * srate_per_ms);
}
value += inc;
shape = read(frame, shape_src).clamp(0.0, 1.0);
match stage {
1 => {
if value >= target {
// transition to stage 2 (decay):
stage = 2;
target = 0.0;
shape_src = dcy_shape;
inc_time_src = dcy;
last_time = -1.0;
}
},
2 => {
if value <= target {
stage = 0;
eov_trigger.trigger();
}
},
_ => {},
}
let in_val = inp.read(frame);
out.write(
frame,
in_val
* sqrt4_to_pow4(
value.clamp(0.0, 1.0) as f32, shape));
trig.write(frame, eov_trigger.next());
*/
impl DspNode for Ad {
fn outputs() -> usize { 1 }
fn set_sample_rate(&mut self, _srate: f32) { }
fn reset(&mut self) { }
fn set_sample_rate(&mut self, srate: f32) {
self.samples_ms = srate as f64 / 1000.0;
self.trig_sig.set_sample_rate(srate);
}
fn reset(&mut self) {
self.stage = 0;
self.value = 0.0;
self.inc = 0.0;
self.last_time = -1.0;
self.trig_sig.reset();
self.trig.reset();
}
#[inline]
fn process<T: NodeAudioContext>(
@ -168,15 +124,98 @@ impl DspNode for Ad {
{
use crate::dsp::{out, inp, denorm, denorm_v, inp_dir, at};
let out = out::Ad::sig(outputs);
let inp = inp::Ad::inp(inputs);
let trig = inp::Ad::trig(inputs);
let atk = inp::Ad::atk(inputs);
let dcy = inp::Ad::dcy(inputs);
let atk_shape = inp::Ad::ashp(inputs);
let dcy_shape = inp::Ad::dshp(inputs);
let mult = at::Ad::mult(atoms);
let last_frame = ctx.nframes() - 1;
// block start:
let (mut shape_src, mut inc_time_src, mut target) =
match self.stage {
1 => (atk_shape, atk, 1.0),
2 => (dcy_shape, dcy, 0.0),
_ => (atk_shape, atk, 0.0),
};
let mut mult : f64 =
match mult.i() {
1 => 10.0,
2 => 100.0,
_ => 1.0,
};
for frame in 0..ctx.nframes() {
out.write(frame, 0.0);
// each frame:
let is_triggered =
self.trig.check_trigger(denorm::Ad::trig(trig, frame));
if self.stage == 0 && is_triggered {
// transition to stage 1 (attack):
self.stage = 1;
self.last_time = -1.0;
target = 1.0;
shape_src = atk_shape;
inc_time_src = atk;
}
ctx_vals[0].set(0.0);
let cur_time = denorm::Ad::atk(inc_time_src, frame);
if self.last_time != cur_time {
self.inc =
if cur_time <= 0.0001 {
target - self.value
} else {
(target - self.value)
/ ((cur_time as f64) * mult * self.samples_ms)
};
self.last_time = cur_time;
}
self.value += self.inc;
let shape =
denorm::Ad::ashp(shape_src, frame)
.clamp(0.0, 1.0);
match self.stage {
1 => {
if self.value >= target {
self.stage = 2;
self.last_time = -1.0;
self.value = target;
target = 0.0;
shape_src = dcy_shape;
inc_time_src = dcy;
}
},
2 => {
if self.value <= target {
self.stage = 0;
self.last_time = -1.0;
self.value = target;
target = 0.0;
self.trig_sig.trigger();
}
},
_ => {},
}
let in_val = denorm::Ad::inp(inp, frame);
let out = out::Ad::sig(outputs);
//d// println!("VAL in={}, val={} shp: {}=>{}", in_val, self.value, shape,
//d// sqrt4_to_pow4(1.0, shape));
out.write(
frame,
in_val
* sqrt4_to_pow4(
self.value.clamp(0.0, 1.0) as f32,
shape));
let eoet = out::Ad::eoet(outputs);
eoet.write(frame, self.trig_sig.next());
}
ctx_vals[0].set(self.value as f32);
// ctx_vals[1].set(self.phase / self. + self.stage * );
}
}

View file

@ -34,7 +34,9 @@ impl Out {
pub const mono : &'static str =
"Out mono\nIf set to 'Mono', ch1 will be sent to both output channels.\n(UI only)";
pub const gain : &'static str =
"Out gain\nThe main gain of the synthesizer output, applied to all channels.\nRange: (0..1)";
"Out gain\nThe main gain of the synthesizer output, applied to all channels. \
Please note that this is a linear control, to prevent inaccuracies for 1.0. \
\nRange: (0..1)";
pub const ch1 : &'static str =
"Out ch1\nAudio channel 1 (left)\nRange: (-1..1)";
pub const ch2 : &'static str =

View file

@ -469,7 +469,7 @@ mod tests {
let s = mr.serialize();
assert_eq!(s,
"{\"VERSION\":1,\"atoms\":[[\"out\",0,\"mono\",[\"i\",0]]],\"cells\":[[\"sin\",2,0,0,[-1,-1,-1],[-1,0,-1]],[\"out\",0,1,0,[-1,0,-1],[-1,-1,0]]],\"params\":[[\"out\",0,\"ch1\",0.0],[\"out\",0,\"ch2\",0.0],[\"sin\",0,\"det\",0.0],[\"sin\",1,\"det\",0.0],[\"sin\",2,\"det\",0.0],[\"sin\",0,\"freq\",0.0],[\"sin\",1,\"freq\",0.0],[\"sin\",2,\"freq\",-0.10000000149011612],[\"out\",0,\"gain\",0.7071067690849304]],\"patterns\":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}");
"{\"VERSION\":1,\"atoms\":[[\"out\",0,\"mono\",[\"i\",0]]],\"cells\":[[\"sin\",2,0,0,[-1,-1,-1],[-1,0,-1]],[\"out\",0,1,0,[-1,0,-1],[-1,-1,0]]],\"params\":[[\"out\",0,\"ch1\",0.0],[\"out\",0,\"ch2\",0.0],[\"sin\",0,\"det\",0.0],[\"sin\",1,\"det\",0.0],[\"sin\",2,\"det\",0.0],[\"sin\",0,\"freq\",0.0],[\"sin\",1,\"freq\",0.0],[\"sin\",2,\"freq\",-0.10000000149011612],[\"out\",0,\"gain\",0.5]],\"patterns\":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}");
let mut mr2 = MatrixRepr::deserialize(&s).unwrap();

View file

@ -79,6 +79,35 @@ assertion failed: `(left[{}] == right[{}])`
}
}
#[macro_export]
macro_rules! assert_decimated_slope_feq {
($vec:expr, $decimate:expr, $cmp_vec:expr) => {
let cmp_vec = $cmp_vec;
let mut res : Vec<f32> = vec![];
let mut prev = 0.0;
for s in $vec.iter() {
let delta = *s - prev;
res.push(delta);
prev = *s;
}
let res : Vec<f32> = res.iter().step_by($decimate).copied().collect();
for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() {
if (s - scmp).abs() > 0.0001 {
panic!(r#"
table_left: {:?}
table_right: {:?}
assertion failed: `(left[{}] == right[{}])`
left: `{:?}`,
right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp)
}
}
}
}
#[macro_export]
macro_rules! assert_rmsmima {
($rms:expr, $b:expr) => {

41
tests/node_ad.rs Normal file
View file

@ -0,0 +1,41 @@
mod common;
use common::*;
#[test]
fn check_node_ad_1() {
let (node_conf, mut node_exec) = new_node_engine();
let mut matrix = Matrix::new(node_conf, 3, 3);
let ad = NodeId::Ad(0);
let out = NodeId::Out(0);
matrix.place(0, 0, Cell::empty(ad)
.out(None, None, ad.out("sig")));
matrix.place(0, 1, Cell::empty(out)
.input(out.inp("ch1"), None, None));
matrix.sync().unwrap();
let trig_p = ad.inp_param("trig").unwrap();
matrix.set_param(trig_p, SAtom::param(1.0));
let res = run_for_ms(&mut node_exec, 25.0);
assert_decimated_slope_feq!(res.0, 50, vec![
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
// 44.1 per ms, attack is default 3.0ms (roughly 3 * 50 samples):
0.007558584, 0.007558584, 0.007558584,
// 44.1 per ms, decay is default 10.0ms (=> roughly 9 * 50 samples):
-0.002267599, -0.0022675395, -0.002267599, -0.0022675395,
-0.0022675693, -0.0022675693, -0.0022675842, -0.0022675693,
-0.0022675726,
0.0, 0.0, 0.0, 0.0
]);
matrix.set_param(trig_p, SAtom::param(0.0));
run_for_ms(&mut node_exec, 10.0);
matrix.set_param(trig_p, SAtom::param(1.0));
let res = run_for_ms(&mut node_exec, 25.0);
//d// println!("RES: {:?}", res);
let start = res.0[330];
assert_float_eq!(start, 0.0075585);
let peak = res.0[330 + ((44.1_f64 * 3.0).floor() as usize)];
assert_float_eq!(peak, 1.0);
}