Merge branch 'master' of ssh://hentai-kamen.m8geil.de:19022/WeirdConstructor/HexoDSP
This commit is contained in:
commit
8c656a8ce3
12 changed files with 832 additions and 80 deletions
|
@ -23,7 +23,7 @@ hound = "3.4.0"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
microfft = "0.3.1"
|
microfft = "0.3.1"
|
||||||
num-complex = "0.2"
|
num-complex = "0.2"
|
||||||
#jack = "0.6.6"
|
jack = "0.6.6"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
|
@ -108,29 +108,29 @@ impl RandGen {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Copyright 2018 Developers of the Rand project.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
//- splitmix64 (http://xoroshiro.di.unimi.it/splitmix64.c)
|
//- splitmix64 (http://xoroshiro.di.unimi.it/splitmix64.c)
|
||||||
//"""
|
|
||||||
// Written in 2015 by Sebastiano Vigna (vigna@acm.org)
|
|
||||||
//
|
//
|
||||||
// To the extent possible under law, the author has dedicated all copyright
|
/// A splitmix64 random number generator.
|
||||||
// and related and neighboring rights to this software to the public domain
|
///
|
||||||
// worldwide. This software is distributed without any warranty.
|
/// The splitmix algorithm is not suitable for cryptographic purposes, but is
|
||||||
//
|
/// very fast and has a 64 bit state.
|
||||||
// See <http://creativecommons.org/publicdomain/zero/1.0/>.
|
///
|
||||||
//"""
|
/// The algorithm used here is translated from [the `splitmix64.c`
|
||||||
//
|
/// reference source code](http://xoshiro.di.unimi.it/splitmix64.c) by
|
||||||
// Written by Alexander Stocko <as@coder.gg>
|
/// Sebastiano Vigna. For `next_u32`, a more efficient mixing function taken
|
||||||
//
|
/// from [`dsiutils`](http://dsiutils.di.unimi.it/) is used.
|
||||||
// To the extent possible under law, the author has dedicated all copyright
|
|
||||||
// and related and neighboring rights to this software to the public domain
|
|
||||||
// worldwide. This software is distributed without any warranty.
|
|
||||||
//
|
|
||||||
// See <LICENSE or http://creativecommons.org/publicdomain/zero/1.0/>
|
|
||||||
|
|
||||||
/// The `SplitMix64` random number generator.
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct SplitMix64(pub u64);
|
pub struct SplitMix64(pub u64);
|
||||||
|
|
||||||
|
const PHI: u64 = 0x9e3779b97f4a7c15;
|
||||||
|
|
||||||
impl SplitMix64 {
|
impl SplitMix64 {
|
||||||
pub fn new(seed: u64) -> Self { Self(seed) }
|
pub fn new(seed: u64) -> Self { Self(seed) }
|
||||||
pub fn new_from_i64(seed: i64) -> Self {
|
pub fn new_from_i64(seed: i64) -> Self {
|
||||||
|
@ -139,13 +139,11 @@ impl SplitMix64 {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn next_u64(&mut self) -> u64 {
|
pub fn next_u64(&mut self) -> u64 {
|
||||||
use std::num::Wrapping as w;
|
self.0 = self.0.wrapping_add(PHI);
|
||||||
|
let mut z = self.0;
|
||||||
let mut z = w(self.0) + w(0x9E37_79B9_7F4A_7C15_u64);
|
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
|
||||||
self.0 = z.0;
|
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
|
||||||
z = (z ^ (z >> 30)) * w(0xBF58_476D_1CE4_E5B9_u64);
|
z ^ (z >> 31)
|
||||||
z = (z ^ (z >> 27)) * w(0x94D0_49BB_1331_11EB_u64);
|
|
||||||
(z ^ (z >> 31)).0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -455,6 +453,11 @@ impl TriggerPhaseClock {
|
||||||
self.clock_samples = 0;
|
self.clock_samples = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sync(&mut self) {
|
||||||
|
self.clock_phase = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 {
|
pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 {
|
||||||
if self.prev_trigger {
|
if self.prev_trigger {
|
||||||
|
@ -559,10 +562,13 @@ impl DelayBuffer {
|
||||||
self.wr = 0;
|
self.wr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Feed one sample into the delay line and increment the write pointer.
|
||||||
|
/// Please note: For sample accurate feedback you need to retrieve the
|
||||||
|
/// output of the delay line before feeding in a new signal.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn feed(&mut self, input: f32) {
|
pub fn feed(&mut self, input: f32) {
|
||||||
self.wr = (self.wr + 1) % self.data.len();
|
|
||||||
self.data[self.wr] = input;
|
self.data[self.wr] = input;
|
||||||
|
self.wr = (self.wr + 1) % self.data.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -613,6 +619,73 @@ impl DelayBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Default size of the delay buffer: 1 seconds at 8 times 48kHz
|
||||||
|
const DEFAULT_ALLPASS_COMB_SAMPLES : usize = 8 * 48000;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AllPass {
|
||||||
|
delay: DelayBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AllPass {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
self.delay.set_sample_rate(srate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.delay.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn next(&mut self, time: f32, g: f32, v: f32) -> f32 {
|
||||||
|
let s = self.delay.cubic_interpolate_at(time);
|
||||||
|
self.delay.feed(v + s * g);
|
||||||
|
s + -1.0 * g * v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Comb {
|
||||||
|
delay: DelayBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Comb {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
self.delay.set_sample_rate(srate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.delay.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn next_feedback(&mut self, time: f32, g: f32, v: f32) -> f32 {
|
||||||
|
let s = self.delay.cubic_interpolate_at(time);
|
||||||
|
self.delay.feed(v + s * g);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn next_feedforward(&mut self, time: f32, g: f32, v: f32) -> f32 {
|
||||||
|
let s = self.delay.cubic_interpolate_at(time);
|
||||||
|
self.delay.feed(v);
|
||||||
|
v + s * g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// translated from Odin 2 Synthesizer Plugin
|
// translated from Odin 2 Synthesizer Plugin
|
||||||
// Copyright (C) 2020 TheWaveWarden
|
// Copyright (C) 2020 TheWaveWarden
|
||||||
// under GPLv3 or any later
|
// under GPLv3 or any later
|
||||||
|
|
|
@ -20,6 +20,10 @@ mod node_fbwr_fbrd;
|
||||||
mod node_ad;
|
mod node_ad;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
mod node_delay;
|
mod node_delay;
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
mod node_allp;
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
mod node_noise;
|
||||||
|
|
||||||
pub mod tracker;
|
pub mod tracker;
|
||||||
mod satom;
|
mod satom;
|
||||||
|
@ -44,6 +48,7 @@ use crate::fa_sampl_pmode;
|
||||||
use crate::fa_sampl_dir;
|
use crate::fa_sampl_dir;
|
||||||
use crate::fa_ad_mult;
|
use crate::fa_ad_mult;
|
||||||
use crate::fa_delay_mode;
|
use crate::fa_delay_mode;
|
||||||
|
use crate::fa_noise_mode;
|
||||||
|
|
||||||
use node_amp::Amp;
|
use node_amp::Amp;
|
||||||
use node_sin::Sin;
|
use node_sin::Sin;
|
||||||
|
@ -55,6 +60,8 @@ use node_fbwr_fbrd::FbWr;
|
||||||
use node_fbwr_fbrd::FbRd;
|
use node_fbwr_fbrd::FbRd;
|
||||||
use node_ad::Ad;
|
use node_ad::Ad;
|
||||||
use node_delay::Delay;
|
use node_delay::Delay;
|
||||||
|
use node_allp::AllP;
|
||||||
|
use node_noise::Noise;
|
||||||
|
|
||||||
pub const MIDI_MAX_FREQ : f32 = 13289.75;
|
pub const MIDI_MAX_FREQ : f32 = 13289.75;
|
||||||
|
|
||||||
|
@ -311,6 +318,20 @@ macro_rules! r_tms { ($x: expr, $coarse: expr) => {
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
/// The rounding function for milliseconds knobs
|
||||||
|
macro_rules! r_fms { ($x: expr, $coarse: expr) => {
|
||||||
|
if $coarse {
|
||||||
|
if d_ftme!($x) > 1000.0 {
|
||||||
|
n_ftme!((d_ftme!($x) / 100.0).round() * 100.0)
|
||||||
|
} else if d_ftme!($x) > 100.0 {
|
||||||
|
n_ftme!((d_ftme!($x) / 10.0).round() * 10.0)
|
||||||
|
} else {
|
||||||
|
n_ftme!((d_ftme!($x)).round())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n_ftme!((d_ftme!($x) * 10.0).round() / 10.0)
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
|
||||||
/// The default steps function:
|
/// The default steps function:
|
||||||
macro_rules! stp_d { () => { (20.0, 100.0) } }
|
macro_rules! stp_d { () => { (20.0, 100.0) } }
|
||||||
|
@ -371,6 +392,7 @@ define_exp!{n_declick d_declick 0.0, 50.0}
|
||||||
define_exp!{n_env d_env 0.0, 1000.0}
|
define_exp!{n_env d_env 0.0, 1000.0}
|
||||||
|
|
||||||
define_exp!{n_time d_time 0.5, 5000.0}
|
define_exp!{n_time d_time 0.5, 5000.0}
|
||||||
|
define_exp!{n_ftme d_ftme 0.25, 1000.0}
|
||||||
|
|
||||||
// Special linear gain factor for the Out node, to be able
|
// Special linear gain factor for the Out node, to be able
|
||||||
// to reach more exact "1.0".
|
// to reach more exact "1.0".
|
||||||
|
@ -405,7 +427,8 @@ macro_rules! node_list {
|
||||||
[0 sig],
|
[0 sig],
|
||||||
tseq => TSeq UIType::Generic UICategory::CV
|
tseq => TSeq UIType::Generic UICategory::CV
|
||||||
(0 clock n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
(0 clock n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
||||||
{1 0 cmode setting(1) fa_tseq_cmode 0 2}
|
(1 trig n_id n_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
|
{2 0 cmode setting(1) fa_tseq_cmode 0 2}
|
||||||
[0 trk1]
|
[0 trk1]
|
||||||
[1 trk2]
|
[1 trk2]
|
||||||
[2 trk3]
|
[2 trk3]
|
||||||
|
@ -463,17 +486,29 @@ macro_rules! node_list {
|
||||||
[0 sig]
|
[0 sig]
|
||||||
[1 eoet],
|
[1 eoet],
|
||||||
delay => Delay UIType::Generic UICategory::Signal
|
delay => Delay UIType::Generic UICategory::Signal
|
||||||
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 1.0)
|
(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)
|
(1 trig n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
(2 time n_time d_time r_tms f_ms stp_m 0.0, 1.0, 0.5)
|
(2 time n_time d_time r_tms f_ms stp_m 0.0, 1.0, 250.0)
|
||||||
(3 fb n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
(3 fb n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
(4 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
(4 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||||
{5 0 mode setting(0) fa_delay_mode 0 1}
|
{5 0 mode setting(0) fa_delay_mode 0 1}
|
||||||
[0 sig],
|
[0 sig],
|
||||||
|
allp => AllP UIType::Generic UICategory::Signal
|
||||||
|
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
|
(1 time n_ftme d_ftme r_fms f_ms stp_m 0.0, 1.0, 25.0)
|
||||||
|
(2 g n_id d_id r_id f_def stp_d -1.0, 1.0, 0.7)
|
||||||
|
[0 sig],
|
||||||
|
noise => Noise UIType::Generic UICategory::Osc
|
||||||
|
(0 atv n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
|
(1 offs n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
|
{2 0 mode setting(0) fa_noise_mode 0 1}
|
||||||
|
[0 sig],
|
||||||
test => Test UIType::Generic UICategory::IOUtil
|
test => Test UIType::Generic UICategory::IOUtil
|
||||||
(0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
(0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||||
{1 0 p param(0.0) fa_test_s 0 10}
|
{1 0 p param(0.0) fa_test_s 0 10}
|
||||||
[0 sig],
|
{2 1 trig param(0.0) fa_test_s 0 0}
|
||||||
|
[0 sig]
|
||||||
|
[1 tsig],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1028,6 +1063,13 @@ macro_rules! make_node_info_enum {
|
||||||
})+
|
})+
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub mod out_idx {
|
||||||
|
$(pub mod $variant {
|
||||||
|
$(#[inline] pub fn $out() -> usize { $out_idx })*
|
||||||
|
})+
|
||||||
|
}
|
||||||
|
|
||||||
mod ni {
|
mod ni {
|
||||||
$(
|
$(
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
110
src/dsp/node_allp.rs
Normal file
110
src/dsp/node_allp.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
|
||||||
|
// This is a part of HexoDSP. Released under (A)GPLv3 or any later.
|
||||||
|
// See README.md and COPYING for details.
|
||||||
|
|
||||||
|
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||||
|
use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals};
|
||||||
|
use crate::dsp::helpers::AllPass;
|
||||||
|
|
||||||
|
/// A simple amplifier
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AllP {
|
||||||
|
allpass: Box<AllPass>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AllP {
|
||||||
|
pub fn new(_nid: &NodeId) -> Self {
|
||||||
|
Self {
|
||||||
|
allpass: Box::new(AllPass::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const inp : &'static str =
|
||||||
|
"AllP inp\nThe signal input for the allpass filter.\nRange: (-1..1)";
|
||||||
|
pub const g : &'static str =
|
||||||
|
"AllP g\nThe internal factor for the allpass filter.\nRange: (-1..1)";
|
||||||
|
pub const time : &'static str =
|
||||||
|
"AllP time\nThe allpass delay time.\nRange: (0..1)";
|
||||||
|
pub const sig : &'static str =
|
||||||
|
"AllP sig\nThe output of allpass filter.\nRange: (-1..1)";
|
||||||
|
|
||||||
|
pub const DESC : &'static str =
|
||||||
|
r#"Simple Single Allpass Filter
|
||||||
|
|
||||||
|
This is an allpass filter that can be used to build reverbs
|
||||||
|
or anything you might find it useful for.
|
||||||
|
"#;
|
||||||
|
pub const HELP : &'static str =
|
||||||
|
r#"AllP - A Simple Single Allpass Filter
|
||||||
|
|
||||||
|
This is an allpass filter that can be used to build reverbs
|
||||||
|
or anything you might find it useful for.
|
||||||
|
|
||||||
|
Typical arrangements are (Schroeder Reverb):
|
||||||
|
|
||||||
|
t=4.5ms
|
||||||
|
g=0.7 -> Comb
|
||||||
|
AllP -> AllP -> AllP -> -> Comb
|
||||||
|
t=42ms t=13.5ms -> Comb
|
||||||
|
g=0.7 g=0.7 -> Comb
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
Comb -> t=0.48ms
|
||||||
|
Comb -> g=0.7
|
||||||
|
Comb -> AllP -> AllP -> AllP
|
||||||
|
Comb -> t=5ms t=1.68ms
|
||||||
|
g=0.7 g=0.7
|
||||||
|
|
||||||
|
Typical values for the comb filters are in the range g=0.6 to 0.9
|
||||||
|
and time in the range of 30ms to 250ms.
|
||||||
|
|
||||||
|
Feel free to deviate from this and experiment around.
|
||||||
|
|
||||||
|
Building your own reverbs is fun!
|
||||||
|
|
||||||
|
(And don't forget that you can create feedback
|
||||||
|
using the FbWr and FbRd nodes!)
|
||||||
|
"#;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DspNode for AllP {
|
||||||
|
fn outputs() -> usize { 1 }
|
||||||
|
|
||||||
|
fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
self.allpass.set_sample_rate(srate);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.allpass.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn process<T: NodeAudioContext>(
|
||||||
|
&mut self, ctx: &mut T, _ectx: &mut NodeExecContext,
|
||||||
|
_atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf],
|
||||||
|
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
||||||
|
{
|
||||||
|
use crate::dsp::{out, inp, denorm};
|
||||||
|
|
||||||
|
let inp = inp::AllP::inp(inputs);
|
||||||
|
let time = inp::AllP::time(inputs);
|
||||||
|
let g = inp::AllP::g(inputs);
|
||||||
|
let out = out::AllP::sig(outputs);
|
||||||
|
|
||||||
|
let ap = &mut *self.allpass;
|
||||||
|
|
||||||
|
for frame in 0..ctx.nframes() {
|
||||||
|
let v = inp.read(frame);
|
||||||
|
|
||||||
|
out.write(frame,
|
||||||
|
ap.next(
|
||||||
|
denorm::AllP::time(time, frame),
|
||||||
|
denorm::AllP::g(g, frame),
|
||||||
|
v));
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_frame = ctx.nframes() - 1;
|
||||||
|
ctx_vals[0].set(out.read(last_frame));
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,6 @@ macro_rules! fa_delay_mode { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Delay {
|
pub struct Delay {
|
||||||
buffer: Box<DelayBuffer>,
|
buffer: Box<DelayBuffer>,
|
||||||
fb_sample: f32,
|
|
||||||
clock: TriggerSampleClock,
|
clock: TriggerSampleClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +28,6 @@ impl Delay {
|
||||||
pub fn new(_nid: &NodeId) -> Self {
|
pub fn new(_nid: &NodeId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer: Box::new(DelayBuffer::new()),
|
buffer: Box::new(DelayBuffer::new()),
|
||||||
fb_sample: 0.0,
|
|
||||||
clock: TriggerSampleClock::new(),
|
clock: TriggerSampleClock::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +43,7 @@ impl Delay {
|
||||||
likings.\nRange: (0..1)";
|
likings.\nRange: (0..1)";
|
||||||
pub const fb : &'static str =
|
pub const fb : &'static str =
|
||||||
"Delay fb\nThe feedback amount of the delay output to it's input. \
|
"Delay fb\nThe feedback amount of the delay output to it's input. \
|
||||||
\nRange: (0..1)";
|
\nRange: (-1..1)";
|
||||||
pub const mix : &'static str =
|
pub const mix : &'static str =
|
||||||
"Delay mix\nThe dry/wet mix of the delay.\nRange: (0..1)";
|
"Delay mix\nThe dry/wet mix of the delay.\nRange: (0..1)";
|
||||||
pub const mode : &'static str =
|
pub const mode : &'static str =
|
||||||
|
@ -108,43 +106,36 @@ impl DspNode for Delay {
|
||||||
let mix = inp::Delay::mix(inputs);
|
let mix = inp::Delay::mix(inputs);
|
||||||
let out = out::Delay::sig(outputs);
|
let out = out::Delay::sig(outputs);
|
||||||
|
|
||||||
let mut fb_s = self.fb_sample;
|
|
||||||
|
|
||||||
if mode.i() == 0 {
|
if mode.i() == 0 {
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
let dry = inp.read(frame);
|
let dry = inp.read(frame);
|
||||||
buffer.feed(dry + fb_s * denorm::Delay::fb(fb, frame));
|
|
||||||
|
|
||||||
let out_sample =
|
let out_sample =
|
||||||
buffer.cubic_interpolate_at(
|
buffer.cubic_interpolate_at(
|
||||||
denorm::Delay::time(time, frame));
|
denorm::Delay::time(time, frame));
|
||||||
|
|
||||||
|
buffer.feed(dry + out_sample * denorm::Delay::fb(fb, frame));
|
||||||
|
|
||||||
out.write(frame,
|
out.write(frame,
|
||||||
crossfade(dry, out_sample,
|
crossfade(dry, out_sample,
|
||||||
denorm::Delay::mix(mix, frame).clamp(0.0, 1.0)));
|
denorm::Delay::mix(mix, frame).clamp(0.0, 1.0)));
|
||||||
|
|
||||||
fb_s = out_sample;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
let dry = inp.read(frame);
|
let dry = inp.read(frame);
|
||||||
buffer.feed(dry + fb_s * denorm::Delay::fb(fb, frame));
|
|
||||||
|
|
||||||
let clock_samples =
|
let clock_samples =
|
||||||
self.clock.next(denorm::Delay::trig(trig, frame));
|
self.clock.next(denorm::Delay::trig(trig, frame));
|
||||||
|
|
||||||
let out_sample = buffer.at(clock_samples as usize);
|
let out_sample = buffer.at(clock_samples as usize);
|
||||||
|
|
||||||
|
buffer.feed(dry + out_sample * denorm::Delay::fb(fb, frame));
|
||||||
|
|
||||||
out.write(frame,
|
out.write(frame,
|
||||||
crossfade(dry, out_sample,
|
crossfade(dry, out_sample,
|
||||||
denorm::Delay::mix(mix, frame).clamp(0.0, 1.0)));
|
denorm::Delay::mix(mix, frame).clamp(0.0, 1.0)));
|
||||||
|
|
||||||
fb_s = out_sample;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fb_sample = fb_s;
|
|
||||||
|
|
||||||
let last_frame = ctx.nframes() - 1;
|
let last_frame = ctx.nframes() - 1;
|
||||||
ctx_vals[0].set(out.read(last_frame));
|
ctx_vals[0].set(out.read(last_frame));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||||
use crate::dsp::{NodeId, SAtom, ProcBuf, GraphFun, GraphAtomData, DspNode, LedPhaseVals};
|
use crate::dsp::{NodeId, SAtom, ProcBuf, GraphFun, GraphAtomData, DspNode, LedPhaseVals};
|
||||||
|
use crate::dsp::helpers::{TrigSignal};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! fa_test_s { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
|
macro_rules! fa_test_s { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
|
||||||
|
@ -28,26 +29,39 @@ macro_rules! fa_test_s { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
|
||||||
/// A simple amplifier
|
/// A simple amplifier
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Test {
|
pub struct Test {
|
||||||
|
trig_sig: TrigSignal,
|
||||||
|
trigger: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Test {
|
impl Test {
|
||||||
|
pub fn new(_nid: &NodeId) -> Self {
|
||||||
|
Self {
|
||||||
|
trigger: false,
|
||||||
|
trig_sig: TrigSignal::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const f : &'static str = "F Test";
|
||||||
|
pub const p : &'static str = "Test p\nAn unsmoothed parameter for automated tests.";
|
||||||
|
pub const trig: &'static str = "Test trig\nA trigger input, that will create a short pulse on the 'tsig' output.\nRange: (-1..1)";
|
||||||
|
pub const sig : &'static str = "Test sig\nThe output of p as signal";
|
||||||
|
pub const tsig : &'static str = "Test tsig\nA short trigger pulse will be generated when the 'trig' input is triggered.";
|
||||||
|
|
||||||
pub const DESC : &'static str = r#""#;
|
pub const DESC : &'static str = r#""#;
|
||||||
pub const HELP : &'static str = r#""#;
|
pub const HELP : &'static str = r#""#;
|
||||||
|
|
||||||
pub fn new(_nid: &NodeId) -> Self {
|
|
||||||
Self {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const f : &'static str = "F Test";
|
|
||||||
pub const p : &'static str = "Test p\nJust an unsmoothed parameter for tests.";
|
|
||||||
pub const sig : &'static str = "Test sig\nThe output of p as signal";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DspNode for Test {
|
impl DspNode for Test {
|
||||||
fn outputs() -> usize { 1 }
|
fn outputs() -> usize { 2 }
|
||||||
|
|
||||||
fn set_sample_rate(&mut self, _srate: f32) { }
|
fn set_sample_rate(&mut self, srate: f32) {
|
||||||
fn reset(&mut self) { }
|
self.trig_sig.set_sample_rate(srate);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.trig_sig.reset();
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn process<T: NodeAudioContext>(
|
fn process<T: NodeAudioContext>(
|
||||||
|
@ -55,12 +69,36 @@ impl DspNode for Test {
|
||||||
atoms: &[SAtom], _params: &[ProcBuf], _inputs: &[ProcBuf],
|
atoms: &[SAtom], _params: &[ProcBuf], _inputs: &[ProcBuf],
|
||||||
outputs: &mut [ProcBuf], _led: LedPhaseVals)
|
outputs: &mut [ProcBuf], _led: LedPhaseVals)
|
||||||
{
|
{
|
||||||
use crate::dsp::{out, at};
|
use crate::dsp::{out_idx, at};
|
||||||
|
|
||||||
let p = at::Test::p(atoms);
|
let p = at::Test::p(atoms);
|
||||||
let out = out::Test::sig(outputs);
|
let trig = at::Test::trig(atoms);
|
||||||
|
let tsig = out_idx::Test::tsig();
|
||||||
|
|
||||||
|
let (out, tsig) = outputs.split_at_mut(tsig);
|
||||||
|
let out = &mut out[0];
|
||||||
|
let tsig = &mut tsig[0];
|
||||||
|
|
||||||
|
let mut trigger = trig.i();
|
||||||
|
if !self.trigger && trigger > 0 {
|
||||||
|
self.trigger = true;
|
||||||
|
|
||||||
|
} else if !self.trigger && trigger == 0 {
|
||||||
|
self.trigger = false;
|
||||||
|
|
||||||
|
} else if self.trigger {
|
||||||
|
trigger = 0;
|
||||||
|
}
|
||||||
|
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
|
if trigger > 0 {
|
||||||
|
self.trig_sig.trigger();
|
||||||
|
trigger = 0;
|
||||||
|
}
|
||||||
|
|
||||||
out.write(frame, p.f());
|
out.write(frame, p.f());
|
||||||
|
let t = self.trig_sig.next();
|
||||||
|
tsig.write(frame, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// See README.md and COPYING for details.
|
// See README.md and COPYING for details.
|
||||||
|
|
||||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||||
use crate::dsp::helpers::TriggerPhaseClock;
|
use crate::dsp::helpers::{TriggerPhaseClock, Trigger};
|
||||||
use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals};
|
use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals};
|
||||||
use crate::dsp::tracker::TrackerBackend;
|
use crate::dsp::tracker::TrackerBackend;
|
||||||
|
|
||||||
|
@ -22,12 +22,18 @@ macro_rules! fa_tseq_cmode { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||||
} } }
|
} } }
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TSeqTime {
|
||||||
|
clock: TriggerPhaseClock,
|
||||||
|
trigger: Trigger,
|
||||||
|
}
|
||||||
|
|
||||||
/// A tracker based sequencer
|
/// A tracker based sequencer
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TSeq {
|
pub struct TSeq {
|
||||||
backend: Option<Box<TrackerBackend>>,
|
backend: Option<Box<TrackerBackend>>,
|
||||||
clock: TriggerPhaseClock,
|
|
||||||
srate: f64,
|
srate: f64,
|
||||||
|
time: Box<TSeqTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for TSeq {
|
impl Clone for TSeq {
|
||||||
|
@ -39,7 +45,10 @@ impl TSeq {
|
||||||
Self {
|
Self {
|
||||||
backend: None,
|
backend: None,
|
||||||
srate: 48000.0,
|
srate: 48000.0,
|
||||||
|
time: Box::new(TSeqTime {
|
||||||
clock: TriggerPhaseClock::new(),
|
clock: TriggerPhaseClock::new(),
|
||||||
|
trigger: Trigger::new(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +58,8 @@ impl TSeq {
|
||||||
|
|
||||||
pub const clock : &'static str =
|
pub const clock : &'static str =
|
||||||
"TSeq clock\nClock input\nRange: (0..1)\n";
|
"TSeq clock\nClock input\nRange: (0..1)\n";
|
||||||
|
pub const trig : &'static str =
|
||||||
|
"TSeq trig\nSynchronization trigger which restarts the sequence.\nRange: (-1..1)\n";
|
||||||
pub const cmode : &'static str =
|
pub const cmode : &'static str =
|
||||||
"TSeq cmode\n'clock' input signal mode:\n\
|
"TSeq cmode\n'clock' input signal mode:\n\
|
||||||
- RowT: Trigger = advance row\n\
|
- RowT: Trigger = advance row\n\
|
||||||
|
@ -89,12 +100,25 @@ impl TSeq {
|
||||||
pub const HELP : &'static str =
|
pub const HELP : &'static str =
|
||||||
r#"Tracker (based) Sequencer
|
r#"Tracker (based) Sequencer
|
||||||
|
|
||||||
|
This sequencer gets it's speed from the clock source. The 'clock'
|
||||||
|
signal can be interpreted in different modes. But if you want to
|
||||||
|
run multiple sequencers in parallel, you want to synchronize them.
|
||||||
|
For this you can use the 'trig' input, it resets the played row to
|
||||||
|
the beginning of the sequence every time a trigger is received.
|
||||||
|
|
||||||
|
Alternatively you can run the sequencer clock using the phase mode.
|
||||||
|
With that the phase (0..1) signal on the 'clock' input determines the
|
||||||
|
exact play head position in the pattern. With this you just need to
|
||||||
|
synchronize the phase generators for different sequencers.
|
||||||
|
|
||||||
|
For an idea how to chain multiple tracker sequencers, see the next page.
|
||||||
|
|
||||||
This tracker provides 6 columns that each can have one of the following
|
This tracker provides 6 columns that each can have one of the following
|
||||||
types:
|
types:
|
||||||
|
|
||||||
- Note column: for specifying pitches.
|
- Note column: for specifying pitches.
|
||||||
- Step column: for specifying non interpolated CV signals.
|
- Step column: for specifying non interpolated CV signals.
|
||||||
- Value column: for specifying linearily interpolated CV signals.
|
- Value column: for specifying linearly interpolated CV signals.
|
||||||
- Gate column: for specifying gates, with probability and ratcheting.
|
- Gate column: for specifying gates, with probability and ratcheting.
|
||||||
|
|
||||||
Step, value and gate cells can be set to 4096 (0xFFF) different values
|
Step, value and gate cells can be set to 4096 (0xFFF) different values
|
||||||
|
@ -102,6 +126,10 @@ or contain nothing at all. For step and value columns these values
|
||||||
are mapped to the 0.0-1.0 CV signal range, with 0xFFF being 1.0
|
are mapped to the 0.0-1.0 CV signal range, with 0xFFF being 1.0
|
||||||
and 0x000 being 0.0.
|
and 0x000 being 0.0.
|
||||||
|
|
||||||
|
On the next page you can read about the gate cells and the gate outputs.
|
||||||
|
---page---
|
||||||
|
Gate Input and Output
|
||||||
|
|
||||||
The gate cells are differently coded:
|
The gate cells are differently coded:
|
||||||
|
|
||||||
- 0x00F: The least significant nibble controls the gate length.
|
- 0x00F: The least significant nibble controls the gate length.
|
||||||
|
@ -109,7 +137,7 @@ The gate cells are differently coded:
|
||||||
- 0x0F0: The second nibble controls ratcheting, with 0x0F0 being one
|
- 0x0F0: The second nibble controls ratcheting, with 0x0F0 being one
|
||||||
gate per row, and 0x000 being 16 gates per row.
|
gate per row, and 0x000 being 16 gates per row.
|
||||||
- 0xF00: The most significant nibble controls probability of the
|
- 0xF00: The most significant nibble controls probability of the
|
||||||
whole gate cell. With 0xF00 meaing the gate will always be
|
whole gate cell. With 0xF00 meaning the gate will always be
|
||||||
triggered, and 0x000 means that the gate is only triggered with
|
triggered, and 0x000 means that the gate is only triggered with
|
||||||
6% probability. 50% is 0x070.
|
6% probability. 50% is 0x070.
|
||||||
|
|
||||||
|
@ -127,6 +155,12 @@ column type:
|
||||||
- Value gat1-gat6: Outputs a 1.0 value for the duration of the last row.
|
- Value gat1-gat6: Outputs a 1.0 value for the duration of the last row.
|
||||||
You can use this to trigger other things once the
|
You can use this to trigger other things once the
|
||||||
sequence has been played.
|
sequence has been played.
|
||||||
|
|
||||||
|
Tip:
|
||||||
|
If you want to use the end of a tracker sequence as trigger for
|
||||||
|
something else, eg. switching to a different 'tseq' and restart
|
||||||
|
it using it's 'trig' input, you will need to use the gate output
|
||||||
|
of a value column and invert it.
|
||||||
"#;
|
"#;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +173,8 @@ impl DspNode for TSeq {
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.backend = None;
|
self.backend = None;
|
||||||
self.clock.reset();
|
self.time.clock.reset();
|
||||||
|
self.time.trigger.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -148,8 +183,9 @@ impl DspNode for TSeq {
|
||||||
atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf],
|
atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf],
|
||||||
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
||||||
{
|
{
|
||||||
use crate::dsp::{out, inp, at};
|
use crate::dsp::{out, inp, at, denorm};
|
||||||
let clock = inp::TSeq::clock(inputs);
|
let clock = inp::TSeq::clock(inputs);
|
||||||
|
let trig = inp::TSeq::trig(inputs);
|
||||||
let cmode = at::TSeq::cmode(atoms);
|
let cmode = at::TSeq::cmode(atoms);
|
||||||
|
|
||||||
let backend =
|
let backend =
|
||||||
|
@ -163,13 +199,21 @@ impl DspNode for TSeq {
|
||||||
[0.0; MAX_BLOCK_SIZE];
|
[0.0; MAX_BLOCK_SIZE];
|
||||||
|
|
||||||
let cmode = cmode.i();
|
let cmode = cmode.i();
|
||||||
let plen = backend.pattern_len() as f64;
|
let plen = backend.pattern_len().max(1) as f64;
|
||||||
|
|
||||||
|
let time = &mut self.time;
|
||||||
|
|
||||||
for frame in 0..ctx.nframes() {
|
for frame in 0..ctx.nframes() {
|
||||||
|
if time.trigger.check_trigger(
|
||||||
|
denorm::TSeq::trig(trig, frame))
|
||||||
|
{
|
||||||
|
time.clock.sync();
|
||||||
|
}
|
||||||
|
|
||||||
let phase =
|
let phase =
|
||||||
match cmode {
|
match cmode {
|
||||||
0 => self.clock.next_phase(plen, clock.read(frame)) / plen,
|
0 => time.clock.next_phase(plen, clock.read(frame)) / plen,
|
||||||
1 => self.clock.next_phase(1.0, clock.read(frame)),
|
1 => time.clock.next_phase(1.0, clock.read(frame)),
|
||||||
2 | _ => (clock.read(frame).abs() as f64).fract(),
|
2 | _ => (clock.read(frame).abs() as f64).fract(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,6 @@ impl SampleLibrary {
|
||||||
/// Returns an SAtom reference that you can clone and send directly
|
/// Returns an SAtom reference that you can clone and send directly
|
||||||
/// to the sampling node of your choice.
|
/// to the sampling node of your choice.
|
||||||
///
|
///
|
||||||
/// The maximum length of the sample is `44100 * 10` samples, which
|
|
||||||
/// is the equivalent of roughly 1.7 MB.
|
|
||||||
///
|
|
||||||
/// Keep in mind that blocking on I/O in the UI might not be desireable.
|
/// Keep in mind that blocking on I/O in the UI might not be desireable.
|
||||||
pub fn load<'a>(&'a mut self, path: &str) -> Result<&'a SAtom, SampleLoadError> {
|
pub fn load<'a>(&'a mut self, path: &str) -> Result<&'a SAtom, SampleLoadError> {
|
||||||
if self.loaded_samples.get(path).is_some() {
|
if self.loaded_samples.get(path).is_some() {
|
||||||
|
|
|
@ -747,10 +747,8 @@ fn check_matrix_tseq() {
|
||||||
.input(out.inp("ch1"), None, None));
|
.input(out.inp("ch1"), None, None));
|
||||||
matrix.sync().unwrap();
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
let freq_param = sin.inp_param("freq").unwrap();
|
pset_n(&mut matrix, sin, "freq", -0.978);
|
||||||
matrix.set_param(freq_param, SAtom::param(-0.978));
|
pset_s(&mut matrix, tsq, "cmode", 1);
|
||||||
let cmode_param = tsq.inp_param("cmode").unwrap();
|
|
||||||
matrix.set_param(cmode_param, SAtom::setting(1));
|
|
||||||
|
|
||||||
let pat = matrix.get_pattern_data(0).unwrap();
|
let pat = matrix.get_pattern_data(0).unwrap();
|
||||||
{
|
{
|
||||||
|
@ -782,13 +780,13 @@ fn check_matrix_tseq() {
|
||||||
assert_float_eq!(samples[9], 0.42228);
|
assert_float_eq!(samples[9], 0.42228);
|
||||||
|
|
||||||
// switch to row trigger:
|
// switch to row trigger:
|
||||||
matrix.set_param(cmode_param, SAtom::setting(0));
|
pset_s(&mut matrix, tsq, "cmode", 0);
|
||||||
let samples = run_and_undersample(&mut node_exec, 2000.0, 5);
|
let samples = run_and_undersample(&mut node_exec, 2000.0, 5);
|
||||||
|
|
||||||
assert_vec_feq!(samples, vec![0.70411, 0.90413, 0.99306, 0.97972, 0.966387]);
|
assert_vec_feq!(samples, vec![0.70411, 0.90413, 0.99306, 0.97972, 0.966387]);
|
||||||
|
|
||||||
// set to phase mode:
|
// set to phase mode:
|
||||||
matrix.set_param(cmode_param, SAtom::setting(2));
|
pset_s(&mut matrix, tsq, "cmode", 2);
|
||||||
let samples = run_and_undersample(&mut node_exec, 1000.0, 5);
|
let samples = run_and_undersample(&mut node_exec, 1000.0, 5);
|
||||||
|
|
||||||
assert_float_eq!(samples[0], 0.2491);
|
assert_float_eq!(samples[0], 0.2491);
|
||||||
|
@ -798,6 +796,73 @@ fn check_matrix_tseq() {
|
||||||
assert_float_eq!(samples[4], 0.8104);
|
assert_float_eq!(samples[4], 0.8104);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_matrix_tseq_trig() {
|
||||||
|
use hexodsp::dsp::tracker::UIPatternModel;
|
||||||
|
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 3, 3);
|
||||||
|
|
||||||
|
let sin = NodeId::Sin(0);
|
||||||
|
let tsq = NodeId::TSeq(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(0, 0, Cell::empty(sin)
|
||||||
|
.out(None, None, sin.out("sig")));
|
||||||
|
matrix.place(0, 1, Cell::empty(tsq)
|
||||||
|
.input(tsq.inp("clock"), None, None)
|
||||||
|
.out(None, None, tsq.out("trk1")));
|
||||||
|
matrix.place(0, 2, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None));
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
pset_n(&mut matrix, sin, "freq", -0.978);
|
||||||
|
pset_s(&mut matrix, tsq, "cmode", 1);
|
||||||
|
|
||||||
|
let pat = matrix.get_pattern_data(0).unwrap();
|
||||||
|
{
|
||||||
|
let mut pr = pat.borrow_mut();
|
||||||
|
pr.set_rows(16);
|
||||||
|
pr.set_cell_value(0, 0, 0xFFF);
|
||||||
|
pr.set_cell_value(15, 0, 0x000);
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..10 {
|
||||||
|
matrix.check_pattern_data(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We let the clock mode tune in:
|
||||||
|
run_and_undersample(&mut node_exec, 10000.0, 1);
|
||||||
|
|
||||||
|
// Take some real samples:
|
||||||
|
let samples = run_and_undersample(&mut node_exec, 2000.0, 10);
|
||||||
|
|
||||||
|
assert_float_eq!(samples[0], 0.3157);
|
||||||
|
assert_float_eq!(samples[1], 0.209);
|
||||||
|
assert_float_eq!(samples[2], 0.1024);
|
||||||
|
assert_float_eq!(samples[3], 0.0648);
|
||||||
|
assert_float_eq!(samples[4], 0.95566);
|
||||||
|
assert_float_eq!(samples[5], 0.84899);
|
||||||
|
assert_float_eq!(samples[6], 0.74231);
|
||||||
|
assert_float_eq!(samples[7], 0.6356);
|
||||||
|
assert_float_eq!(samples[8], 0.5289);
|
||||||
|
assert_float_eq!(samples[9], 0.42228);
|
||||||
|
|
||||||
|
pset_n(&mut matrix, tsq, "trig", 1.0);
|
||||||
|
|
||||||
|
// Take some real samples:
|
||||||
|
let samples = run_and_undersample(&mut node_exec, 2000.0, 10);
|
||||||
|
|
||||||
|
assert_float_eq!(samples[0], 0.3157);
|
||||||
|
// trigger hits:
|
||||||
|
assert_float_eq!(samples[1], 0.9639);
|
||||||
|
assert_float_eq!(samples[2], 0.8572);
|
||||||
|
assert_float_eq!(samples[3], 0.7506);
|
||||||
|
assert_float_eq!(samples[4], 0.6439);
|
||||||
|
assert_float_eq!(samples[5], 0.5372);
|
||||||
|
assert_float_eq!(samples[6], 0.4305);
|
||||||
|
assert_float_eq!(samples[7], 0.3239);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_matrix_tseq_gate() {
|
fn check_matrix_tseq_gate() {
|
||||||
use hexodsp::dsp::tracker::UIPatternModel;
|
use hexodsp::dsp::tracker::UIPatternModel;
|
||||||
|
|
|
@ -194,6 +194,27 @@ assertion failed: `(left[{}] == right[{}])`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn collect_signal_changes(inp: &[f32], thres: i64) -> Vec<(usize, i64)> {
|
||||||
|
let mut idxs = vec![];
|
||||||
|
let mut last_sig = 0.0;
|
||||||
|
for i in 0..inp.len() {
|
||||||
|
if (inp[i] - last_sig).abs() > 0.1 {
|
||||||
|
idxs.push((i, (inp[i] * 100.0).floor() as i64));
|
||||||
|
last_sig = inp[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut idxs_big = vec![];
|
||||||
|
for v in idxs.iter() {
|
||||||
|
if v.1.abs() > thres {
|
||||||
|
idxs_big.push(*v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idxs_big;
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_rmsmima {
|
macro_rules! assert_rmsmima {
|
||||||
($rms:expr, $b:expr) => {
|
($rms:expr, $b:expr) => {
|
||||||
|
@ -209,6 +230,12 @@ macro_rules! assert_minmax_of_rms {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn pset_s(matrix: &mut Matrix, nid: NodeId, parm: &str, set: i64) {
|
||||||
|
let p = nid.inp_param(parm).unwrap();
|
||||||
|
matrix.set_param(p, SAtom::setting(set));
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn pset_n(matrix: &mut Matrix, nid: NodeId, parm: &str, v_norm: f32) {
|
pub fn pset_n(matrix: &mut Matrix, nid: NodeId, parm: &str, v_norm: f32) {
|
||||||
let p = nid.inp_param(parm).unwrap();
|
let p = nid.inp_param(parm).unwrap();
|
||||||
|
|
60
tests/node_allp.rs
Normal file
60
tests/node_allp.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_allp() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let test = NodeId::Test(0);
|
||||||
|
let ap = NodeId::AllP(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(0, 0, Cell::empty(test)
|
||||||
|
.out(None, None, test.out("tsig")));
|
||||||
|
matrix.place(0, 1, Cell::empty(ap)
|
||||||
|
.input(ap.inp("inp"), None, None)
|
||||||
|
.out(None, None, ap.out("sig")));
|
||||||
|
matrix.place(0, 2, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
matrix.place(1, 0, Cell::empty(test)
|
||||||
|
.out(None, None, test.out("tsig")));
|
||||||
|
matrix.place(1, 1, Cell::empty(out)
|
||||||
|
.input(out.inp("ch2"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
pset_d(&mut matrix, ap, "time", 3.0);
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
pset_s(&mut matrix, test, "trig", 1);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 20.0);
|
||||||
|
|
||||||
|
// the original signal on ch2: 2ms trigger up:
|
||||||
|
let mut v = vec![1.0; (2.0 * 44.1_f32).ceil() as usize];
|
||||||
|
v.append(&mut vec![0.0; (18.0 * 44.1_f32).ceil() as usize]);
|
||||||
|
assert_vec_feq!(res.1, v);
|
||||||
|
|
||||||
|
// now signal on ch1 from the allpass:
|
||||||
|
// starts with original signal * -0.7
|
||||||
|
let mut v = vec![-0.7; (2.0 * 44.1_f32).ceil() as usize];
|
||||||
|
// silence for 1ms, which is the internal delay of the allpass
|
||||||
|
v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 3]);
|
||||||
|
|
||||||
|
// allpass feedback of the original signal for 2ms:
|
||||||
|
// XXX: the smearing before and after the allpass is due to the
|
||||||
|
// cubic interpolation!
|
||||||
|
v.append(&mut vec![-0.03150302, 0.25802, 1.0735]);
|
||||||
|
v.append(&mut vec![1.0; (2.0 * 44.1_f32).ceil() as usize - 3]);
|
||||||
|
v.append(&mut vec![1.0315, 0.7419, -0.0735]);
|
||||||
|
// 1ms allpass silence like before:
|
||||||
|
v.append(&mut vec![0.0; (1.0 * 44.1_f32).floor() as usize - 6]);
|
||||||
|
|
||||||
|
// 2ms the previous 1.0 * 0.7 fed back into the filter,
|
||||||
|
// including even more smearing due to cubic interpolation:
|
||||||
|
v.append(&mut vec![0.0006, -0.0120, 0.0106, 0.3444, 0.7801, 0.6962]);
|
||||||
|
v.append(&mut vec![0.7; (2.0 * 44.1_f32).floor() as usize - 5]);
|
||||||
|
v.append(&mut vec![0.6993, 0.712, 0.6893, 0.3555, -0.0801, 0.0037]);
|
||||||
|
|
||||||
|
//d// println!("res={:?}", res.1);
|
||||||
|
assert_vec_feq!(res.0, v);
|
||||||
|
}
|
305
tests/node_delay.rs
Normal file
305
tests/node_delay.rs
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_1() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let ad = NodeId::Ad(0);
|
||||||
|
let sin = NodeId::Sin(0);
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(0, 0, Cell::empty(sin)
|
||||||
|
.out(None, None, sin.out("sig")));
|
||||||
|
matrix.place(0, 1, Cell::empty(ad)
|
||||||
|
.input(ad.inp("inp"), None, None)
|
||||||
|
.out(None, None, ad.out("sig")));
|
||||||
|
matrix.place(0, 2, Cell::empty(dly)
|
||||||
|
.input(dly.inp("inp"), None, None)
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(0, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
pset_d(&mut matrix, ad, "atk", 50.0);
|
||||||
|
pset_d(&mut matrix, ad, "dcy", 50.0);
|
||||||
|
pset_n(&mut matrix, ad, "trig", 1.0);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 500.0);
|
||||||
|
// 441 decimation => 10ms resolution
|
||||||
|
assert_decimated_feq!(res.0, 441, vec![
|
||||||
|
// 10ms smoothing time
|
||||||
|
0.0,
|
||||||
|
// burst of sine for 100ms:
|
||||||
|
0.018363932, -0.124816686, 0.21992423, -0.19471036, 0.00002711302,
|
||||||
|
0.27546832, -0.35064548, 0.25555965, -0.0991776, 0.000008648983,
|
||||||
|
// 150ms silence:
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
// delayed burst of sine for 100ms:
|
||||||
|
0.015279313, -0.119179465, 0.22757527, -0.22698581, 0.05398392,
|
||||||
|
0.22569486, -0.3332433, 0.26348564, -0.11514694, 0.008539479,
|
||||||
|
// silence afterwards:
|
||||||
|
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_2() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(0, 2, Cell::empty(dly)
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(0, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
pset_d(&mut matrix, dly, "time", 31.0);
|
||||||
|
pset_d(&mut matrix, dly, "inp", 1.0);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 150.0);
|
||||||
|
// 441 decimation => 10ms resolution
|
||||||
|
assert_decimated_feq!(res.0, 441, vec![
|
||||||
|
// 10ms smoothing time for "inp"
|
||||||
|
0.001133,
|
||||||
|
// 30ms delaytime just mixing the 0.5:
|
||||||
|
0.5, 0.5, 0.5,
|
||||||
|
// the delayed smoothing ramp (10ms):
|
||||||
|
0.9513,
|
||||||
|
// the delay + input signal:
|
||||||
|
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_time_mod() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let sin = NodeId::Sin(0);
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(1, 1, Cell::empty(sin)
|
||||||
|
.out(None, None, sin.out("sig")));
|
||||||
|
matrix.place(1, 2, Cell::empty(dly)
|
||||||
|
.input(dly.inp("inp"), None, dly.inp("time"))
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(1, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
pset_n(&mut matrix, dly, "mix", 1.0);
|
||||||
|
pset_d(&mut matrix, dly, "time", 100.0);
|
||||||
|
|
||||||
|
// skip delay time:
|
||||||
|
run_for_ms(&mut node_exec, 100.0);
|
||||||
|
|
||||||
|
let fft = run_and_get_fft4096_now(&mut node_exec, 600);
|
||||||
|
assert_eq!(fft[0], (431, 614));
|
||||||
|
assert_eq!(fft[1], (441, 1012));
|
||||||
|
|
||||||
|
let sin2 = NodeId::Sin(1);
|
||||||
|
matrix.place(0, 3, Cell::empty(sin2)
|
||||||
|
.out(sin2.out("sig"), None, None));
|
||||||
|
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
pset_d(&mut matrix, sin2, "freq", 0.5);
|
||||||
|
|
||||||
|
// let everything settle down and the delay buffer fill with stuff:
|
||||||
|
run_for_ms(&mut node_exec, 5000.0);
|
||||||
|
|
||||||
|
// skip some time to let everything settle:
|
||||||
|
run_for_ms(&mut node_exec, 670.0);
|
||||||
|
|
||||||
|
let fft = run_and_get_fft4096_now(&mut node_exec, 110);
|
||||||
|
// Expect a sine sweep over a
|
||||||
|
// range of low frequencies:
|
||||||
|
assert_eq!(fft[0], (108, 111));
|
||||||
|
assert_eq!(fft[5], (312, 110));
|
||||||
|
assert_eq!(fft[10], (700, 110));
|
||||||
|
|
||||||
|
// Sweep upwards:
|
||||||
|
run_for_ms(&mut node_exec, 300.0);
|
||||||
|
let fft = run_and_get_fft4096_now(&mut node_exec, 122);
|
||||||
|
assert_eq!(fft[0], (2509, 123));
|
||||||
|
assert_eq!(fft[8], (2821, 123));
|
||||||
|
|
||||||
|
// Sweep at mostly highest point:
|
||||||
|
run_for_ms(&mut node_exec, 700.0);
|
||||||
|
let fft = run_and_get_fft4096_now(&mut node_exec, 300);
|
||||||
|
assert_eq!(fft[0], (6417, 309));
|
||||||
|
assert_eq!(fft[4], (6471, 407));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_trig() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let test = NodeId::Test(0);
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(1, 1, Cell::empty(test)
|
||||||
|
.out(None, None, test.out("tsig")));
|
||||||
|
matrix.place(0, 3, Cell::empty(test)
|
||||||
|
.out(test.out("sig"), None, None));
|
||||||
|
matrix.place(1, 2, Cell::empty(dly)
|
||||||
|
.input(dly.inp("inp"), None, dly.inp("trig"))
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(1, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
pset_n(&mut matrix, dly, "mix", 1.0);
|
||||||
|
pset_n(&mut matrix, dly, "mode", 1.0);
|
||||||
|
pset_d(&mut matrix, dly, "time", 5.0);
|
||||||
|
|
||||||
|
// Trigger the delay 2 times, with an interval of 20ms:
|
||||||
|
pset_n(&mut matrix, test, "p", 1.0);
|
||||||
|
run_for_ms(&mut node_exec, 10.0);
|
||||||
|
pset_n(&mut matrix, test, "p", 0.0);
|
||||||
|
run_for_ms(&mut node_exec, 10.0);
|
||||||
|
pset_n(&mut matrix, test, "p", 1.0);
|
||||||
|
run_for_ms(&mut node_exec, 10.0);
|
||||||
|
pset_n(&mut matrix, test, "p", 0.0);
|
||||||
|
run_for_ms(&mut node_exec, 10.0);
|
||||||
|
|
||||||
|
// Now the delay should have a 20ms delay time.
|
||||||
|
|
||||||
|
// Emit the trigger signal:
|
||||||
|
pset_n(&mut matrix, test, "trig", 1.0);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 30.0);
|
||||||
|
|
||||||
|
let mut idx_first_non_zero = 99999;
|
||||||
|
for i in 0..res.0.len() {
|
||||||
|
if res.0[i] > 0.0 {
|
||||||
|
idx_first_non_zero = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect the signal to be delayed by 20ms:
|
||||||
|
assert_eq!(idx_first_non_zero, (44100 * 20) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_fb() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let test = NodeId::Test(0);
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(1, 1, Cell::empty(test)
|
||||||
|
.out(None, None, test.out("tsig")));
|
||||||
|
matrix.place(1, 2, Cell::empty(dly)
|
||||||
|
.input(dly.inp("inp"), None, None)
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(1, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
|
||||||
|
pset_n(&mut matrix, dly, "mix", 1.0);
|
||||||
|
pset_d(&mut matrix, dly, "time", 5.0);
|
||||||
|
pset_n(&mut matrix, dly, "fb", 0.5);
|
||||||
|
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
// Emit the trigger signal:
|
||||||
|
pset_n(&mut matrix, test, "trig", 1.0);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 100.0);
|
||||||
|
|
||||||
|
let idxs_big = collect_signal_changes(&res.0[..], 50);
|
||||||
|
|
||||||
|
// We expect the signal to be delayed by 20ms:
|
||||||
|
assert_eq!(idxs_big, vec![(220, 106), (440, 53)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_fb_neg() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let test = NodeId::Test(0);
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(1, 1, Cell::empty(test)
|
||||||
|
.out(None, None, test.out("tsig")));
|
||||||
|
matrix.place(1, 2, Cell::empty(dly)
|
||||||
|
.input(dly.inp("inp"), None, None)
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(1, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
|
||||||
|
pset_n(&mut matrix, dly, "mix", 1.0);
|
||||||
|
pset_d(&mut matrix, dly, "time", 10.0);
|
||||||
|
pset_n(&mut matrix, dly, "fb", -1.0);
|
||||||
|
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
// Emit the trigger signal:
|
||||||
|
pset_n(&mut matrix, test, "trig", 1.0);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 40.0);
|
||||||
|
|
||||||
|
let idxs_big = collect_signal_changes(&res.0[..], 70);
|
||||||
|
|
||||||
|
assert_eq!(idxs_big, vec![(441, 100), (882, -100), (1323, 100)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_node_delay_fb_pos() {
|
||||||
|
let (node_conf, mut node_exec) = new_node_engine();
|
||||||
|
let mut matrix = Matrix::new(node_conf, 4, 4);
|
||||||
|
|
||||||
|
let test = NodeId::Test(0);
|
||||||
|
let dly = NodeId::Delay(0);
|
||||||
|
let out = NodeId::Out(0);
|
||||||
|
matrix.place(1, 1, Cell::empty(test)
|
||||||
|
.out(None, None, test.out("tsig")));
|
||||||
|
matrix.place(1, 2, Cell::empty(dly)
|
||||||
|
.input(dly.inp("inp"), None, None)
|
||||||
|
.out(None, None, dly.out("sig")));
|
||||||
|
matrix.place(1, 3, Cell::empty(out)
|
||||||
|
.input(out.inp("ch1"), None, None)
|
||||||
|
.out(None, None, None));
|
||||||
|
|
||||||
|
pset_n(&mut matrix, dly, "mix", 1.0);
|
||||||
|
pset_d(&mut matrix, dly, "time", 10.0);
|
||||||
|
pset_n(&mut matrix, dly, "fb", 1.0);
|
||||||
|
|
||||||
|
matrix.sync().unwrap();
|
||||||
|
|
||||||
|
// Emit the trigger signal:
|
||||||
|
pset_n(&mut matrix, test, "trig", 1.0);
|
||||||
|
|
||||||
|
let res = run_for_ms(&mut node_exec, 100.0);
|
||||||
|
|
||||||
|
let idxs_big = collect_signal_changes(&res.0[..], 70);
|
||||||
|
|
||||||
|
assert_eq!(idxs_big, vec![
|
||||||
|
(441, 100),
|
||||||
|
(441 + 1 * 441, 100),
|
||||||
|
(441 + 2 * 441, 100),
|
||||||
|
(441 + 3 * 441, 100),
|
||||||
|
(441 + 4 * 441, 100),
|
||||||
|
(441 + 5 * 441, 100),
|
||||||
|
(441 + 6 * 441, 100),
|
||||||
|
(441 + 7 * 441, 100),
|
||||||
|
(441 + 8 * 441, 100),
|
||||||
|
]);
|
||||||
|
}
|
Loading…
Reference in a new issue