firewheel_core/
node.rs

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