Skip to main content

firewheel_nodes/
delay_compensation.rs

1use bevy_platform::prelude::Vec;
2use firewheel_core::node::NodeError;
3use firewheel_core::{
4    channel_config::{ChannelConfig, NonZeroChannelCount},
5    mask::{MaskType, SilenceMask},
6    node::{
7        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
8        ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
9    },
10};
11use smallvec::{SmallVec, smallvec};
12
13/// The configuration for a [`DelayCompensationNode`]
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
16#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct DelayCompNodeConfig {
19    /// The number of input and output channels.
20    pub channels: NonZeroChannelCount,
21    /// The number of frames (samples in a single channel of audio) of
22    /// delay compensation.
23    pub delay_frames: usize,
24}
25
26impl Default for DelayCompNodeConfig {
27    fn default() -> Self {
28        Self {
29            channels: NonZeroChannelCount::STEREO,
30            delay_frames: 0,
31        }
32    }
33}
34
35/// A node which delays a signal by a given number samples.
36///
37/// This can be used to avoid phasing issues (comb filtering) caused by
38/// parallel signal paths having differing latencies.
39#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
40#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
41#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub struct DelayCompensationNode;
44
45impl AudioNode for DelayCompensationNode {
46    type Configuration = DelayCompNodeConfig;
47
48    fn info(&self, config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
49        Ok(AudioNodeInfo::new()
50            .debug_name("stereo_to_mono")
51            .channel_config(ChannelConfig {
52                num_inputs: config.channels.get(),
53                num_outputs: config.channels.get(),
54            }))
55        // TODO: If and when the scheduler gets proper in-place processing support, use
56        // in-place processing for this node.
57    }
58
59    fn construct_processor(
60        &self,
61        config: &Self::Configuration,
62        _cx: ConstructProcessorContext,
63    ) -> Result<impl AudioNodeProcessor, NodeError> {
64        let channels = config.channels.get().get() as usize;
65        let buffer_len = channels * config.delay_frames;
66
67        let mut buffer: Vec<f32> = Vec::new();
68        buffer.reserve_exact(buffer_len);
69        buffer.resize(buffer_len, 0.0);
70
71        Ok(Processor {
72            buffer,
73            delay_frames: config.delay_frames,
74            ptr: 0,
75            num_silent_frames_per_channel: smallvec![config.delay_frames; channels],
76        })
77    }
78}
79
80struct Processor {
81    buffer: Vec<f32>,
82    delay_frames: usize,
83    ptr: usize,
84    num_silent_frames_per_channel: SmallVec<[usize; 4]>,
85}
86
87impl AudioNodeProcessor for Processor {
88    fn bypassed(&mut self, bypassed: bool) {
89        if !bypassed {
90            self.buffer.fill(0.0);
91            self.ptr = 0;
92            for ch in self.num_silent_frames_per_channel.iter_mut() {
93                *ch = self.buffer.len();
94            }
95        }
96    }
97
98    fn process(
99        &mut self,
100        info: &ProcInfo,
101        buffers: ProcBuffers,
102        _extra: &mut ProcExtra,
103    ) -> ProcessStatus {
104        if self.delay_frames == 0 {
105            return ProcessStatus::Bypass;
106        }
107
108        // TODO: Use constant mask instead
109        let mut out_silence_mask = SilenceMask::NONE_SILENT;
110
111        let extra_input_frames = info.frames.saturating_sub(self.delay_frames);
112        let first_copy_frames = info.frames.min(self.delay_frames - self.ptr);
113        let second_copy_frames = (info.frames - first_copy_frames).min(self.ptr);
114
115        for (ch_i, (((in_buf, out_buf), delay_buf), num_silent_frames)) in buffers
116            .inputs
117            .iter()
118            .zip(buffers.outputs.iter_mut())
119            .zip(self.buffer.chunks_exact_mut(self.delay_frames))
120            .zip(self.num_silent_frames_per_channel.iter_mut())
121            .enumerate()
122        {
123            let is_input_silent = info.in_silence_mask.is_channel_silent(ch_i);
124
125            let clear_output = *num_silent_frames == self.delay_frames
126                && (info.frames <= self.delay_frames || is_input_silent);
127
128            if clear_output {
129                if !info.out_silence_mask.is_channel_silent(ch_i) {
130                    out_buf[..info.frames].fill(0.0);
131                }
132
133                out_silence_mask.set_channel(ch_i, true);
134            } else {
135                out_buf[..first_copy_frames]
136                    .copy_from_slice(&delay_buf[self.ptr..self.ptr + first_copy_frames]);
137
138                if second_copy_frames > 0 {
139                    out_buf[first_copy_frames..first_copy_frames + second_copy_frames]
140                        .copy_from_slice(&delay_buf[..second_copy_frames]);
141                }
142
143                if extra_input_frames > 0 {
144                    if is_input_silent {
145                        out_buf[self.delay_frames..info.frames].fill(0.0);
146                    } else {
147                        out_buf[self.delay_frames..info.frames]
148                            .copy_from_slice(&in_buf[..extra_input_frames]);
149                    }
150                }
151            }
152
153            if !is_input_silent || *num_silent_frames < self.delay_frames {
154                delay_buf[self.ptr..self.ptr + first_copy_frames].copy_from_slice(
155                    &in_buf[extra_input_frames..extra_input_frames + first_copy_frames],
156                );
157
158                if second_copy_frames > 0 {
159                    delay_buf[..second_copy_frames].copy_from_slice(
160                        &in_buf[extra_input_frames + first_copy_frames..info.frames],
161                    );
162                }
163            }
164
165            *num_silent_frames = if is_input_silent {
166                (*num_silent_frames + info.frames).min(self.delay_frames)
167            } else {
168                0
169            };
170        }
171
172        if info.frames < self.delay_frames {
173            self.ptr += info.frames;
174            if self.ptr >= self.delay_frames {
175                self.ptr -= self.delay_frames;
176            }
177        }
178
179        ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(out_silence_mask))
180    }
181
182    fn new_stream(
183        &mut self,
184        _stream_info: &firewheel_core::StreamInfo,
185        _context: &mut ProcStreamCtx,
186    ) {
187        self.buffer.fill(0.0);
188        self.num_silent_frames_per_channel.fill(self.delay_frames);
189    }
190}