Skip to main content

firewheel_nodes/
triple_buffer.rs

1use bevy_platform::sync::{Arc, Mutex, MutexGuard};
2use core::num::{NonZeroU32, NonZeroUsize};
3use firewheel_core::node::NodeError;
4use firewheel_core::{
5    StreamInfo,
6    channel_config::{ChannelConfig, ChannelCount, NonZeroChannelCount},
7    diff::{Diff, EventQueue, Patch, PatchError, PathBuilder},
8    dsp::buffer::SequentialBuffer,
9    event::{ParamData, ProcEvents},
10    node::{
11        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
12        ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
13    },
14};
15
16#[cfg(not(feature = "std"))]
17use num_traits::Float;
18
19/// The configuration of a [`TripleBufferNode`]
20#[derive(Debug, Clone, Copy, PartialEq)]
21#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
22#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct TripleBufferConfig {
25    /// The number of channels
26    pub channels: NonZeroChannelCount,
27    /// The maximum window size that can be used
28    pub max_window_size: WindowSize,
29}
30
31impl Default for TripleBufferConfig {
32    fn default() -> Self {
33        Self {
34            channels: NonZeroChannelCount::STEREO,
35            max_window_size: WindowSize::default(),
36        }
37    }
38}
39
40/// The window size for a [`TripleBufferNode`]
41#[derive(Debug, Clone, Copy, PartialEq)]
42#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
43#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub enum WindowSize {
46    /// Use the capacity in units of samples (of a single channel
47    /// of audio)
48    Samples(u32),
49    /// Use the capacity in units of seconds
50    Seconds(f64),
51}
52
53impl WindowSize {
54    pub fn as_frames(&self, sample_rate: NonZeroU32) -> u32 {
55        match self {
56            Self::Samples(samples) => *samples,
57            Self::Seconds(seconds) => (seconds * (sample_rate.get() as f64)).round() as u32,
58        }
59    }
60}
61
62impl Default for WindowSize {
63    fn default() -> Self {
64        Self::Samples(2048)
65    }
66}
67
68impl Diff for WindowSize {
69    fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
70        if self != baseline {
71            match self {
72                WindowSize::Samples(samples) => event_queue.push_param(*samples, path),
73                WindowSize::Seconds(seconds) => event_queue.push_param(*seconds, path),
74            }
75        }
76    }
77}
78
79impl Patch for WindowSize {
80    type Patch = Self;
81
82    fn patch(data: &ParamData, _: &[u32]) -> Result<Self::Patch, PatchError> {
83        match data {
84            ParamData::U32(samples) => Ok(Self::Samples(*samples)),
85            ParamData::F64(seconds) => Ok(Self::Seconds(*seconds)),
86            _ => Err(PatchError::InvalidData),
87        }
88    }
89
90    fn apply(&mut self, value: Self::Patch) {
91        *self = value;
92    }
93}
94
95/// A node that sends raw audio data from the audio graph to another
96/// thread. Useful for cases where you only care about the latest data
97/// in the buffer, such as for creating visualizers.
98#[derive(Default, Diff, Patch, Debug, Clone, Copy, PartialEq)]
99#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
100#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub struct TripleBufferNode {
103    /// The window size (the number of frames in each channel in the output buffer)
104    pub window_size: WindowSize,
105}
106
107#[derive(Clone)]
108pub struct TripleBufferState {
109    num_channels: NonZeroChannelCount,
110    active_state: Arc<Mutex<Option<ActiveState>>>,
111}
112
113impl TripleBufferState {
114    /// The number of channels in this buffer.
115    pub fn num_channels(&self) -> NonZeroChannelCount {
116        self.num_channels
117    }
118
119    /// Get the latest audio data in the triple buffer.
120    pub fn output<'a>(&'a mut self) -> OutputDataGuard<'a> {
121        OutputDataGuard {
122            guarded_state: self.active_state.lock().unwrap(),
123        }
124    }
125}
126
127struct ActiveState {
128    consumer: triple_buffer::Output<TripleBufferData>,
129    sample_rate: NonZeroU32,
130}
131
132pub struct OutputData<'a> {
133    /// The samples of data.
134    ///
135    /// Note, the length of this buffer may be longer than the actual number of
136    /// frames that are currently written to. Only read up to [`OutputData::frames`]
137    /// from this buffer.
138    pub buffer: &'a SequentialBuffer<f32>,
139
140    /// The number of frames of usable data that are in [`OutputData::buffer`].
141    pub frames: usize,
142
143    /// A value equal to how many times the buffer has been updated since the node
144    /// was first created. This can be used to quickly check if the buffer differs
145    /// from the previous read.
146    pub generation: u64,
147}
148
149pub struct OutputDataGuard<'a> {
150    guarded_state: MutexGuard<'a, Option<ActiveState>>,
151}
152
153impl<'a> OutputDataGuard<'a> {
154    /// Returns `true` if the node is currently active.
155    pub fn is_active(&self) -> bool {
156        self.guarded_state.is_some()
157    }
158
159    /// The sample rate of the audio data.
160    ///
161    /// If the node is not currently active, then this will return `None`.
162    pub fn sample_rate(&self) -> Option<NonZeroU32> {
163        self.guarded_state.as_ref().map(|s| s.sample_rate)
164    }
165
166    /// Get the latest audio data.
167    ///
168    /// If the node is not currently active, then this will return `None`.
169    pub fn data<'b>(&'b mut self) -> Option<OutputData<'b>> {
170        self.guarded_state.as_mut().map(|s| {
171            let c = s.consumer.read();
172            OutputData {
173                buffer: &c.buffer,
174                frames: c.frames,
175                generation: c.generation,
176            }
177        })
178    }
179
180    /// Peek the data that is currently in the buffer without checking if
181    /// there is new data.
182    ///
183    /// If the node is not currently active, then this will return `None`.
184    pub fn peek_data<'b>(&'b self) -> Option<OutputData<'b>> {
185        self.guarded_state.as_ref().map(|s| {
186            let c = s.consumer.output_buffer();
187            OutputData {
188                buffer: &c.buffer,
189                frames: c.frames,
190                generation: c.generation,
191            }
192        })
193    }
194}
195
196impl AudioNode for TripleBufferNode {
197    type Configuration = TripleBufferConfig;
198
199    fn info(&self, config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
200        Ok(AudioNodeInfo::new()
201            .debug_name("triple_buffer")
202            .channel_config(ChannelConfig {
203                num_inputs: config.channels.get(),
204                num_outputs: ChannelCount::ZERO,
205            })
206            .custom_state(TripleBufferState {
207                num_channels: config.channels,
208                active_state: Arc::new(Mutex::new(None)),
209            }))
210    }
211
212    fn construct_processor(
213        &self,
214        config: &Self::Configuration,
215        mut cx: ConstructProcessorContext,
216    ) -> Result<impl AudioNodeProcessor, NodeError> {
217        let sample_rate = cx.stream_info.sample_rate;
218        let max_window_size_frames = config.max_window_size.as_frames(sample_rate) as usize;
219
220        let (producer, consumer) =
221            triple_buffer::triple_buffer::<TripleBufferData>(&TripleBufferData::new(
222                NonZeroUsize::new(config.channels.get().get() as usize).unwrap(),
223                max_window_size_frames,
224                0,
225            ));
226
227        let state = cx.custom_state_mut::<TripleBufferState>().unwrap();
228
229        *state.active_state.lock().unwrap() = Some(ActiveState {
230            consumer,
231            sample_rate,
232        });
233        let active_state = Arc::clone(&state.active_state);
234
235        let window_size_frames =
236            (self.window_size.as_frames(sample_rate) as usize).min(max_window_size_frames);
237
238        Ok(Processor {
239            producer: Some(producer),
240            config: *config,
241            max_window_size_frames,
242            params: *self,
243            window_size_frames,
244            tmp_ring_buffer: SequentialBuffer::new(
245                NonZeroUsize::new(config.channels.get().get() as usize).unwrap(),
246                max_window_size_frames,
247            ),
248            ring_buf_ptr: 0,
249            active_state,
250            generation: 0,
251            prev_publish_was_silent: true,
252            num_silent_frames_in_tmp: window_size_frames,
253            tmp_buffer_needs_cleared: false,
254            num_inputs: config.channels.get().get() as usize,
255            did_resize: false,
256        })
257    }
258}
259
260struct Processor {
261    producer: Option<triple_buffer::Input<TripleBufferData>>,
262    config: TripleBufferConfig,
263    max_window_size_frames: usize,
264
265    params: TripleBufferNode,
266    window_size_frames: usize,
267
268    tmp_ring_buffer: SequentialBuffer<f32>,
269    ring_buf_ptr: usize,
270
271    // The processor only uses this when a new stream has started.
272    active_state: Arc<Mutex<Option<ActiveState>>>,
273    generation: u64,
274
275    prev_publish_was_silent: bool,
276    num_silent_frames_in_tmp: usize,
277    tmp_buffer_needs_cleared: bool,
278    num_inputs: usize,
279    did_resize: bool,
280}
281
282impl AudioNodeProcessor for Processor {
283    fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
284        let mut new_window_size_frames = self.window_size_frames;
285        for patch in events.drain_patches::<TripleBufferNode>() {
286            match patch {
287                TripleBufferNodePatch::WindowSize(window_size) => {
288                    new_window_size_frames = (window_size.as_frames(info.sample_rate) as usize)
289                        .min(self.max_window_size_frames);
290                }
291            }
292
293            self.params.apply(patch);
294        }
295
296        let producer = self.producer.as_mut().unwrap();
297
298        if self.window_size_frames != new_window_size_frames {
299            let prev = self.window_size_frames;
300
301            // Use the data in the triple buffer as a temporary scratch buffer.
302            let data = producer.input_buffer_mut();
303
304            for (buf_ch, tmp_ch) in data
305                .buffer
306                .iter_channels_mut()
307                .zip(self.tmp_ring_buffer.iter_channels_mut())
308            {
309                let (head, tail) = tmp_ch[..prev].split_at(self.ring_buf_ptr);
310                buf_ch[..tail.len()].copy_from_slice(tail);
311                if tail.len() < prev {
312                    buf_ch[tail.len()..prev].copy_from_slice(head);
313                }
314
315                // Rebuild tmp_ch at the new window size.
316                if prev >= new_window_size_frames {
317                    tmp_ch[..new_window_size_frames]
318                        .copy_from_slice(&buf_ch[prev - new_window_size_frames..prev]);
319                } else {
320                    let pad = new_window_size_frames - prev;
321                    tmp_ch[..pad].fill(0.0);
322                    tmp_ch[pad..new_window_size_frames].copy_from_slice(&buf_ch[..prev]);
323                }
324            }
325
326            self.window_size_frames = new_window_size_frames;
327            self.ring_buf_ptr = 0;
328            self.num_silent_frames_in_tmp = 0;
329            self.did_resize = true;
330        }
331    }
332
333    fn bypassed(&mut self, bypassed: bool) {
334        let Some(producer) = self.producer.as_mut() else {
335            return;
336        };
337
338        if bypassed {
339            {
340                let data = producer.input_buffer_mut();
341
342                for buf_ch in data.buffer.iter_channels_mut() {
343                    buf_ch[..self.window_size_frames].fill(0.0);
344                }
345
346                self.generation += 1;
347                data.generation = self.generation;
348                data.frames = self.window_size_frames;
349            }
350
351            producer.publish();
352
353            for tmp_ch in self.tmp_ring_buffer.iter_channels_mut() {
354                tmp_ch[..self.window_size_frames].fill(0.0);
355            }
356
357            self.ring_buf_ptr = 0;
358            self.prev_publish_was_silent = true;
359            self.num_silent_frames_in_tmp = self.window_size_frames;
360            self.tmp_buffer_needs_cleared = false;
361        }
362    }
363
364    fn process(
365        &mut self,
366        info: &ProcInfo,
367        buffers: ProcBuffers,
368        _extra: &mut ProcExtra,
369    ) -> ProcessStatus {
370        let input_is_silent = info.in_silence_mask.all_channels_silent(self.num_inputs);
371        if input_is_silent {
372            self.num_silent_frames_in_tmp =
373                (self.num_silent_frames_in_tmp + info.frames).min(self.window_size_frames);
374        } else {
375            self.num_silent_frames_in_tmp = 0;
376        }
377
378        if self.num_silent_frames_in_tmp == self.window_size_frames
379            && self.prev_publish_was_silent
380            && !self.did_resize
381        {
382            // The previous publish already contained silence, so no need to publish again.
383            self.tmp_buffer_needs_cleared = true;
384            return ProcessStatus::ClearAllOutputs;
385        }
386        self.did_resize = false;
387
388        if info.frames >= self.window_size_frames {
389            // Just copy all the new data.
390            for (tmp_ch, in_ch) in self
391                .tmp_ring_buffer
392                .iter_channels_mut()
393                .zip(buffers.inputs.iter())
394            {
395                tmp_ch[..self.window_size_frames]
396                    .copy_from_slice(&in_ch[info.frames - self.window_size_frames..info.frames]);
397            }
398            self.ring_buf_ptr = 0;
399            self.tmp_buffer_needs_cleared = false;
400        } else {
401            if self.tmp_buffer_needs_cleared {
402                self.tmp_buffer_needs_cleared = false;
403
404                for tmp_ch in self.tmp_ring_buffer.iter_channels_mut() {
405                    tmp_ch[..self.window_size_frames].fill(0.0);
406                }
407                self.ring_buf_ptr = 0;
408
409                self.num_silent_frames_in_tmp = self.window_size_frames;
410            }
411
412            let first_copy_frames = info.frames.min(self.window_size_frames - self.ring_buf_ptr);
413            let second_copy_frames = info.frames - first_copy_frames;
414
415            for (tmp_ch, in_ch) in self
416                .tmp_ring_buffer
417                .iter_channels_mut()
418                .zip(buffers.inputs.iter())
419            {
420                if first_copy_frames > 0 {
421                    tmp_ch[self.ring_buf_ptr..self.ring_buf_ptr + first_copy_frames]
422                        .copy_from_slice(&in_ch[..first_copy_frames]);
423                }
424
425                if second_copy_frames > 0 {
426                    tmp_ch[..second_copy_frames]
427                        .copy_from_slice(&in_ch[first_copy_frames..info.frames]);
428                }
429            }
430
431            self.ring_buf_ptr = if second_copy_frames > 0 {
432                second_copy_frames
433            } else {
434                self.ring_buf_ptr + first_copy_frames
435            };
436        }
437
438        let producer = self.producer.as_mut().unwrap();
439
440        {
441            let buffer = producer.input_buffer_mut();
442
443            for (buf_ch, tmp_ch) in buffer
444                .buffer
445                .iter_channels_mut()
446                .zip(self.tmp_ring_buffer.iter_channels())
447            {
448                let (head, tail) = tmp_ch[..self.window_size_frames].split_at(self.ring_buf_ptr);
449                buf_ch[..tail.len()].copy_from_slice(tail);
450                buf_ch[tail.len()..self.window_size_frames].copy_from_slice(head);
451            }
452
453            self.generation += 1;
454            buffer.generation = self.generation;
455            buffer.frames = self.window_size_frames;
456        }
457
458        producer.publish();
459
460        self.prev_publish_was_silent = self.num_silent_frames_in_tmp == self.window_size_frames;
461
462        ProcessStatus::ClearAllOutputs
463    }
464
465    fn stream_stopped(&mut self, _context: &mut ProcStreamCtx) {
466        *self.active_state.lock().unwrap() = None;
467        self.producer = None;
468    }
469
470    fn new_stream(&mut self, stream_info: &StreamInfo, _context: &mut ProcStreamCtx) {
471        self.max_window_size_frames = self
472            .config
473            .max_window_size
474            .as_frames(stream_info.sample_rate) as usize;
475
476        self.window_size_frames = (self.params.window_size.as_frames(stream_info.sample_rate)
477            as usize)
478            .min(self.max_window_size_frames);
479
480        self.tmp_ring_buffer = SequentialBuffer::new(
481            NonZeroUsize::new(self.config.channels.get().get() as usize).unwrap(),
482            self.max_window_size_frames,
483        );
484
485        self.ring_buf_ptr = 0;
486        self.num_silent_frames_in_tmp = self.window_size_frames;
487        self.tmp_buffer_needs_cleared = false;
488        self.prev_publish_was_silent = true;
489
490        self.generation += 1;
491
492        let (producer, consumer) =
493            triple_buffer::triple_buffer::<TripleBufferData>(&TripleBufferData::new(
494                NonZeroUsize::new(self.config.channels.get().get() as usize).unwrap(),
495                self.max_window_size_frames,
496                self.generation,
497            ));
498
499        *self.active_state.lock().unwrap() = Some(ActiveState {
500            consumer,
501            sample_rate: stream_info.sample_rate,
502        });
503
504        self.producer = Some(producer);
505    }
506}
507
508// A wrapper to ensure that the triple buffer uses `reserve_exact` when cloning
509// the initial buffers.
510struct TripleBufferData {
511    buffer: SequentialBuffer<f32>,
512    max_frames: usize,
513    frames: usize,
514    generation: u64,
515}
516
517impl TripleBufferData {
518    fn new(num_channels: NonZeroUsize, max_frames: usize, generation: u64) -> Self {
519        Self {
520            buffer: SequentialBuffer::new(num_channels, max_frames),
521            max_frames,
522            frames: 0,
523            generation,
524        }
525    }
526}
527
528impl Clone for TripleBufferData {
529    fn clone(&self) -> Self {
530        Self::new(self.buffer.num_channels(), self.max_frames, self.generation)
531    }
532}