started implementing feedback delays

This commit is contained in:
Weird Constructor 2021-06-01 05:14:06 +02:00
parent 2350db8e60
commit 297360e398
3 changed files with 102 additions and 6 deletions

View file

@ -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<T: NodeAudioContext>(
&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,

View file

@ -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;

View file

@ -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<FeedbackDelay>,
}
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],