From 297360e398783301c5970e019712a744ee498647 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Tue, 1 Jun 2021 05:14:06 +0200 Subject: [PATCH] started implementing feedback delays --- src/dsp/mod.rs | 7 +++- src/nodes/mod.rs | 12 ++++-- src/nodes/node_exec.rs | 89 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 465d6c4..c379ad1 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -20,6 +20,7 @@ mod satom; pub mod helpers; use crate::nodes::NodeAudioContext; +use crate::nodes::NodeExecContext; use crate::util::AtomicFloat; use std::sync::Arc; @@ -1139,8 +1140,10 @@ impl Node { /// display some kind of position indicator. #[inline] pub fn process( - &mut self, ctx: &mut T, atoms: &[SAtom], params: &[ProcBuf], - inputs: &[ProcBuf], outputs: &mut [ProcBuf], led: LedPhaseVals) + &mut self, ctx: &mut T, ectx: &mut NodeExecContext, + atoms: &[SAtom], params: &[ProcBuf], + inputs: &[ProcBuf], outputs: &mut [ProcBuf], + led: LedPhaseVals) { macro_rules! make_node_process { ($s1: ident => $v1: ident, diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 0dbe208..172f1aa 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -2,9 +2,15 @@ // This is a part of HexoDSP. Released under (A)GPLv3 or any later. // See README.md and COPYING for details. -pub const MAX_ALLOCATED_NODES : usize = 256; -pub const MAX_SMOOTHERS : usize = 36 + 4; // 6 * 6 modulator inputs + 4 UI Knobs -pub const MAX_AVAIL_TRACKERS : usize = 128; +pub const MAX_ALLOCATED_NODES : usize = 256; +pub const MAX_SMOOTHERS : usize = 36 + 4; // 6 * 6 modulator inputs + 4 UI Knobs +pub const MAX_AVAIL_TRACKERS : usize = 128; +pub const MAX_FB_DELAYS : usize = 256; // 256 feedback delays, thats roughly 1.2MB RAM +pub const FB_DELAY_TIME_US : usize = 1500; // 1.5ms +// This means, until 384000 sample rate the times are accurate. +pub const MAX_FB_DELAY_SRATE : usize = 48000 * 8; +pub const MAX_FB_DELAY_SIZE : usize = + (MAX_FB_DELAY_SRATE * FB_DELAY_TIME_US) / 1000000; mod node_prog; mod node_exec; diff --git a/src/nodes/node_exec.rs b/src/nodes/node_exec.rs index 2472a34..cb89e7b 100644 --- a/src/nodes/node_exec.rs +++ b/src/nodes/node_exec.rs @@ -4,7 +4,8 @@ use super::{ GraphMessage, QuickMessage, DropMsg, NodeProg, - UNUSED_MONITOR_IDX, MAX_ALLOCATED_NODES, MAX_SMOOTHERS + UNUSED_MONITOR_IDX, MAX_ALLOCATED_NODES, MAX_SMOOTHERS, + MAX_FB_DELAY_SIZE, FB_DELAY_TIME_US, }; use crate::dsp::{NodeId, Node}; use crate::util::{Smoother, AtomicFloat}; @@ -48,6 +49,9 @@ pub struct NodeExecutor { /// The sample rate pub(crate) sample_rate: f32, + /// Context that can be accessed by all (executed) nodes at runtime. + pub(crate) exec_ctx: NodeExecContext, + /// The connection with the [crate::nodes::NodeConfigurator]. shared: SharedNodeExec, } @@ -69,12 +73,90 @@ pub(crate) struct SharedNodeExec { pub(crate) monitor_backend: MonitorBackend, } +/// Contains audio driver context informations. Such as the number +/// of frames of the current buffer period and allows +/// writing output samples and reading input samples. pub trait NodeAudioContext { fn nframes(&self) -> usize; fn output(&mut self, channel: usize, frame: usize, v: f32); fn input(&mut self, channel: usize, frame: usize) -> f32; } +/// Implements a trivial delay buffer for the feedback nodes +/// FbWr and FbRd. +pub struct FeedbackDelay { + buffer: [f32; MAX_FB_DELAY_SIZE], + write_ptr: usize, + read_ptr: usize, +} + +impl FeedbackDelay { + pub fn new() -> Self { + Self { + buffer: [0.0; MAX_FB_DELAY_SIZE], + write_ptr: 0, + read_ptr: (64 + MAX_FB_DELAY_SIZE) % MAX_FB_DELAY_SIZE, + } + } + + pub fn clear(&mut self) { + self.buffer = [0.0; MAX_FB_DELAY_SIZE]; + } + + pub fn set_sample_rate(&mut self, sr: f32) { + self.buffer = [0.0; MAX_FB_DELAY_SIZE]; + self.write_ptr = 0; + // The delay sample count maximum is defined by MAX_FB_DELAY_SRATE, + // after that the feedback delays become shorter than they should be + // and things won't sound the same at sample rate + // exceeding MAX_FB_DELAY_SRATE. + // + // This is a tradeoff of wasted memory and not having to reallocate + // these delays for sample rate changes and providing delay buffers + // for all 256 theoretical feedback delay nodes. + // + // For more elaborate and longer delays an extra delay node should + // be used before FbWr or after FbRd. + let delay_sample_count = (sr as usize * FB_DELAY_TIME_US) / 1000000; + self.read_ptr = (delay_sample_count + FB_DELAY_TIME_US) + % FB_DELAY_TIME_US; + } + + #[inline] + pub fn write(&mut self, s: f32) { + self.write_ptr = (self.write_ptr + 1) % MAX_FB_DELAY_SIZE; + self.buffer[self.write_ptr] = s; + } + + #[inline] + pub fn read(&mut self) -> f32 { + self.read_ptr = (self.read_ptr + 1) % MAX_FB_DELAY_SIZE; + self.buffer[self.read_ptr] + } +} + +/// Contains global state that all nodes can access. +/// This is used for instance to implement the feedbackd delay nodes. +pub struct NodeExecContext { + pub feedback_delay_buffers: Vec, +} + +impl NodeExecContext { + fn new() -> Self { + let mut fbdb = vec![]; + fbdb.resize_with(MAX_ALLOCATED_NODES, || FeedbackDelay::new()); + Self { + feedback_delay_buffers: fbdb, + } + } + + fn clear(&mut self) { + for b in self.feedback_delay_buffers.iter_mut() { + b.clear(); + } + } +} + impl NodeExecutor { pub(crate) fn new(shared: SharedNodeExec) -> Self { let mut nodes = Vec::new(); @@ -92,6 +174,7 @@ impl NodeExecutor { sample_rate: 44100.0, prog: NodeProg::empty(), monitor_signal_cur_inp_indices: [UNUSED_MONITOR_IDX; MON_SIG_CNT], + exec_ctx: NodeExecContext::new(), shared, } } @@ -120,6 +203,8 @@ impl NodeExecutor { } } + self.exec_ctx.clear(); + self.monitor_signal_cur_inp_indices = [UNUSED_MONITOR_IDX; MON_SIG_CNT]; @@ -312,6 +397,7 @@ impl NodeExecutor { let nodes = &mut self.nodes; let ctx_vals = &mut self.shared.node_ctx_values; let prog = &mut self.prog; + let exec_ctx = &mut self.exec_ctx; let prog_out_fb = prog.out_feedback.input_buffer(); @@ -325,6 +411,7 @@ impl NodeExecutor { nodes[op.idx as usize] .process( ctx, + exec_ctx, &prog.atoms[at.0..at.1], &prog.inp[inp.0..inp.1], &prog.cur_inp[inp.0..inp.1],