started implementing feedback delays
This commit is contained in:
parent
2350db8e60
commit
297360e398
3 changed files with 102 additions and 6 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in a new issue