rust_audio_api/nodes/delay.rs
1use crate::types::{AudioUnit, empty_audio_unit};
2use std::collections::VecDeque;
3
4/// A node that delays the input signal by a specified number of audio blocks.
5///
6/// Each audio block ([`AudioUnit`]) contains [`AUDIO_UNIT_SIZE`](crate::types::AUDIO_UNIT_SIZE) samples.
7///
8/// Supports dynamic delay updates via [`ControlMessage::SetParameter`](crate::graph::ControlMessage::SetParameter).
9///
10/// # Example
11/// ```no_run
12/// use rust_audio_api::nodes::{DelayNode, NodeType};
13/// use rust_audio_api::{AudioContext, NodeParameter};
14///
15/// let mut ctx = AudioContext::new().unwrap();
16/// let sample_rate = ctx.sample_rate();
17///
18/// let mut delay_id = None;
19/// let dest_id = ctx.build_graph(|builder| {
20/// // Max delay 2 seconds, initial delay 0.5 seconds
21/// let max_units = (sample_rate as usize * 2) / rust_audio_api::types::AUDIO_UNIT_SIZE;
22/// let initial_units = (sample_rate as f32 * 0.5) as usize / rust_audio_api::types::AUDIO_UNIT_SIZE;
23///
24/// let delay = builder.add_node(NodeType::Delay(DelayNode::new(max_units, initial_units)));
25/// delay_id = Some(delay);
26/// delay
27/// });
28///
29/// // Dynamically change the delay to 1.0 seconds
30/// let new_units = (sample_rate as f32 * 1.0) as usize / rust_audio_api::types::AUDIO_UNIT_SIZE;
31/// ctx.control_sender().send(
32/// rust_audio_api::graph::ControlMessage::SetParameter(
33/// delay_id.unwrap(),
34/// NodeParameter::DelayUnits(new_units)
35/// )
36/// ).unwrap();
37/// ```
38pub struct DelayNode {
39 queue: VecDeque<AudioUnit>,
40 delay_units: usize,
41 max_delay_units: usize,
42}
43
44impl DelayNode {
45 /// Creates a new `DelayNode`.
46 ///
47 /// # Parameters
48 /// - `max_delay_units`: The maximum delay buffer size in units.
49 /// - `default_delay_units`: The initial delay in units.
50 pub fn new(max_delay_units: usize, default_delay_units: usize) -> Self {
51 let delay_units = default_delay_units.min(max_delay_units);
52 let mut queue = VecDeque::with_capacity(max_delay_units + 1);
53
54 // Seed the queue with silent Units based on initial delay_units
55 for _ in 0..delay_units {
56 queue.push_back(empty_audio_unit());
57 }
58
59 Self {
60 queue,
61 delay_units,
62 max_delay_units,
63 }
64 }
65
66 /// Dynamically updates the delay time.
67 ///
68 /// If the new delay is larger than the current one, silent blocks are inserted.
69 /// If it is smaller, old blocks are discarded.
70 pub fn set_delay_units(&mut self, units: usize) {
71 let target_units = units.min(self.max_delay_units);
72
73 if target_units > self.delay_units {
74 // Increase delay: add silent Units
75 for _ in 0..(target_units - self.delay_units) {
76 self.queue.push_front(empty_audio_unit()); // Push to front; these are the new delayed units
77 }
78 } else if target_units < self.delay_units {
79 // Decrease delay: discard old Units (front represents the oldest)
80 for _ in 0..(self.delay_units - target_units) {
81 self.queue.pop_front();
82 }
83 }
84 self.delay_units = target_units;
85 }
86
87 #[inline(always)]
88 pub fn process(&mut self, input: Option<&AudioUnit>, output: &mut AudioUnit) {
89 // Core algorithm: push input (or silence) into the queue
90 if let Some(in_unit) = input {
91 self.queue.push_back(*in_unit);
92 } else {
93 self.queue.push_back(empty_audio_unit());
94 }
95
96 // Then pop a unit from the queue as current output
97 // If delay_units is 0, the queue will only contain the unit just pushed;
98 // popping it results in zero delay.
99 if let Some(delayed_unit) = self.queue.pop_front() {
100 output.copy_from_slice(&delayed_unit);
101 } else {
102 // Fallback mechanism; theoretically, the queue should always have at least one unit
103 dasp::slice::equilibrium(&mut output[..]);
104 }
105 }
106}