Skip to main content

firewheel_nodes/
delay_compensation.rs

1use bevy_platform::prelude::Vec;
2use firewheel_core::{
3    channel_config::{ChannelConfig, NonZeroChannelCount},
4    event::ProcEvents,
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) -> AudioNodeInfo {
49        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    }
56
57    fn construct_processor(
58        &self,
59        config: &Self::Configuration,
60        _cx: ConstructProcessorContext,
61    ) -> impl AudioNodeProcessor {
62        let channels = config.channels.get().get() as usize;
63        let buffer_len = channels * config.delay_frames;
64
65        let mut buffer: Vec<f32> = Vec::new();
66        buffer.reserve_exact(buffer_len);
67        buffer.resize(buffer_len, 0.0);
68
69        Processor {
70            buffer,
71            delay_frames: config.delay_frames,
72            ptr: 0,
73            num_silent_frames_per_channel: smallvec![config.delay_frames; channels],
74        }
75    }
76}
77
78struct Processor {
79    buffer: Vec<f32>,
80    delay_frames: usize,
81    ptr: usize,
82    num_silent_frames_per_channel: SmallVec<[usize; 4]>,
83}
84
85impl AudioNodeProcessor for Processor {
86    fn process(
87        &mut self,
88        info: &ProcInfo,
89        buffers: ProcBuffers,
90        _events: &mut ProcEvents,
91        _extra: &mut ProcExtra,
92    ) -> ProcessStatus {
93        if self.delay_frames == 0 {
94            return ProcessStatus::Bypass;
95        }
96
97        // TODO: Use constant mask instead
98        let mut out_silence_mask = SilenceMask::NONE_SILENT;
99
100        let extra_input_frames = if info.frames > self.delay_frames {
101            info.frames - self.delay_frames
102        } else {
103            0
104        };
105        let first_copy_frames = info.frames.min(self.delay_frames - self.ptr);
106        let second_copy_frames = (info.frames - first_copy_frames).min(self.ptr);
107
108        for (ch_i, (((in_buf, out_buf), delay_buf), num_silent_frames)) in buffers
109            .inputs
110            .iter()
111            .zip(buffers.outputs.iter_mut())
112            .zip(self.buffer.chunks_exact_mut(self.delay_frames))
113            .zip(self.num_silent_frames_per_channel.iter_mut())
114            .enumerate()
115        {
116            let is_input_silent = info.in_silence_mask.is_channel_silent(ch_i);
117
118            let clear_output = *num_silent_frames == self.delay_frames
119                && (info.frames <= self.delay_frames || is_input_silent);
120
121            if clear_output {
122                if !info.out_silence_mask.is_channel_silent(ch_i) {
123                    out_buf[..info.frames].fill(0.0);
124                }
125
126                out_silence_mask.set_channel(ch_i, true);
127            } else {
128                out_buf[..first_copy_frames]
129                    .copy_from_slice(&delay_buf[self.ptr..self.ptr + first_copy_frames]);
130
131                if second_copy_frames > 0 {
132                    out_buf[first_copy_frames..first_copy_frames + second_copy_frames]
133                        .copy_from_slice(&delay_buf[..second_copy_frames]);
134                }
135
136                if extra_input_frames > 0 {
137                    if is_input_silent {
138                        out_buf[self.delay_frames..info.frames].fill(0.0);
139                    } else {
140                        out_buf[self.delay_frames..info.frames]
141                            .copy_from_slice(&in_buf[..extra_input_frames]);
142                    }
143                }
144            }
145
146            if !is_input_silent || *num_silent_frames < self.delay_frames {
147                delay_buf[self.ptr..self.ptr + first_copy_frames].copy_from_slice(
148                    &in_buf[extra_input_frames..extra_input_frames + first_copy_frames],
149                );
150
151                if second_copy_frames > 0 {
152                    delay_buf[..second_copy_frames].copy_from_slice(
153                        &in_buf[extra_input_frames + first_copy_frames..info.frames],
154                    );
155                }
156            }
157
158            *num_silent_frames = if is_input_silent {
159                (*num_silent_frames + info.frames).min(self.delay_frames)
160            } else {
161                0
162            };
163        }
164
165        if info.frames < self.delay_frames {
166            self.ptr += info.frames;
167            if self.ptr >= self.delay_frames {
168                self.ptr -= self.delay_frames;
169            }
170        }
171
172        ProcessStatus::OutputsModifiedWithMask(MaskType::Silence(out_silence_mask))
173    }
174
175    fn new_stream(
176        &mut self,
177        _stream_info: &firewheel_core::StreamInfo,
178        _context: &mut ProcStreamCtx,
179    ) {
180        self.buffer.fill(0.0);
181        self.num_silent_frames_per_channel.fill(self.delay_frames);
182    }
183}