Skip to main content

firewheel_core/
node.rs

1use core::any::TypeId;
2use core::error::Error;
3use core::fmt;
4use core::marker::PhantomData;
5use core::ops::Range;
6use core::time::Duration;
7use core::{any::Any, fmt::Debug, hash::Hash, num::NonZeroU32};
8
9#[cfg(feature = "std")]
10use std::collections::hash_map::{Entry, HashMap};
11
12#[cfg(not(feature = "std"))]
13use bevy_platform::collections::hash_map::{Entry, HashMap};
14#[cfg(not(feature = "std"))]
15use bevy_platform::prelude::{Box, Vec};
16
17use crate::dsp::buffer::ConstSequentialBuffer;
18use crate::dsp::volume::is_buffer_silent;
19use crate::log::RealtimeLogger;
20use crate::mask::{ConnectedMask, ConstantMask, MaskType, SilenceMask};
21use crate::{
22    StreamInfo,
23    channel_config::{ChannelConfig, ChannelCount},
24    clock::{DurationSamples, InstantSamples, InstantSeconds},
25    dsp::declick::DeclickValues,
26    event::{NodeEvent, NodeEventType, ProcEvents},
27};
28
29#[cfg(feature = "scheduled_events")]
30use crate::clock::EventInstant;
31
32#[cfg(feature = "musical_transport")]
33use crate::clock::{InstantMusical, MusicalTransport};
34
35/// A globally unique identifier for a node.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
38#[cfg_attr(feature = "bevy_reflect", reflect(opaque))]
39pub struct NodeID(pub thunderdome::Index);
40
41impl NodeID {
42    pub const DANGLING: Self = Self(thunderdome::Index::DANGLING);
43}
44
45impl Default for NodeID {
46    fn default() -> Self {
47        Self::DANGLING
48    }
49}
50
51/// Trait-based catchall error type for node trait methods
52#[derive(Debug)]
53pub struct NodeError(pub Box<dyn Error>);
54
55impl NodeError {
56    pub const fn from_boxed(error: Box<dyn Error>) -> Self {
57        Self(error)
58    }
59}
60
61impl<E> From<E> for NodeError
62where
63    E: Error + 'static,
64{
65    fn from(err: E) -> Self {
66        NodeError(Box::new(err))
67    }
68}
69
70impl fmt::Display for NodeError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "Node Error: {}", self.0)
73    }
74}
75
76impl From<NodeError> for Box<dyn Error> {
77    fn from(value: NodeError) -> Self {
78        value.0
79    }
80}
81
82/// Information about an [`AudioNode`].
83///
84/// This struct enforces the use of the builder pattern for future-proof-ness, as
85/// it is likely that more fields will be added in the future.
86#[derive(Debug)]
87pub struct AudioNodeInfo {
88    debug_name: &'static str,
89    channel_config: ChannelConfig,
90    call_update_method: bool,
91    custom_state: Option<Box<dyn Any>>,
92    latency_frames: u32,
93    in_place_buffers: bool,
94}
95
96impl AudioNodeInfo {
97    /// Construct a new [`AudioNodeInfo`] builder struct.
98    pub const fn new() -> Self {
99        Self {
100            debug_name: "unnamed",
101            channel_config: ChannelConfig {
102                num_inputs: ChannelCount::ZERO,
103                num_outputs: ChannelCount::ZERO,
104            },
105            call_update_method: false,
106            custom_state: None,
107            latency_frames: 0,
108            in_place_buffers: false,
109        }
110    }
111
112    /// A unique name for this type of node, used for debugging purposes.
113    pub const fn debug_name(mut self, debug_name: &'static str) -> Self {
114        self.debug_name = debug_name;
115        self
116    }
117
118    /// The channel configuration of this node.
119    ///
120    /// By default this has a channel configuration with zero input and output
121    /// channels.
122    ///
123    /// WARNING: Audio nodes *MUST* either completely fill all output buffers
124    /// with data, or return [`ProcessStatus::ClearAllOutputs`]/[`ProcessStatus::Bypass`].
125    /// Failing to do this will result in audio glitches.
126    pub const fn channel_config(mut self, channel_config: ChannelConfig) -> Self {
127        self.channel_config = channel_config;
128        self
129    }
130
131    /// Specify that this node is a "pre process" node. Pre-process nodes have zero
132    /// inputs and outputs, and they are processed before all other nodes in the
133    /// graph.
134    pub const fn is_pre_process(mut self) -> Self {
135        self.channel_config = ChannelConfig {
136            num_inputs: ChannelCount::ZERO,
137            num_outputs: ChannelCount::ZERO,
138        };
139        self
140    }
141
142    /// Set to `true` if this node wishes to have the Firewheel context call
143    /// [`AudioNode::update`] on every update cycle.
144    ///
145    /// By default this is set to `false`.
146    pub const fn call_update_method(mut self, call_update_method: bool) -> Self {
147        self.call_update_method = call_update_method;
148        self
149    }
150
151    /// Custom `!Send` state that can be stored in the Firewheel context and accessed
152    /// by the user.
153    ///
154    /// The user accesses this state via `FirewheelCtx::node_state` and
155    /// `FirewheelCtx::node_state_mut`.
156    pub fn custom_state<T: 'static>(mut self, custom_state: T) -> Self {
157        self.custom_state = Some(Box::new(custom_state));
158        self
159    }
160
161    /// Set the latency of this node in frames (samples in a single channel of audio).
162    ///
163    /// By default this is set to `0`.
164    pub const fn latency_frames(mut self, latency_frames: u32) -> Self {
165        self.latency_frames = latency_frames;
166        self
167    }
168
169    /// If set to `true`, then the input buffers will be merged into the output
170    /// buffers. This may improve performance in cases where this node is commonly used
171    /// in a serial chain such as when in a node pool.
172    ///
173    /// If the number of input channels is greater than the number of output channels,
174    /// then the input buffers passed into [`AudioNodeProcessor::process`] will contain
175    /// ONLY the input buffers in the range `[num_outputs_in_config..num_inputs_in_config]`.
176    /// Otherwise, the number of input buffers will be 0.
177    ///
178    /// Note, this currently doesn't improve performance. But if and when the scheduler
179    /// is updated to support in-place buffer processing in a future version, then it
180    /// will.
181    pub const fn in_place_buffers(mut self, in_place_buffers: bool) -> Self {
182        self.in_place_buffers = in_place_buffers;
183        self
184    }
185}
186
187impl Default for AudioNodeInfo {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193impl From<AudioNodeInfo> for AudioNodeInfoInner {
194    fn from(value: AudioNodeInfo) -> Self {
195        AudioNodeInfoInner {
196            debug_name: value.debug_name,
197            channel_config: value.channel_config,
198            call_update_method: value.call_update_method,
199            custom_state: value.custom_state,
200            latency_frames: value.latency_frames,
201            in_place_buffers: value.in_place_buffers,
202        }
203    }
204}
205
206/// Information about an [`AudioNode`]. Used internally by the Firewheel context.
207#[derive(Debug)]
208pub struct AudioNodeInfoInner {
209    pub debug_name: &'static str,
210    pub channel_config: ChannelConfig,
211    pub call_update_method: bool,
212    pub custom_state: Option<Box<dyn Any>>,
213    pub latency_frames: u32,
214    pub in_place_buffers: bool,
215}
216
217/// A trait representing a node in a Firewheel audio graph.
218///
219/// # Notes about ECS
220///
221/// In order to be friendlier to ECS's (entity component systems), it is encouraged
222/// that any struct deriving this trait be POD (plain ol' data). If you want your
223/// audio node to be usable in the Bevy game engine, also derive
224/// `bevy_ecs::prelude::Component`. (You can hide this derive behind a feature flag
225/// by using `#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]`).
226///
227/// # Audio Node Lifecycle
228///
229/// 1. The user constructs the node as POD or from a custom constructor method for
230///    that node.
231/// 2. The user adds the node to the graph using `FirewheelCtx::add_node`. If the
232///    node has any custom configuration, then the user passes that configuration to this
233///    method as well. In this method, the Firewheel context calls [`AudioNode::info`] to
234///    get information about the node. The node can also store any custom state in the
235///    [`AudioNodeInfo`] struct.
236/// 3. At this point the user may now call `FirewheelCtx::node_state` and
237///    `FirewheelCtx::node_state_mut` to retrieve the node's custom state.
238/// 4. If [`AudioNodeInfo::call_update_method`] was set to `true`, then
239///    [`AudioNode::update`] will be called every time the Firewheel context updates.
240///    The node's custom state is also accessible in this method.
241/// 5. When the Firewheel context is ready for the node to start processing data,
242///    it calls [`AudioNode::construct_processor`] to retrieve the realtime
243///    [`AudioNodeProcessor`] counterpart of the node. This processor counterpart is
244///    then sent to the audio thread.
245/// 6. The Firewheel processor calls [`AudioNodeProcessor::process`] whenever there
246///    is a new block of audio data to process.
247///    WARNING: Audio nodes *MUST* either completely fill all output buffers
248///    with data, or return [`ProcessStatus::ClearAllOutputs`]/[`ProcessStatus::Bypass`].
249///    Failing to do this will result in audio glitches.
250/// 7. (Graceful shutdown)
251///
252///    7a. The Firewheel processor calls [`AudioNodeProcessor::stream_stopped`].
253///    The processor is then sent back to the main thread.
254///
255///    7b. If a new audio stream is started, then the context will call
256///    [`AudioNodeProcessor::new_stream`] on the main thread, and then send the
257///    processor back to the audio thread for processing.
258///
259///    7c. If the Firewheel context is dropped before a new stream is started, then
260///    both the node and the processor counterpart are dropped on the main thread.
261/// 8. (Audio thread crashes or stops unexpectedly) - The node's processor counterpart
262///    may or may not be dropped. The user may try to create a new audio stream, in which
263///    case [`AudioNode::construct_processor`] might be called again. If a second processor
264///    instance is not able to be created, or if dropping the processor on the audio thread
265///    is unacceptable behavior, then the node may panic.
266pub trait AudioNode {
267    /// A type representing this constructor's configuration.
268    ///
269    /// This is intended as a one-time configuration to be used
270    /// when constructing an audio node. When no configuration
271    /// is required, [`EmptyConfig`] should be used.
272    type Configuration: Default;
273
274    /// Get information about this node.
275    ///
276    /// This method is only called once per instance after the node is added to the
277    /// audio graph.
278    fn info(&self, configuration: &Self::Configuration) -> Result<AudioNodeInfo, NodeError>;
279
280    /// Construct a realtime processor for this node.
281    ///
282    /// * `configuration` - The custom configuration of this node.
283    /// * `cx` - A context for interacting with the Firewheel context. This context
284    ///   also includes information about the audio stream.
285    fn construct_processor(
286        &self,
287        configuration: &Self::Configuration,
288        cx: ConstructProcessorContext,
289    ) -> Result<impl AudioNodeProcessor, NodeError>;
290
291    /// If [`AudioNodeInfo::call_update_method`] was set to `true`, then the Firewheel
292    /// context will call this method on every update cycle.
293    ///
294    /// * `configuration` - The custom configuration of this node.
295    /// * `cx` - A context for interacting with the Firewheel context.
296    fn update(&mut self, configuration: &Self::Configuration, cx: UpdateContext) {
297        let _ = configuration;
298        let _ = cx;
299    }
300}
301
302/// A context for [`AudioNode::construct_processor`].
303pub struct ConstructProcessorContext<'a> {
304    /// The ID of this audio node.
305    pub node_id: NodeID,
306    /// Information about the running audio stream.
307    pub stream_info: &'a StreamInfo,
308    custom_state: &'a mut Option<Box<dyn Any>>,
309}
310
311impl<'a> ConstructProcessorContext<'a> {
312    pub fn new(
313        node_id: NodeID,
314        stream_info: &'a StreamInfo,
315        custom_state: &'a mut Option<Box<dyn Any>>,
316    ) -> Self {
317        Self {
318            node_id,
319            stream_info,
320            custom_state,
321        }
322    }
323
324    /// Get an immutable reference to the custom state that was created in
325    /// [`AudioNodeInfo::custom_state`].
326    pub fn custom_state<T: 'static>(&self) -> Option<&T> {
327        self.custom_state
328            .as_ref()
329            .and_then(|s| s.downcast_ref::<T>())
330    }
331
332    /// Get a mutable reference to the custom state that was created in
333    /// [`AudioNodeInfo::custom_state`].
334    pub fn custom_state_mut<T: 'static>(&mut self) -> Option<&mut T> {
335        self.custom_state
336            .as_mut()
337            .and_then(|s| s.downcast_mut::<T>())
338    }
339}
340
341/// A context for [`AudioNode::update`].
342pub struct UpdateContext<'a> {
343    /// The ID of this audio node.
344    pub node_id: NodeID,
345    /// Information about the running audio stream. If no audio stream is running,
346    /// then this will be `None`.
347    pub stream_info: Option<&'a StreamInfo>,
348    custom_state: &'a mut Option<Box<dyn Any>>,
349    event_queue: &'a mut Vec<NodeEvent>,
350}
351
352impl<'a> UpdateContext<'a> {
353    pub fn new(
354        node_id: NodeID,
355        stream_info: Option<&'a StreamInfo>,
356        custom_state: &'a mut Option<Box<dyn Any>>,
357        event_queue: &'a mut Vec<NodeEvent>,
358    ) -> Self {
359        Self {
360            node_id,
361            stream_info,
362            custom_state,
363            event_queue,
364        }
365    }
366
367    /// Queue an event to send to this node's processor counterpart.
368    pub fn queue_event(&mut self, event: NodeEventType) {
369        self.event_queue.push(NodeEvent {
370            node_id: self.node_id,
371            #[cfg(feature = "scheduled_events")]
372            time: None,
373            event,
374        });
375    }
376
377    /// Queue an event to send to this node's processor counterpart, at a certain time.
378    ///
379    /// # Performance
380    ///
381    /// Note that for most nodes that handle scheduled events, this will split the buffer
382    /// into chunks and process those chunks. If two events are scheduled too close to one
383    /// another in time then that chunk may be too small for the audio processing to be
384    /// fully vectorized.
385    #[cfg(feature = "scheduled_events")]
386    pub fn schedule_event(&mut self, event: NodeEventType, time: EventInstant) {
387        self.event_queue.push(NodeEvent {
388            node_id: self.node_id,
389            time: Some(time),
390            event,
391        });
392    }
393
394    /// Get an immutable reference to the custom state that was created in
395    /// [`AudioNodeInfo::custom_state`].
396    pub fn custom_state<T: 'static>(&self) -> Option<&T> {
397        self.custom_state
398            .as_ref()
399            .and_then(|s| s.downcast_ref::<T>())
400    }
401
402    /// Get a mutable reference to the custom state that was created in
403    /// [`AudioNodeInfo::custom_state`].
404    pub fn custom_state_mut<T: 'static>(&mut self) -> Option<&mut T> {
405        self.custom_state
406            .as_mut()
407            .and_then(|s| s.downcast_mut::<T>())
408    }
409}
410
411/// An empty constructor configuration.
412///
413/// This should be preferred over `()` because it implements
414/// Bevy's `Component` trait, making the
415/// [`AudioNode`] implementor trivially Bevy-compatible.
416#[derive(Debug, Default, Clone, Copy, PartialEq)]
417#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
418#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
419#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
420pub struct EmptyConfig;
421
422/// A type-erased dyn-compatible [`AudioNode`].
423pub trait DynAudioNode {
424    /// Get information about this node.
425    ///
426    /// This method is only called once after the node is added to the audio graph.
427    fn info(&self) -> Result<AudioNodeInfo, NodeError>;
428
429    /// Construct a realtime processor for this node.
430    ///
431    /// * `cx` - A context for interacting with the Firewheel context. This context
432    ///   also includes information about the audio stream.
433    fn construct_processor(
434        &self,
435        cx: ConstructProcessorContext,
436    ) -> Result<Box<dyn AudioNodeProcessor>, NodeError>;
437
438    /// If [`AudioNodeInfo::call_update_method`] was set to `true`, then the Firewheel
439    /// context will call this method on every update cycle.
440    ///
441    /// * `cx` - A context for interacting with the Firewheel context.
442    fn update(&mut self, cx: UpdateContext) {
443        let _ = cx;
444    }
445}
446
447/// Pairs constructors with their configurations.
448///
449/// This is useful for type-erasing an [`AudioNode`].
450pub struct Constructor<T, C> {
451    constructor: T,
452    configuration: C,
453}
454
455impl<T: AudioNode> Constructor<T, T::Configuration> {
456    pub fn new(constructor: T, configuration: Option<T::Configuration>) -> Self {
457        Self {
458            constructor,
459            configuration: configuration.unwrap_or_default(),
460        }
461    }
462}
463
464impl<T: AudioNode> DynAudioNode for Constructor<T, T::Configuration> {
465    fn info(&self) -> Result<AudioNodeInfo, NodeError> {
466        self.constructor.info(&self.configuration)
467    }
468
469    fn construct_processor(
470        &self,
471        cx: ConstructProcessorContext,
472    ) -> Result<Box<dyn AudioNodeProcessor>, NodeError> {
473        Ok(Box::new(
474            self.constructor
475                .construct_processor(&self.configuration, cx)?,
476        ))
477    }
478
479    fn update(&mut self, cx: UpdateContext) {
480        self.constructor.update(&self.configuration, cx);
481    }
482}
483
484/// The trait describing the realtime processor counterpart to an
485/// audio node.
486pub trait AudioNodeProcessor: 'static + Send {
487    /// Called when there are new events for this node to process.
488    ///
489    /// This is called once before the first call to `process`, and after that
490    /// it will be called whenever there are new events (including when the
491    /// node is bypassed).
492    ///
493    /// Unless this node is bypassed, then [`AudioNodeProcessor::process`] will be
494    /// called immediately after.
495    ///
496    /// * `info` - Information about this processing block.
497    /// * `events` - A list of events for this node to process.
498    /// * `extra` - Additional buffers and utilities.
499    ///
500    /// This is always called in a realtime thread, so do not perform any
501    /// realtime-unsafe operations.
502    fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, extra: &mut ProcExtra) {
503        let _ = info;
504        let _ = events;
505        let _ = extra;
506    }
507
508    /// Called when the node has been fully bypassed/un-bypassed.
509    ///
510    /// The Firewheel processor automatically handles bypass declicking, so
511    /// there is no need to handle that manually.
512    ///
513    /// This is always called in a realtime thread, so do not perform any
514    /// realtime-unsafe operations.
515    fn bypassed(&mut self, bypassed: bool) {
516        let _ = bypassed;
517    }
518
519    /// Process the given block of audio.
520    ///
521    /// * `info` - Information about this processing block.
522    /// * `buffers` - The buffers of data to process.
523    /// * `extra` - Additional buffers and utilities.
524    ///
525    /// WARNING: Audio nodes *MUST* either completely fill all output buffers
526    /// with data, or return [`ProcessStatus::ClearAllOutputs`]/[`ProcessStatus::Bypass`].
527    /// Failing to do this will result in audio glitches. If using
528    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers in the
529    /// range `[0..num_inputs_in_config.min(num_outputs_in_config)]` do not
530    /// need to be filled with data.
531    ///
532    /// This is always called in a realtime thread, so do not perform any
533    /// realtime-unsafe operations.
534    fn process(
535        &mut self,
536        info: &ProcInfo,
537        buffers: ProcBuffers,
538        extra: &mut ProcExtra,
539    ) -> ProcessStatus {
540        let _ = info;
541        let _ = buffers;
542        let _ = extra;
543
544        ProcessStatus::Bypass
545    }
546
547    /// Called when the audio stream has been stopped.
548    ///
549    /// This may or may not be called in a realtime thread, so prefer not
550    /// perform any realtime-unsafe operations.
551    fn stream_stopped(&mut self, context: &mut ProcStreamCtx) {
552        let _ = context;
553    }
554
555    /// Called when a new audio stream has been started after a previous
556    /// call to [`AudioNodeProcessor::stream_stopped`].
557    ///
558    /// This method gets called on the main thread, not the realtime audio
559    /// thread. So it is safe to allocate/deallocate here.
560    fn new_stream(&mut self, stream_info: &StreamInfo, context: &mut ProcStreamCtx) {
561        let _ = stream_info;
562        let _ = context;
563    }
564}
565
566impl AudioNodeProcessor for Box<dyn AudioNodeProcessor> {
567    fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, extra: &mut ProcExtra) {
568        self.as_mut().events(info, events, extra);
569    }
570    fn bypassed(&mut self, bypassed: bool) {
571        self.as_mut().bypassed(bypassed);
572    }
573    fn process(
574        &mut self,
575        info: &ProcInfo,
576        buffers: ProcBuffers,
577        extra: &mut ProcExtra,
578    ) -> ProcessStatus {
579        self.as_mut().process(info, buffers, extra)
580    }
581    fn stream_stopped(&mut self, context: &mut ProcStreamCtx) {
582        self.as_mut().stream_stopped(context)
583    }
584    fn new_stream(&mut self, stream_info: &StreamInfo, context: &mut ProcStreamCtx) {
585        self.as_mut().new_stream(stream_info, context)
586    }
587}
588
589pub struct ProcStreamCtx<'a> {
590    pub store: &'a mut ProcStore,
591    pub logger: &'a mut RealtimeLogger,
592}
593
594pub const NUM_SCRATCH_BUFFERS: usize = 8;
595
596/// The buffers used in [`AudioNodeProcessor::process`]
597#[derive(Debug)]
598pub struct ProcBuffers<'a, 'b> {
599    /// The audio input buffers.
600    ///
601    /// The number of channels will always equal the [`ChannelConfig::num_inputs`]
602    /// value that was returned in [`AudioNode::info`]. Except when
603    /// [`AudioNodeInfo::in_place_buffers`] is used, in which case this will contain
604    /// ONLY the input buffers in the range `[num_outputs_in_config..num_inputs_in_config]`.
605    ///
606    /// Each channel slice will have a length of [`ProcInfo::frames`].
607    pub inputs: &'a [&'b [f32]],
608
609    /// The audio output buffers.
610    ///
611    /// WARNING: The node *MUST* either completely fill all output buffers
612    /// with data, or return [`ProcessStatus::ClearAllOutputs`]/[`ProcessStatus::Bypass`].
613    /// Failing to do this will result in audio glitches. If using
614    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers in the
615    /// range `[0..num_inputs_in_config.min(num_outputs_in_config)]` do not
616    /// need to be filled with data.
617    ///
618    /// The number of channels will always equal the [`ChannelConfig::num_outputs`]
619    /// value that was returned in [`AudioNode::info`].
620    ///
621    /// Each channel slice will have a length of [`ProcInfo::frames`].
622    ///
623    /// These buffers may contain stale data from previous processing cycles.
624    /// They are zero-initialized before the first use, so this is not
625    /// uninitialized memory, but the contents should not be assumed zero.
626    pub outputs: &'a mut [&'b mut [f32]],
627}
628
629impl<'a, 'b> ProcBuffers<'a, 'b> {
630    /// Thoroughly checks if all output buffers contain silence (as in all
631    /// samples have an absolute amplitude less than or equal to `min_amp`).
632    ///
633    /// If all buffers are silent, then [`ProcessStatus::ClearAllOutputs`] will
634    /// be returned. Otherwise, [`ProcessStatus::OutputsModified`] will be
635    /// returned.
636    pub fn check_for_silence_on_outputs(&self, min_amp: f32) -> ProcessStatus {
637        let mut silent = true;
638        for buffer in self.outputs.iter() {
639            if !is_buffer_silent(buffer, min_amp) {
640                silent = false;
641                break;
642            }
643        }
644
645        if silent {
646            ProcessStatus::ClearAllOutputs
647        } else {
648            ProcessStatus::OutputsModified
649        }
650    }
651
652    /// Returns `true` if the input signal has settled to silence by the end
653    /// of the block.
654    pub fn inputs_settled_at_zero(&self) -> bool {
655        let mut settled_at_zero = true;
656
657        for ch in self.inputs.iter() {
658            // Check the last two samples instead of just one since it is
659            // incredibly unlikely that an active signal has two exact
660            // zeros in a row.
661            settled_at_zero = ch.iter().rev().take(2).all(|s| *s == 0.0);
662
663            if !settled_at_zero {
664                break;
665            }
666        }
667
668        settled_at_zero
669    }
670}
671
672/// Extra buffers and utilities for [`AudioNodeProcessor::process`]
673pub struct ProcExtra {
674    /// A list of extra scratch buffers that can be used for processing.
675    /// This removes the need for nodes to allocate their own scratch buffers.
676    /// Each buffer has a length of [`StreamInfo::max_block_frames`]. These
677    /// buffers are shared across all nodes, so assume that they contain junk
678    /// data.
679    pub scratch_buffers: ConstSequentialBuffer<f32, NUM_SCRATCH_BUFFERS>,
680
681    /// A buffer of values that linearly ramp up/down between `0.0` and `1.0`
682    /// which can be used to implement efficient declicking when
683    /// pausing/resuming/stopping.
684    pub declick_values: DeclickValues,
685
686    /// A realtime-safe logger helper.
687    pub logger: RealtimeLogger,
688
689    /// A type-erased store accessible to all [`AudioNodeProcessor`]s.
690    pub store: ProcStore,
691}
692
693/// Information for [`AudioNodeProcessor::process`]
694#[derive(Debug)]
695pub struct ProcInfo {
696    /// The number of frames (samples in a single channel of audio) in
697    /// this processing block.
698    ///
699    /// Not to be confused with video frames.
700    pub frames: usize,
701
702    /// An optional optimization hint on which input channels contain
703    /// all zeros (silence). The first bit (`0x1`) is the first channel,
704    /// the second bit is the second channel, and so on.
705    pub in_silence_mask: SilenceMask,
706
707    /// An optional optimization hint on which output channels contain
708    /// all zeros (silence). The first bit (`0x1`) is the first channel,
709    /// the second bit is the second channel, and so on.
710    pub out_silence_mask: SilenceMask,
711
712    /// An optional optimization hint on which input channels have all
713    /// samples set to the same value. The first bit (`0x1`) is the
714    /// first channel, the second bit is the second channel, and so on.
715    ///
716    /// This can be useful for nodes that use audio buffers as CV
717    /// (control voltage) ports.
718    pub in_constant_mask: ConstantMask,
719
720    /// An optional optimization hint on which input channels have all
721    /// samples set to the same value. The first bit (`0x1`) is the
722    /// first channel, the second bit is the second channel, and so on.
723    ///
724    /// This can be useful for nodes that use audio buffers as CV
725    /// (control voltage) ports.
726    pub out_constant_mask: ConstantMask,
727
728    /// An optional hint on which input channels are connected to other
729    /// nodes in the graph.
730    pub in_connected_mask: ConnectedMask,
731
732    /// An optional hint on which output channels are connected to other
733    /// nodes in the graph.
734    pub out_connected_mask: ConnectedMask,
735
736    /// The sample rate of the audio stream in samples per second.
737    pub sample_rate: NonZeroU32,
738
739    /// The reciprocal of the sample rate. This can be used to avoid a
740    /// division and improve performance.
741    pub sample_rate_recip: f64,
742
743    /// The current time of the audio clock at the first frame in this
744    /// processing block, equal to the total number of frames (samples in
745    /// a single channel of audio) that have been processed since this
746    /// Firewheel context was first started.
747    ///
748    /// Note, this value does *NOT* account for any output underflows
749    /// (underruns) that may have occurred.
750    ///
751    /// Note, generally this value will always count up, but there may be
752    /// a few edge cases that cause this value to be less than the previous
753    /// block, such as when the sample rate of the stream has been changed.
754    pub clock_samples: InstantSamples,
755
756    /// The reciprocal of the total amount of seconds that the CPU can
757    /// spend in this call to the Firewheel Processor's process method
758    /// before underruns will occur.
759    ///
760    /// This can be used for performance profiling.
761    pub total_cpu_seconds_recip: f64,
762
763    /// The duration between when the stream was started an when the
764    /// Firewheel processor's `process` method was called.
765    ///
766    /// Note, this clock is not as accurate as the audio clock.
767    pub duration_since_stream_start: Duration,
768
769    /// Flags indicating the current status of the audio stream
770    pub stream_status: StreamStatus,
771
772    /// If an output underflow (underrun) occurred, then this will contain
773    /// an estimate for the number of frames (samples in a single channel
774    /// of audio) that were dropped.
775    ///
776    /// This can be used to correct the timing of events if desired.
777    ///
778    /// Note, this is just an estimate, and may not always be perfectly
779    /// accurate.
780    ///
781    /// If an underrun did not occur, then this will be `0`.
782    pub dropped_frames: u32,
783
784    /// The estimated time between when this process loop was called and
785    /// when the data will be delivered to the output device for playback.
786    ///
787    /// If the audio backend does not provide this information, then this
788    /// will be `None`.
789    pub process_to_playback_delay: Option<Duration>,
790
791    /// If the node has just been un-bypassed, then this will be `true`.
792    pub did_just_unbypass: bool,
793
794    /// Information about the musical transport.
795    ///
796    /// This will be `None` if no musical transport is currently active,
797    /// or if the current transport is currently paused.
798    #[cfg(feature = "musical_transport")]
799    pub transport_info: Option<TransportInfo>,
800}
801
802impl ProcInfo {
803    /// The current time of the audio clock at the first frame in this
804    /// processing block, equal to the total number of seconds of data that
805    /// have been processed since this Firewheel context was first started.
806    ///
807    /// Note, this value does *NOT* account for any output underflows
808    /// (underruns) that may have occurred.
809    ///
810    /// Note, generally this value will always count up, but there may be
811    /// a few edge cases that cause this value to be less than the previous
812    /// block, such as when the sample rate of the stream has been changed.
813    pub fn clock_seconds(&self) -> InstantSeconds {
814        self.clock_samples
815            .to_seconds(self.sample_rate, self.sample_rate_recip)
816    }
817
818    /// Get the current time of the audio clock in frames as a range for this
819    /// processing block.
820    pub fn clock_samples_range(&self) -> Range<InstantSamples> {
821        self.clock_samples..self.clock_samples + DurationSamples(self.frames as i64)
822    }
823
824    /// Get the current time of the audio clock in frames as a range for this
825    /// processing block.
826    pub fn clock_seconds_range(&self) -> Range<InstantSeconds> {
827        self.clock_seconds()
828            ..(self.clock_samples + DurationSamples(self.frames as i64))
829                .to_seconds(self.sample_rate, self.sample_rate_recip)
830    }
831
832    /// Get the playhead of the transport at the first frame in this processing
833    /// block.
834    ///
835    /// If there is no active transport, or if the transport is not currently
836    /// playing, then this will return `None`.
837    #[cfg(feature = "musical_transport")]
838    pub fn playhead(&self) -> Option<InstantMusical> {
839        self.transport_info.as_ref().and_then(|transport_info| {
840            transport_info
841                .start_clock_samples
842                .map(|start_clock_samples| {
843                    transport_info.transport.samples_to_musical(
844                        self.clock_samples,
845                        start_clock_samples,
846                        transport_info.speed_multiplier,
847                        self.sample_rate,
848                        self.sample_rate_recip,
849                    )
850                })
851        })
852    }
853
854    /// Get the playhead of the transport as a range for this processing
855    /// block.
856    ///
857    /// If there is no active transport, or if the transport is not currently
858    /// playing, then this will return `None`.
859    #[cfg(feature = "musical_transport")]
860    pub fn playhead_range(&self) -> Option<Range<InstantMusical>> {
861        self.transport_info.as_ref().and_then(|transport_info| {
862            transport_info
863                .start_clock_samples
864                .map(|start_clock_samples| {
865                    transport_info.transport.samples_to_musical(
866                        self.clock_samples,
867                        start_clock_samples,
868                        transport_info.speed_multiplier,
869                        self.sample_rate,
870                        self.sample_rate_recip,
871                    )
872                        ..transport_info.transport.samples_to_musical(
873                            self.clock_samples + DurationSamples(self.frames as i64),
874                            start_clock_samples,
875                            transport_info.speed_multiplier,
876                            self.sample_rate,
877                            self.sample_rate_recip,
878                        )
879                })
880        })
881    }
882
883    /// Returns `true` if there is a transport and that transport is playing,
884    /// `false` otherwise.
885    #[cfg(feature = "musical_transport")]
886    pub fn transport_is_playing(&self) -> bool {
887        self.transport_info
888            .as_ref()
889            .map(|t| t.playing())
890            .unwrap_or(false)
891    }
892
893    /// Converts the given musical time to the corresponding time in samples.
894    ///
895    /// If there is no musical transport or the transport is not currently playing,
896    /// then this will return `None`.
897    #[cfg(feature = "musical_transport")]
898    pub fn musical_to_samples(&self, musical: InstantMusical) -> Option<InstantSamples> {
899        self.transport_info.as_ref().and_then(|transport_info| {
900            transport_info
901                .start_clock_samples
902                .map(|start_clock_samples| {
903                    transport_info.transport.musical_to_samples(
904                        musical,
905                        start_clock_samples,
906                        transport_info.speed_multiplier,
907                        self.sample_rate,
908                    )
909                })
910        })
911    }
912}
913
914#[cfg(feature = "musical_transport")]
915#[derive(Debug, Clone, PartialEq)]
916pub struct TransportInfo {
917    /// The current transport.
918    pub transport: MusicalTransport,
919
920    /// The instant that `MusicaltTime::ZERO` occurred in units of
921    /// `ClockSamples`.
922    ///
923    /// If the transport is not currently playing, then this will be `None`.
924    pub start_clock_samples: Option<InstantSamples>,
925
926    /// The beats per minute at the first frame of this process block.
927    ///
928    /// (The `speed_multipler` has already been applied to this value.)
929    pub beats_per_minute: f64,
930
931    /// A multiplier for the playback speed of the transport. A value of `1.0`
932    /// means no change in speed, a value less than `1.0` means a decrease in
933    /// speed, and a value greater than `1.0` means an increase in speed.
934    pub speed_multiplier: f64,
935}
936
937#[cfg(feature = "musical_transport")]
938impl TransportInfo {
939    /// Whether or not the transport is currently playing (true) or paused
940    /// (false).
941    pub const fn playing(&self) -> bool {
942        self.start_clock_samples.is_some()
943    }
944}
945
946bitflags::bitflags! {
947    /// Flags indicating the current status of the audio stream
948    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
949    pub struct StreamStatus: u32 {
950        /// Some input data was discarded because of an overflow condition
951        /// at the audio driver.
952        const INPUT_OVERFLOW = 0b001;
953
954        /// The output buffer ran low, likely producing a break in the
955        /// output sound. (This is also known as an "underrun").
956        const OUTPUT_UNDERFLOW = 0b010;
957
958        /// The stream was closed (i.e. because a microphone was unplugged).
959        const CLOSED = 0b100;
960    }
961}
962
963/// The status of processing buffers in an audio node.
964#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
965pub enum ProcessStatus {
966    /// No output buffers were modified. If this is returned, then
967    /// the engine will automatically clear all output buffers
968    /// for you as efficiently as possible.
969    #[default]
970    ClearAllOutputs,
971    /// No output buffers were modified. If this is returned, then
972    /// the engine will automatically copy the input buffers to
973    /// their corresponding output buffers for you as efficiently
974    /// as possible.
975    Bypass,
976    /// All output buffers were filled with data.
977    ///
978    /// WARNING: The node must fill all audio audio output buffers
979    /// completely with data when returning this process status.
980    /// Failing to do so will result in audio glitches. If using
981    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
982    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
983    /// do not need to be filled with data.
984    OutputsModified,
985    /// All output buffers were filled with data. Additionally,
986    /// a constant/silence mask is provided for optimizations.
987    ///
988    /// WARNING: The node must fill all audio audio output buffers
989    /// completely with data when returning this process status.
990    /// Failing to do so will result in audio glitches. If using
991    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
992    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
993    /// do not need to be filled with data.
994    ///
995    /// WARNING: Incorrectly marking a channel as containing
996    /// silence/constant values when it doesn't will result in audio
997    /// glitches. Please take great care when using this, or
998    /// use [`ProcessStatus::OutputsModified`] instead.
999    OutputsModifiedWithMask(MaskType),
1000}
1001
1002impl ProcessStatus {
1003    /// All output buffers were filled with data. Additionally,
1004    /// a constant/silence mask is provided for optimizations.
1005    ///
1006    /// WARNING: The node must fill all audio audio output buffers
1007    /// completely with data when returning this process status.
1008    /// Failing to do so will result in audio glitches. If using
1009    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
1010    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
1011    /// do not need to be filled with data.
1012    ///
1013    /// WARNING: Incorrectly marking a channel as containing
1014    /// silence when it doesn't will result in audio glitches.
1015    /// Please take great care when using this, or use
1016    /// [`ProcessStatus::OutputsModified`] instead.
1017    pub const fn outputs_modified_with_silence_mask(mask: SilenceMask) -> Self {
1018        Self::OutputsModifiedWithMask(MaskType::Silence(mask))
1019    }
1020
1021    /// All output buffers were filled with data. Additionally,
1022    /// a constant/silence mask is provided for optimizations.
1023    ///
1024    /// WARNING: The node must fill all audio audio output buffers
1025    /// completely with data when returning this process status.
1026    /// Failing to do so will result in audio glitches. If using
1027    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
1028    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
1029    /// do not need to be filled with data.
1030    ///
1031    /// WARNING: Incorrectly marking a channel as containing
1032    /// constant values when it doesn't will result in audio
1033    /// glitches. Please take great care when using this, or use
1034    /// [`ProcessStatus::OutputsModified`] instead.
1035    pub const fn outputs_modified_with_constant_mask(mask: ConstantMask) -> Self {
1036        Self::OutputsModifiedWithMask(MaskType::Constant(mask))
1037    }
1038}
1039
1040/// A type-erased store accessible to all [`AudioNodeProcessor`]s.
1041pub struct ProcStore(HashMap<TypeId, Box<dyn Any + Send>>);
1042
1043impl ProcStore {
1044    pub fn with_capacity(capacity: usize) -> Self {
1045        let mut h = HashMap::default();
1046        h.reserve(capacity);
1047        Self(h)
1048    }
1049
1050    /// Insert a new resource into the store.
1051    ///
1052    /// If a resource with this `TypeID` already exists, then an error will
1053    /// be returned instead.
1054    pub fn insert<S: Send + 'static>(&mut self, resource: S) -> Result<(), S> {
1055        if let Entry::Vacant(e) = self.0.entry(TypeId::of::<S>()) {
1056            e.insert(Box::new(resource));
1057            Ok(())
1058        } else {
1059            Err(resource)
1060        }
1061    }
1062
1063    /// Insert a new already type-erased resource into the store.
1064    ///
1065    /// If a resource with this `TypeID` already exists, then an error will
1066    /// be returned instead.
1067    pub fn insert_any<S: Send + 'static>(
1068        &mut self,
1069        resource: Box<dyn Any + Send>,
1070        type_id: TypeId,
1071    ) -> Result<(), Box<dyn Any + Send>> {
1072        if let Entry::Vacant(e) = self.0.entry(type_id) {
1073            e.insert(resource);
1074            Ok(())
1075        } else {
1076            Err(resource)
1077        }
1078    }
1079
1080    /// Get the entry for the given resource.
1081    pub fn entry<'a, S: Send + 'static>(&'a mut self) -> ProcStoreEntry<'a, S> {
1082        ProcStoreEntry {
1083            boxed_entry: self.0.entry(TypeId::of::<S>()),
1084            type_: PhantomData,
1085        }
1086    }
1087
1088    /// Returns `true` if a resource with the given `TypeID` exists in this
1089    /// store.
1090    pub fn contains<S: Send + 'static>(&self) -> bool {
1091        self.0.contains_key(&TypeId::of::<S>())
1092    }
1093
1094    /// Get an immutable reference to a resource in the store.
1095    ///
1096    /// # Panics
1097    /// Panics if the given resource does not exist.
1098    pub fn get<S: Send + 'static>(&self) -> &S {
1099        self.try_get().unwrap()
1100    }
1101
1102    /// Get a mutable reference to a resource in the store.
1103    ///
1104    /// # Panics
1105    /// Panics if the given resource does not exist.
1106    pub fn get_mut<S: Send + 'static>(&mut self) -> &mut S {
1107        self.try_get_mut().unwrap()
1108    }
1109
1110    /// Get an immutable reference to a resource in the store.
1111    ///
1112    /// Returns `None` if the given resource does not exist.
1113    pub fn try_get<S: Send + 'static>(&self) -> Option<&S> {
1114        self.0
1115            .get(&TypeId::of::<S>())
1116            .map(|s| s.downcast_ref().unwrap())
1117    }
1118
1119    /// Get a mutable reference to a resource in the store.
1120    ///
1121    /// Returns `None` if the given resource does not exist.
1122    pub fn try_get_mut<S: Send + 'static>(&mut self) -> Option<&mut S> {
1123        self.0
1124            .get_mut(&TypeId::of::<S>())
1125            .map(|s| s.downcast_mut().unwrap())
1126    }
1127}
1128
1129pub struct ProcStoreEntry<'a, S: Send + 'static> {
1130    pub boxed_entry: Entry<'a, TypeId, Box<dyn Any + Send>>,
1131    type_: PhantomData<S>,
1132}
1133
1134impl<'a, S: Send + 'static> ProcStoreEntry<'a, S> {
1135    pub fn or_insert_with(self, default: impl FnOnce() -> S) -> &'a mut S {
1136        self.boxed_entry
1137            .or_insert_with(|| Box::new((default)()))
1138            .downcast_mut()
1139            .unwrap()
1140    }
1141
1142    pub fn or_insert_with_any(self, default: impl FnOnce() -> Box<dyn Any + Send>) -> &'a mut S {
1143        self.boxed_entry
1144            .or_insert_with(default)
1145            .downcast_mut()
1146            .unwrap()
1147    }
1148
1149    pub fn and_modify(self, f: impl FnOnce(&mut S)) -> Self {
1150        let entry = self
1151            .boxed_entry
1152            .and_modify(|e| (f)(e.downcast_mut().unwrap()));
1153        Self {
1154            boxed_entry: entry,
1155            type_: PhantomData,
1156        }
1157    }
1158}