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]
|
||||
microfft = "0.3.1"
|
||||
num-complex = "0.2"
|
||||
#jack = "0.6.6"
|
||||
jack = "0.6.6"
|
||||
|
||||
[lib]
|
||||
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)
|
||||
//"""
|
||||
// Written in 2015 by Sebastiano Vigna (vigna@acm.org)
|
||||
//
|
||||
// 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 <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//"""
|
||||
//
|
||||
// Written by Alexander Stocko <as@coder.gg>
|
||||
//
|
||||
// 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.
|
||||
/// A splitmix64 random number generator.
|
||||
///
|
||||
/// The splitmix algorithm is not suitable for cryptographic purposes, but is
|
||||
/// very fast and has a 64 bit state.
|
||||
///
|
||||
/// The algorithm used here is translated from [the `splitmix64.c`
|
||||
/// reference source code](http://xoshiro.di.unimi.it/splitmix64.c) by
|
||||
/// Sebastiano Vigna. For `next_u32`, a more efficient mixing function taken
|
||||
/// from [`dsiutils`](http://dsiutils.di.unimi.it/) is used.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SplitMix64(pub u64);
|
||||
|
||||
const PHI: u64 = 0x9e3779b97f4a7c15;
|
||||
|
||||
impl SplitMix64 {
|
||||
pub fn new(seed: u64) -> Self { Self(seed) }
|
||||
pub fn new_from_i64(seed: i64) -> Self {
|
||||
|
@ -139,13 +139,11 @@ impl SplitMix64 {
|
|||
|
||||
#[inline]
|
||||
pub fn next_u64(&mut self) -> u64 {
|
||||
use std::num::Wrapping as w;
|
||||
|
||||
let mut z = w(self.0) + w(0x9E37_79B9_7F4A_7C15_u64);
|
||||
self.0 = z.0;
|
||||
z = (z ^ (z >> 30)) * w(0xBF58_476D_1CE4_E5B9_u64);
|
||||
z = (z ^ (z >> 27)) * w(0x94D0_49BB_1331_11EB_u64);
|
||||
(z ^ (z >> 31)).0
|
||||
self.0 = self.0.wrapping_add(PHI);
|
||||
let mut z = self.0;
|
||||
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
|
||||
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
|
||||
z ^ (z >> 31)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -455,6 +453,11 @@ impl TriggerPhaseClock {
|
|||
self.clock_samples = 0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sync(&mut self) {
|
||||
self.clock_phase = 0.0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 {
|
||||
if self.prev_trigger {
|
||||
|
@ -559,10 +562,13 @@ impl DelayBuffer {
|
|||
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]
|
||||
pub fn feed(&mut self, input: f32) {
|
||||
self.wr = (self.wr + 1) % self.data.len();
|
||||
self.data[self.wr] = input;
|
||||
self.wr = (self.wr + 1) % self.data.len();
|
||||
}
|
||||
|
||||
#[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
|
||||
// Copyright (C) 2020 TheWaveWarden
|
||||
// under GPLv3 or any later
|
||||
|
|
|
@ -20,6 +20,10 @@ mod node_fbwr_fbrd;
|
|||
mod node_ad;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_delay;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_allp;
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod node_noise;
|
||||
|
||||
pub mod tracker;
|
||||
mod satom;
|
||||
|
@ -44,6 +48,7 @@ use crate::fa_sampl_pmode;
|
|||
use crate::fa_sampl_dir;
|
||||
use crate::fa_ad_mult;
|
||||
use crate::fa_delay_mode;
|
||||
use crate::fa_noise_mode;
|
||||
|
||||
use node_amp::Amp;
|
||||
use node_sin::Sin;
|
||||
|
@ -55,6 +60,8 @@ use node_fbwr_fbrd::FbWr;
|
|||
use node_fbwr_fbrd::FbRd;
|
||||
use node_ad::Ad;
|
||||
use node_delay::Delay;
|
||||
use node_allp::AllP;
|
||||
use node_noise::Noise;
|
||||
|
||||
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:
|
||||
macro_rules! stp_d { () => { (20.0, 100.0) } }
|
||||
|
@ -370,7 +391,8 @@ define_exp!{n_declick d_declick 0.0, 50.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
|
||||
// to reach more exact "1.0".
|
||||
|
@ -405,7 +427,8 @@ macro_rules! node_list {
|
|||
[0 sig],
|
||||
tseq => TSeq UIType::Generic UICategory::CV
|
||||
(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]
|
||||
[1 trk2]
|
||||
[2 trk3]
|
||||
|
@ -463,17 +486,29 @@ macro_rules! node_list {
|
|||
[0 sig]
|
||||
[1 eoet],
|
||||
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)
|
||||
(2 time n_time d_time r_tms f_ms stp_m 0.0, 1.0, 0.5)
|
||||
(3 fb n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0)
|
||||
(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 -1.0, 1.0, 0.0)
|
||||
(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}
|
||||
[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
|
||||
(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}
|
||||
[0 sig],
|
||||
{1 0 p param(0.0) fa_test_s 0 10}
|
||||
{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 {
|
||||
$(
|
||||
#[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)]
|
||||
pub struct Delay {
|
||||
buffer: Box<DelayBuffer>,
|
||||
fb_sample: f32,
|
||||
clock: TriggerSampleClock,
|
||||
}
|
||||
|
||||
|
@ -29,7 +28,6 @@ impl Delay {
|
|||
pub fn new(_nid: &NodeId) -> Self {
|
||||
Self {
|
||||
buffer: Box::new(DelayBuffer::new()),
|
||||
fb_sample: 0.0,
|
||||
clock: TriggerSampleClock::new(),
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +43,7 @@ impl Delay {
|
|||
likings.\nRange: (0..1)";
|
||||
pub const fb : &'static str =
|
||||
"Delay fb\nThe feedback amount of the delay output to it's input. \
|
||||
\nRange: (0..1)";
|
||||
\nRange: (-1..1)";
|
||||
pub const mix : &'static str =
|
||||
"Delay mix\nThe dry/wet mix of the delay.\nRange: (0..1)";
|
||||
pub const mode : &'static str =
|
||||
|
@ -108,43 +106,36 @@ impl DspNode for Delay {
|
|||
let mix = inp::Delay::mix(inputs);
|
||||
let out = out::Delay::sig(outputs);
|
||||
|
||||
let mut fb_s = self.fb_sample;
|
||||
|
||||
if mode.i() == 0 {
|
||||
for frame in 0..ctx.nframes() {
|
||||
let dry = inp.read(frame);
|
||||
buffer.feed(dry + fb_s * denorm::Delay::fb(fb, frame));
|
||||
|
||||
let out_sample =
|
||||
buffer.cubic_interpolate_at(
|
||||
denorm::Delay::time(time, frame));
|
||||
|
||||
buffer.feed(dry + out_sample * denorm::Delay::fb(fb, frame));
|
||||
|
||||
out.write(frame,
|
||||
crossfade(dry, out_sample,
|
||||
denorm::Delay::mix(mix, frame).clamp(0.0, 1.0)));
|
||||
|
||||
fb_s = out_sample;
|
||||
}
|
||||
} else {
|
||||
for frame in 0..ctx.nframes() {
|
||||
let dry = inp.read(frame);
|
||||
buffer.feed(dry + fb_s * denorm::Delay::fb(fb, frame));
|
||||
|
||||
let clock_samples =
|
||||
self.clock.next(denorm::Delay::trig(trig, frame));
|
||||
|
||||
let out_sample = buffer.at(clock_samples as usize);
|
||||
|
||||
buffer.feed(dry + out_sample * denorm::Delay::fb(fb, frame));
|
||||
|
||||
out.write(frame,
|
||||
crossfade(dry, out_sample,
|
||||
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;
|
||||
ctx_vals[0].set(out.read(last_frame));
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||
use crate::dsp::{NodeId, SAtom, ProcBuf, GraphFun, GraphAtomData, DspNode, LedPhaseVals};
|
||||
use crate::dsp::helpers::{TrigSignal};
|
||||
|
||||
#[macro_export]
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Test {
|
||||
trig_sig: TrigSignal,
|
||||
trigger: bool,
|
||||
}
|
||||
|
||||
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 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 {
|
||||
fn outputs() -> usize { 1 }
|
||||
fn outputs() -> usize { 2 }
|
||||
|
||||
fn set_sample_rate(&mut self, _srate: f32) { }
|
||||
fn reset(&mut self) { }
|
||||
fn set_sample_rate(&mut self, srate: f32) {
|
||||
self.trig_sig.set_sample_rate(srate);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.trig_sig.reset();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process<T: NodeAudioContext>(
|
||||
|
@ -55,12 +69,36 @@ impl DspNode for Test {
|
|||
atoms: &[SAtom], _params: &[ProcBuf], _inputs: &[ProcBuf],
|
||||
outputs: &mut [ProcBuf], _led: LedPhaseVals)
|
||||
{
|
||||
use crate::dsp::{out, at};
|
||||
use crate::dsp::{out_idx, at};
|
||||
|
||||
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() {
|
||||
if trigger > 0 {
|
||||
self.trig_sig.trigger();
|
||||
trigger = 0;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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::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
|
||||
#[derive(Debug)]
|
||||
pub struct TSeq {
|
||||
backend: Option<Box<TrackerBackend>>,
|
||||
clock: TriggerPhaseClock,
|
||||
srate: f64,
|
||||
time: Box<TSeqTime>,
|
||||
}
|
||||
|
||||
impl Clone for TSeq {
|
||||
|
@ -39,7 +45,10 @@ impl TSeq {
|
|||
Self {
|
||||
backend: None,
|
||||
srate: 48000.0,
|
||||
clock: TriggerPhaseClock::new(),
|
||||
time: Box::new(TSeqTime {
|
||||
clock: TriggerPhaseClock::new(),
|
||||
trigger: Trigger::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,6 +58,8 @@ impl TSeq {
|
|||
|
||||
pub const clock : &'static str =
|
||||
"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 =
|
||||
"TSeq cmode\n'clock' input signal mode:\n\
|
||||
- RowT: Trigger = advance row\n\
|
||||
|
@ -89,12 +100,25 @@ impl TSeq {
|
|||
pub const HELP : &'static str =
|
||||
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
|
||||
types:
|
||||
|
||||
- Note column: for specifying pitches.
|
||||
- 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.
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
- 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
|
||||
gate per row, and 0x000 being 16 gates per row.
|
||||
- 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
|
||||
6% probability. 50% is 0x070.
|
||||
|
||||
|
@ -118,7 +146,7 @@ column type:
|
|||
|
||||
- Step gat1-gat6: Like note columns, this will output a 1.0 for the whole
|
||||
row if a step value is set. With two step values directly
|
||||
following each other no 0.0 will be emitted inbetween
|
||||
following each other no 0.0 will be emitted in between
|
||||
the rows. This means if you want to drive an envelope
|
||||
with release phase with this signal, you need to make
|
||||
space for the release phase.
|
||||
|
@ -127,6 +155,12 @@ column type:
|
|||
- 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
|
||||
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) {
|
||||
self.backend = None;
|
||||
self.clock.reset();
|
||||
self.time.clock.reset();
|
||||
self.time.trigger.reset();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -148,8 +183,9 @@ impl DspNode for TSeq {
|
|||
atoms: &[SAtom], _params: &[ProcBuf], inputs: &[ProcBuf],
|
||||
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 trig = inp::TSeq::trig(inputs);
|
||||
let cmode = at::TSeq::cmode(atoms);
|
||||
|
||||
let backend =
|
||||
|
@ -163,13 +199,21 @@ impl DspNode for TSeq {
|
|||
[0.0; MAX_BLOCK_SIZE];
|
||||
|
||||
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() {
|
||||
if time.trigger.check_trigger(
|
||||
denorm::TSeq::trig(trig, frame))
|
||||
{
|
||||
time.clock.sync();
|
||||
}
|
||||
|
||||
let phase =
|
||||
match cmode {
|
||||
0 => self.clock.next_phase(plen, clock.read(frame)) / plen,
|
||||
1 => self.clock.next_phase(1.0, clock.read(frame)),
|
||||
0 => time.clock.next_phase(plen, clock.read(frame)) / plen,
|
||||
1 => time.clock.next_phase(1.0, clock.read(frame)),
|
||||
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
|
||||
/// 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.
|
||||
pub fn load<'a>(&'a mut self, path: &str) -> Result<&'a SAtom, SampleLoadError> {
|
||||
if self.loaded_samples.get(path).is_some() {
|
||||
|
|
|
@ -747,10 +747,8 @@ fn check_matrix_tseq() {
|
|||
.input(out.inp("ch1"), None, None));
|
||||
matrix.sync().unwrap();
|
||||
|
||||
let freq_param = sin.inp_param("freq").unwrap();
|
||||
matrix.set_param(freq_param, SAtom::param(-0.978));
|
||||
let cmode_param = tsq.inp_param("cmode").unwrap();
|
||||
matrix.set_param(cmode_param, SAtom::setting(1));
|
||||
pset_n(&mut matrix, sin, "freq", -0.978);
|
||||
pset_s(&mut matrix, tsq, "cmode", 1);
|
||||
|
||||
let pat = matrix.get_pattern_data(0).unwrap();
|
||||
{
|
||||
|
@ -782,13 +780,13 @@ fn check_matrix_tseq() {
|
|||
assert_float_eq!(samples[9], 0.42228);
|
||||
|
||||
// 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);
|
||||
|
||||
assert_vec_feq!(samples, vec![0.70411, 0.90413, 0.99306, 0.97972, 0.966387]);
|
||||
|
||||
// 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);
|
||||
|
||||
assert_float_eq!(samples[0], 0.2491);
|
||||
|
@ -798,6 +796,73 @@ fn check_matrix_tseq() {
|
|||
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]
|
||||
fn check_matrix_tseq_gate() {
|
||||
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_rules! assert_rmsmima {
|
||||
($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)]
|
||||
pub fn pset_n(matrix: &mut Matrix, nid: NodeId, parm: &str, v_norm: f32) {
|
||||
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