implemente Ad node, still need to test
This commit is contained in:
parent
3fbc471ff4
commit
52ce2f26af
6 changed files with 219 additions and 103 deletions
|
@ -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},
|
||||
|
|
|
@ -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 * );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
41
tests/node_ad.rs
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue