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    /// The instant that the last [`NodeEventType::Marker`] event that
795    /// was sent to this node occured at.
796    ///
797    /// If a [`NodeEventType::Marker`] was never sent to this node, then
798    /// this will be `InstantSamples(0)`.
799    #[cfg(feature = "scheduled_events")]
800    pub last_marker_instant: InstantSamples,
801
802    /// Information about the musical transport.
803    ///
804    /// This will be `None` if no musical transport is currently active,
805    /// or if the current transport is currently paused.
806    #[cfg(feature = "musical_transport")]
807    pub transport_info: Option<TransportInfo>,
808}
809
810impl ProcInfo {
811    /// The current time of the audio clock at the first frame in this
812    /// processing block, equal to the total number of seconds of data that
813    /// have been processed since this Firewheel context was first started.
814    ///
815    /// Note, this value does *NOT* account for any output underflows
816    /// (underruns) that may have occurred.
817    ///
818    /// Note, generally this value will always count up, but there may be
819    /// a few edge cases that cause this value to be less than the previous
820    /// block, such as when the sample rate of the stream has been changed.
821    pub fn clock_seconds(&self) -> InstantSeconds {
822        self.clock_samples
823            .to_seconds(self.sample_rate, self.sample_rate_recip)
824    }
825
826    /// Get the current time of the audio clock in frames as a range for this
827    /// processing block.
828    pub fn clock_samples_range(&self) -> Range<InstantSamples> {
829        self.clock_samples..self.clock_samples + DurationSamples(self.frames as i64)
830    }
831
832    /// Get the current time of the audio clock in frames as a range for this
833    /// processing block.
834    pub fn clock_seconds_range(&self) -> Range<InstantSeconds> {
835        self.clock_seconds()
836            ..(self.clock_samples + DurationSamples(self.frames as i64))
837                .to_seconds(self.sample_rate, self.sample_rate_recip)
838    }
839
840    /// Get the playhead of the transport at the first frame in this processing
841    /// block.
842    ///
843    /// If there is no active transport, or if the transport is not currently
844    /// playing, then this will return `None`.
845    #[cfg(feature = "musical_transport")]
846    pub fn playhead(&self) -> Option<InstantMusical> {
847        self.transport_info.as_ref().and_then(|transport_info| {
848            transport_info
849                .start_clock_samples
850                .map(|start_clock_samples| {
851                    transport_info.transport.samples_to_musical(
852                        self.clock_samples,
853                        start_clock_samples,
854                        transport_info.speed_multiplier,
855                        self.sample_rate,
856                        self.sample_rate_recip,
857                    )
858                })
859        })
860    }
861
862    /// Get the playhead of the transport as a range for this processing
863    /// block.
864    ///
865    /// If there is no active transport, or if the transport is not currently
866    /// playing, then this will return `None`.
867    #[cfg(feature = "musical_transport")]
868    pub fn playhead_range(&self) -> Option<Range<InstantMusical>> {
869        self.transport_info.as_ref().and_then(|transport_info| {
870            transport_info
871                .start_clock_samples
872                .map(|start_clock_samples| {
873                    transport_info.transport.samples_to_musical(
874                        self.clock_samples,
875                        start_clock_samples,
876                        transport_info.speed_multiplier,
877                        self.sample_rate,
878                        self.sample_rate_recip,
879                    )
880                        ..transport_info.transport.samples_to_musical(
881                            self.clock_samples + DurationSamples(self.frames as i64),
882                            start_clock_samples,
883                            transport_info.speed_multiplier,
884                            self.sample_rate,
885                            self.sample_rate_recip,
886                        )
887                })
888        })
889    }
890
891    /// Returns `true` if there is a transport and that transport is playing,
892    /// `false` otherwise.
893    #[cfg(feature = "musical_transport")]
894    pub fn transport_is_playing(&self) -> bool {
895        self.transport_info
896            .as_ref()
897            .map(|t| t.playing())
898            .unwrap_or(false)
899    }
900
901    /// Converts the given musical time to the corresponding time in samples.
902    ///
903    /// If there is no musical transport or the transport is not currently playing,
904    /// then this will return `None`.
905    #[cfg(feature = "musical_transport")]
906    pub fn musical_to_samples(&self, musical: InstantMusical) -> Option<InstantSamples> {
907        self.transport_info.as_ref().and_then(|transport_info| {
908            transport_info
909                .start_clock_samples
910                .map(|start_clock_samples| {
911                    transport_info.transport.musical_to_samples(
912                        musical,
913                        start_clock_samples,
914                        transport_info.speed_multiplier,
915                        self.sample_rate,
916                    )
917                })
918        })
919    }
920}
921
922#[cfg(feature = "musical_transport")]
923#[derive(Debug, Clone, PartialEq)]
924pub struct TransportInfo {
925    /// The current transport.
926    pub transport: MusicalTransport,
927
928    /// The instant that `MusicaltTime::ZERO` occurred in units of
929    /// `ClockSamples`.
930    ///
931    /// If the transport is not currently playing, then this will be `None`.
932    pub start_clock_samples: Option<InstantSamples>,
933
934    /// The beats per minute at the first frame of this process block.
935    ///
936    /// (The `speed_multipler` has already been applied to this value.)
937    pub beats_per_minute: f64,
938
939    /// A multiplier for the playback speed of the transport. A value of `1.0`
940    /// means no change in speed, a value less than `1.0` means a decrease in
941    /// speed, and a value greater than `1.0` means an increase in speed.
942    pub speed_multiplier: f64,
943}
944
945#[cfg(feature = "musical_transport")]
946impl TransportInfo {
947    /// Whether or not the transport is currently playing (true) or paused
948    /// (false).
949    pub const fn playing(&self) -> bool {
950        self.start_clock_samples.is_some()
951    }
952}
953
954bitflags::bitflags! {
955    /// Flags indicating the current status of the audio stream
956    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
957    pub struct StreamStatus: u32 {
958        /// Some input data was discarded because of an overflow condition
959        /// at the audio driver.
960        const INPUT_OVERFLOW = 0b001;
961
962        /// The output buffer ran low, likely producing a break in the
963        /// output sound. (This is also known as an "underrun").
964        const OUTPUT_UNDERFLOW = 0b010;
965
966        /// The stream was closed (i.e. because a microphone was unplugged).
967        const CLOSED = 0b100;
968    }
969}
970
971/// The status of processing buffers in an audio node.
972#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
973pub enum ProcessStatus {
974    /// No output buffers were modified. If this is returned, then
975    /// the engine will automatically clear all output buffers
976    /// for you as efficiently as possible.
977    #[default]
978    ClearAllOutputs,
979    /// No output buffers were modified. If this is returned, then
980    /// the engine will automatically copy the input buffers to
981    /// their corresponding output buffers for you as efficiently
982    /// as possible.
983    Bypass,
984    /// All output buffers were filled with data.
985    ///
986    /// WARNING: The node must fill all audio audio output buffers
987    /// completely with data when returning this process status.
988    /// Failing to do so will result in audio glitches. If using
989    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
990    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
991    /// do not need to be filled with data.
992    OutputsModified,
993    /// All output buffers were filled with data. Additionally,
994    /// a constant/silence mask is provided for optimizations.
995    ///
996    /// WARNING: The node must fill all audio audio output buffers
997    /// completely with data when returning this process status.
998    /// Failing to do so will result in audio glitches. If using
999    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
1000    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
1001    /// do not need to be filled with data.
1002    ///
1003    /// WARNING: Incorrectly marking a channel as containing
1004    /// silence/constant values when it doesn't will result in audio
1005    /// glitches. Please take great care when using this, or
1006    /// use [`ProcessStatus::OutputsModified`] instead.
1007    OutputsModifiedWithMask(MaskType),
1008}
1009
1010impl ProcessStatus {
1011    /// All output buffers were filled with data. Additionally,
1012    /// a constant/silence mask is provided for optimizations.
1013    ///
1014    /// WARNING: The node must fill all audio audio output buffers
1015    /// completely with data when returning this process status.
1016    /// Failing to do so will result in audio glitches. If using
1017    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
1018    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
1019    /// do not need to be filled with data.
1020    ///
1021    /// WARNING: Incorrectly marking a channel as containing
1022    /// silence when it doesn't will result in audio glitches.
1023    /// Please take great care when using this, or use
1024    /// [`ProcessStatus::OutputsModified`] instead.
1025    pub const fn outputs_modified_with_silence_mask(mask: SilenceMask) -> Self {
1026        Self::OutputsModifiedWithMask(MaskType::Silence(mask))
1027    }
1028
1029    /// All output buffers were filled with data. Additionally,
1030    /// a constant/silence mask is provided for optimizations.
1031    ///
1032    /// WARNING: The node must fill all audio audio output buffers
1033    /// completely with data when returning this process status.
1034    /// Failing to do so will result in audio glitches. If using
1035    /// [`AudioNodeInfo::in_place_buffers`], then the output buffers
1036    /// in the range `[0..num_inputs_in_config.min(num_outputs_in_config)]`
1037    /// do not need to be filled with data.
1038    ///
1039    /// WARNING: Incorrectly marking a channel as containing
1040    /// constant values when it doesn't will result in audio
1041    /// glitches. Please take great care when using this, or use
1042    /// [`ProcessStatus::OutputsModified`] instead.
1043    pub const fn outputs_modified_with_constant_mask(mask: ConstantMask) -> Self {
1044        Self::OutputsModifiedWithMask(MaskType::Constant(mask))
1045    }
1046}
1047
1048/// A type-erased store accessible to all [`AudioNodeProcessor`]s.
1049pub struct ProcStore(HashMap<TypeId, Box<dyn Any + Send>>);
1050
1051impl ProcStore {
1052    pub fn with_capacity(capacity: usize) -> Self {
1053        let mut h = HashMap::default();
1054        h.reserve(capacity);
1055        Self(h)
1056    }
1057
1058    /// Insert a new resource into the store.
1059    ///
1060    /// If a resource with this `TypeID` already exists, then an error will
1061    /// be returned instead.
1062    pub fn insert<S: Send + 'static>(&mut self, resource: S) -> Result<(), S> {
1063        if let Entry::Vacant(e) = self.0.entry(TypeId::of::<S>()) {
1064            e.insert(Box::new(resource));
1065            Ok(())
1066        } else {
1067            Err(resource)
1068        }
1069    }
1070
1071    /// Insert a new already type-erased resource into the store.
1072    ///
1073    /// If a resource with this `TypeID` already exists, then an error will
1074    /// be returned instead.
1075    pub fn insert_any<S: Send + 'static>(
1076        &mut self,
1077        resource: Box<dyn Any + Send>,
1078        type_id: TypeId,
1079    ) -> Result<(), Box<dyn Any + Send>> {
1080        if let Entry::Vacant(e) = self.0.entry(type_id) {
1081            e.insert(resource);
1082            Ok(())
1083        } else {
1084            Err(resource)
1085        }
1086    }
1087
1088    /// Get the entry for the given resource.
1089    pub fn entry<'a, S: Send + 'static>(&'a mut self) -> ProcStoreEntry<'a, S> {
1090        ProcStoreEntry {
1091            boxed_entry: self.0.entry(TypeId::of::<S>()),
1092            type_: PhantomData,
1093        }
1094    }
1095
1096    /// Returns `true` if a resource with the given `TypeID` exists in this
1097    /// store.
1098    pub fn contains<S: Send + 'static>(&self) -> bool {
1099        self.0.contains_key(&TypeId::of::<S>())
1100    }
1101
1102    /// Get an immutable reference to a resource in the store.
1103    ///
1104    /// # Panics
1105    /// Panics if the given resource does not exist.
1106    pub fn get<S: Send + 'static>(&self) -> &S {
1107        self.try_get().unwrap()
1108    }
1109
1110    /// Get a mutable reference to a resource in the store.
1111    ///
1112    /// # Panics
1113    /// Panics if the given resource does not exist.
1114    pub fn get_mut<S: Send + 'static>(&mut self) -> &mut S {
1115        self.try_get_mut().unwrap()
1116    }
1117
1118    /// Get an immutable reference to a resource in the store.
1119    ///
1120    /// Returns `None` if the given resource does not exist.
1121    pub fn try_get<S: Send + 'static>(&self) -> Option<&S> {
1122        self.0
1123            .get(&TypeId::of::<S>())
1124            .map(|s| s.downcast_ref().unwrap())
1125    }
1126
1127    /// Get a mutable reference to a resource in the store.
1128    ///
1129    /// Returns `None` if the given resource does not exist.
1130    pub fn try_get_mut<S: Send + 'static>(&mut self) -> Option<&mut S> {
1131        self.0
1132            .get_mut(&TypeId::of::<S>())
1133            .map(|s| s.downcast_mut().unwrap())
1134    }
1135}
1136
1137pub struct ProcStoreEntry<'a, S: Send + 'static> {
1138    pub boxed_entry: Entry<'a, TypeId, Box<dyn Any + Send>>,
1139    type_: PhantomData<S>,
1140}
1141
1142impl<'a, S: Send + 'static> ProcStoreEntry<'a, S> {
1143    pub fn or_insert_with(self, default: impl FnOnce() -> S) -> &'a mut S {
1144        self.boxed_entry
1145            .or_insert_with(|| Box::new((default)()))
1146            .downcast_mut()
1147            .unwrap()
1148    }
1149
1150    pub fn or_insert_with_any(self, default: impl FnOnce() -> Box<dyn Any + Send>) -> &'a mut S {
1151        self.boxed_entry
1152            .or_insert_with(default)
1153            .downcast_mut()
1154            .unwrap()
1155    }
1156
1157    pub fn and_modify(self, f: impl FnOnce(&mut S)) -> Self {
1158        let entry = self
1159            .boxed_entry
1160            .and_modify(|e| (f)(e.downcast_mut().unwrap()));
1161        Self {
1162            boxed_entry: entry,
1163            type_: PhantomData,
1164        }
1165    }
1166}