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 /// * `info` - Information about this processing block.
429 /// * `buffers` - The buffers of data to process.
430 /// * `events` - A list of events for this node to process.
431 /// * `extra` - Additional buffers and utilities.
432 ///
433 /// WARNING: Audio nodes *MUST* either completely fill all output buffers
434 /// with data, or return [`ProcessStatus::ClearAllOutputs`]/[`ProcessStatus::Bypass`].
435 /// Failing to do this will result in audio glitches.
436 fn process(
437 &mut self,
438 info: &ProcInfo,
439 buffers: ProcBuffers,
440 events: &mut ProcEvents,
441 extra: &mut ProcExtra,
442 ) -> ProcessStatus;
443
444 /// Called when the audio stream has been stopped.
445 fn stream_stopped(&mut self, context: &mut ProcStreamCtx) {
446 let _ = context;
447 }
448
449 /// Called when a new audio stream has been started after a previous
450 /// call to [`AudioNodeProcessor::stream_stopped`].
451 ///
452 /// Note, this method gets called on the main thread, not the audio
453 /// thread. So it is safe to allocate/deallocate here.
454 fn new_stream(&mut self, stream_info: &StreamInfo, context: &mut ProcStreamCtx) {
455 let _ = stream_info;
456 let _ = context;
457 }
458}
459
460impl AudioNodeProcessor for Box<dyn AudioNodeProcessor> {
461 fn new_stream(&mut self, stream_info: &StreamInfo, context: &mut ProcStreamCtx) {
462 self.as_mut().new_stream(stream_info, context)
463 }
464 fn process(
465 &mut self,
466 info: &ProcInfo,
467 buffers: ProcBuffers,
468 events: &mut ProcEvents,
469 extra: &mut ProcExtra,
470 ) -> ProcessStatus {
471 self.as_mut().process(info, buffers, events, extra)
472 }
473 fn stream_stopped(&mut self, context: &mut ProcStreamCtx) {
474 self.as_mut().stream_stopped(context)
475 }
476}
477
478pub struct ProcStreamCtx<'a> {
479 pub store: &'a mut ProcStore,
480 pub logger: &'a mut RealtimeLogger,
481}
482
483pub const NUM_SCRATCH_BUFFERS: usize = 8;
484
485/// The buffers used in [`AudioNodeProcessor::process`]
486#[derive(Debug)]
487pub struct ProcBuffers<'a, 'b> {
488 /// The audio input buffers.
489 ///
490 /// The number of channels will always equal the [`ChannelConfig::num_inputs`]
491 /// value that was returned in [`AudioNode::info`].
492 ///
493 /// Each channel slice will have a length of [`ProcInfo::frames`].
494 pub inputs: &'a [&'b [f32]],
495
496 /// The audio output buffers.
497 ///
498 /// WARNING: The node *MUST* either completely fill all output buffers
499 /// with data, or return [`ProcessStatus::ClearAllOutputs`]/[`ProcessStatus::Bypass`].
500 /// Failing to do this will result in audio glitches.
501 ///
502 /// The number of channels will always equal the [`ChannelConfig::num_outputs`]
503 /// value that was returned in [`AudioNode::info`].
504 ///
505 /// Each channel slice will have a length of [`ProcInfo::frames`].
506 ///
507 /// These buffers may contain junk data.
508 pub outputs: &'a mut [&'b mut [f32]],
509}
510
511impl<'a, 'b> ProcBuffers<'a, 'b> {
512 /// Thouroughly checks if all output buffers contain silence (as in all
513 /// samples have an absolute amplitude less than or equal to `amp_epsilon`).
514 ///
515 /// If all buffers are silent, then [`ProcessStatus::ClearAllOutputs`] will
516 /// be returned. Otherwise, [`ProcessStatus::OutputsModified`] will be
517 /// returned.
518 pub fn check_for_silence_on_outputs(&self, amp_epsilon: f32) -> ProcessStatus {
519 let mut silent = true;
520 for buffer in self.outputs.iter() {
521 if !is_buffer_silent(buffer, amp_epsilon) {
522 silent = false;
523 break;
524 }
525 }
526
527 if silent {
528 ProcessStatus::ClearAllOutputs
529 } else {
530 ProcessStatus::OutputsModified
531 }
532 }
533}
534
535/// Extra buffers and utilities for [`AudioNodeProcessor::process`]
536pub struct ProcExtra {
537 /// A list of extra scratch buffers that can be used for processing.
538 /// This removes the need for nodes to allocate their own scratch buffers.
539 /// Each buffer has a length of [`StreamInfo::max_block_frames`]. These
540 /// buffers are shared across all nodes, so assume that they contain junk
541 /// data.
542 pub scratch_buffers: ChannelBuffer<f32, NUM_SCRATCH_BUFFERS>,
543
544 /// A buffer of values that linearly ramp up/down between `0.0` and `1.0`
545 /// which can be used to implement efficient declicking when
546 /// pausing/resuming/stopping.
547 pub declick_values: DeclickValues,
548
549 /// A realtime-safe logger helper.
550 pub logger: RealtimeLogger,
551
552 /// A type-erased store accessible to all [`AudioNodeProcessor`]s.
553 pub store: ProcStore,
554}
555
556/// Information for [`AudioNodeProcessor::process`]
557#[derive(Debug)]
558pub struct ProcInfo {
559 /// The number of frames (samples in a single channel of audio) in
560 /// this processing block.
561 ///
562 /// Not to be confused with video frames.
563 pub frames: usize,
564
565 /// An optional optimization hint on which input channels contain
566 /// all zeros (silence). The first bit (`0x1`) is the first channel,
567 /// the second bit is the second channel, and so on.
568 pub in_silence_mask: SilenceMask,
569
570 /// An optional optimization hint on which output channels contain
571 /// all zeros (silence). The first bit (`0x1`) is the first channel,
572 /// the second bit is the second channel, and so on.
573 pub out_silence_mask: SilenceMask,
574
575 /// An optional optimization hint on which input channels have all
576 /// samples set to the same value. The first bit (`0x1`) is the
577 /// first channel, the second bit is the second channel, and so on.
578 ///
579 /// This can be useful for nodes that use audio buffers as CV
580 /// (control voltage) ports.
581 pub in_constant_mask: ConstantMask,
582
583 /// An optional optimization hint on which input channels have all
584 /// samples set to the same value. The first bit (`0x1`) is the
585 /// first channel, the second bit is the second channel, and so on.
586 ///
587 /// This can be useful for nodes that use audio buffers as CV
588 /// (control voltage) ports.
589 pub out_constant_mask: ConstantMask,
590
591 /// An optional hint on which input channels are connected to other
592 /// nodes in the graph.
593 pub in_connected_mask: ConnectedMask,
594
595 /// An optional hint on which output channels are connected to other
596 /// nodes in the graph.
597 pub out_connected_mask: ConnectedMask,
598
599 /// If the previous processing block had all output buffers silent
600 /// (or if this is the first processing block), then this will be
601 /// `true`. Otherwise, this will be `false`.
602 pub prev_output_was_silent: bool,
603
604 /// The sample rate of the audio stream in samples per second.
605 pub sample_rate: NonZeroU32,
606
607 /// The reciprocal of the sample rate. This can be used to avoid a
608 /// division and improve performance.
609 pub sample_rate_recip: f64,
610
611 /// The current time of the audio clock at the first frame in this
612 /// processing block, equal to the total number of frames (samples in
613 /// a single channel of audio) that have been processed since this
614 /// Firewheel context was first started.
615 ///
616 /// Note, this value does *NOT* account for any output underflows
617 /// (underruns) that may have occured.
618 ///
619 /// Note, generally this value will always count up, but there may be
620 /// a few edge cases that cause this value to be less than the previous
621 /// block, such as when the sample rate of the stream has been changed.
622 pub clock_samples: InstantSamples,
623
624 /// The duration between when the stream was started an when the
625 /// Firewheel processor's `process` method was called.
626 ///
627 /// Note, this clock is not as accurate as the audio clock.
628 pub duration_since_stream_start: Duration,
629
630 /// Flags indicating the current status of the audio stream
631 pub stream_status: StreamStatus,
632
633 /// If an output underflow (underrun) occured, then this will contain
634 /// an estimate for the number of frames (samples in a single channel
635 /// of audio) that were dropped.
636 ///
637 /// This can be used to correct the timing of events if desired.
638 ///
639 /// Note, this is just an estimate, and may not always be perfectly
640 /// accurate.
641 ///
642 /// If an underrun did not occur, then this will be `0`.
643 pub dropped_frames: u32,
644
645 /// Information about the musical transport.
646 ///
647 /// This will be `None` if no musical transport is currently active,
648 /// or if the current transport is currently paused.
649 #[cfg(feature = "musical_transport")]
650 pub transport_info: Option<TransportInfo>,
651}
652
653impl ProcInfo {
654 /// The current time of the audio clock at the first frame in this
655 /// processing block, equal to the total number of seconds of data that
656 /// have been processed since this Firewheel context was first started.
657 ///
658 /// Note, this value does *NOT* account for any output underflows
659 /// (underruns) that may have occured.
660 ///
661 /// Note, generally this value will always count up, but there may be
662 /// a few edge cases that cause this value to be less than the previous
663 /// block, such as when the sample rate of the stream has been changed.
664 pub fn clock_seconds(&self) -> InstantSeconds {
665 self.clock_samples
666 .to_seconds(self.sample_rate, self.sample_rate_recip)
667 }
668
669 /// Get the current time of the audio clock in frames as a range for this
670 /// processing block.
671 pub fn clock_samples_range(&self) -> Range<InstantSamples> {
672 self.clock_samples..self.clock_samples + DurationSamples(self.frames as i64)
673 }
674
675 /// Get the current time of the audio clock in frames as a range for this
676 /// processing block.
677 pub fn clock_seconds_range(&self) -> Range<InstantSeconds> {
678 self.clock_seconds()
679 ..(self.clock_samples + DurationSamples(self.frames as i64))
680 .to_seconds(self.sample_rate, self.sample_rate_recip)
681 }
682
683 /// Get the playhead of the transport at the first frame in this processing
684 /// block.
685 ///
686 /// If there is no active transport, or if the transport is not currently
687 /// playing, then this will return `None`.
688 #[cfg(feature = "musical_transport")]
689 pub fn playhead(&self) -> Option<InstantMusical> {
690 self.transport_info.as_ref().and_then(|transport_info| {
691 transport_info
692 .start_clock_samples
693 .map(|start_clock_samples| {
694 transport_info.transport.samples_to_musical(
695 self.clock_samples,
696 start_clock_samples,
697 transport_info.speed_multiplier,
698 self.sample_rate,
699 self.sample_rate_recip,
700 )
701 })
702 })
703 }
704
705 /// Get the playhead of the transport as a range for this processing
706 /// block.
707 ///
708 /// If there is no active transport, or if the transport is not currently
709 /// playing, then this will return `None`.
710 #[cfg(feature = "musical_transport")]
711 pub fn playhead_range(&self) -> Option<Range<InstantMusical>> {
712 self.transport_info.as_ref().and_then(|transport_info| {
713 transport_info
714 .start_clock_samples
715 .map(|start_clock_samples| {
716 transport_info.transport.samples_to_musical(
717 self.clock_samples,
718 start_clock_samples,
719 transport_info.speed_multiplier,
720 self.sample_rate,
721 self.sample_rate_recip,
722 )
723 ..transport_info.transport.samples_to_musical(
724 self.clock_samples + DurationSamples(self.frames as i64),
725 start_clock_samples,
726 transport_info.speed_multiplier,
727 self.sample_rate,
728 self.sample_rate_recip,
729 )
730 })
731 })
732 }
733
734 /// Returns `true` if there is a transport and that transport is playing,
735 /// `false` otherwise.
736 #[cfg(feature = "musical_transport")]
737 pub fn transport_is_playing(&self) -> bool {
738 self.transport_info
739 .as_ref()
740 .map(|t| t.playing())
741 .unwrap_or(false)
742 }
743
744 /// Converts the given musical time to the corresponding time in samples.
745 ///
746 /// If there is no musical transport or the transport is not currently playing,
747 /// then this will return `None`.
748 #[cfg(feature = "musical_transport")]
749 pub fn musical_to_samples(&self, musical: InstantMusical) -> Option<InstantSamples> {
750 self.transport_info.as_ref().and_then(|transport_info| {
751 transport_info
752 .start_clock_samples
753 .map(|start_clock_samples| {
754 transport_info.transport.musical_to_samples(
755 musical,
756 start_clock_samples,
757 transport_info.speed_multiplier,
758 self.sample_rate,
759 )
760 })
761 })
762 }
763}
764
765#[cfg(feature = "musical_transport")]
766#[derive(Debug, Clone, PartialEq)]
767pub struct TransportInfo {
768 /// The current transport.
769 pub transport: MusicalTransport,
770
771 /// The instant that `MusicaltTime::ZERO` occured in units of
772 /// `ClockSamples`.
773 ///
774 /// If the transport is not currently playing, then this will be `None`.
775 pub start_clock_samples: Option<InstantSamples>,
776
777 /// The beats per minute at the first frame of this process block.
778 ///
779 /// (The `speed_multipler` has already been applied to this value.)
780 pub beats_per_minute: f64,
781
782 /// A multiplier for the playback speed of the transport. A value of `1.0`
783 /// means no change in speed, a value less than `1.0` means a decrease in
784 /// speed, and a value greater than `1.0` means an increase in speed.
785 pub speed_multiplier: f64,
786}
787
788#[cfg(feature = "musical_transport")]
789impl TransportInfo {
790 /// Whether or not the transport is currently playing (true) or paused
791 /// (false).
792 pub const fn playing(&self) -> bool {
793 self.start_clock_samples.is_some()
794 }
795}
796
797bitflags::bitflags! {
798 /// Flags indicating the current status of the audio stream
799 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
800 pub struct StreamStatus: u32 {
801 /// Some input data was discarded because of an overflow condition
802 /// at the audio driver.
803 const INPUT_OVERFLOW = 0b01;
804
805 /// The output buffer ran low, likely producing a break in the
806 /// output sound. (This is also known as an "underrun").
807 const OUTPUT_UNDERFLOW = 0b10;
808 }
809}
810
811/// The status of processing buffers in an audio node.
812#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
813pub enum ProcessStatus {
814 /// No output buffers were modified. If this is returned, then
815 /// the engine will automatically clear all output buffers
816 /// for you as efficiently as possible.
817 #[default]
818 ClearAllOutputs,
819 /// No output buffers were modified. If this is returned, then
820 /// the engine will automatically copy the input buffers to
821 /// their corresponding output buffers for you as efficiently
822 /// as possible.
823 Bypass,
824 /// All output buffers were filled with data.
825 ///
826 /// WARNING: The node must fill all audio audio output buffers
827 /// completely with data when returning this process status.
828 /// Failing to do so will result in audio glitches.
829 OutputsModified,
830 /// All output buffers were filled with data. Additionally,
831 /// a constant/silence mask is provided for optimizations.
832 ///
833 /// WARNING: The node must fill all audio audio output buffers
834 /// completely with data when returning this process status.
835 /// Failing to do so will result in audio glitches.
836 ///
837 /// WARNING: Incorrectly marking a channel as containing
838 /// silence/constant values when it doesn't will result in audio
839 /// glitches. Please take great care when using this, or
840 /// use [`ProcessStatus::OutputsModified`] instead.
841 OutputsModifiedWithMask(MaskType),
842}
843
844impl ProcessStatus {
845 /// All output buffers were filled with data. Additionally,
846 /// a constant/silence mask is provided for optimizations.
847 ///
848 /// WARNING: The node must fill all audio audio output buffers
849 /// completely with data when returning this process status.
850 /// Failing to do so will result in audio glitches.
851 ///
852 /// WARNING: Incorrectly marking a channel as containing
853 /// silence when it doesn't will result in audio glitches.
854 /// Please take great care when using this, or use
855 /// [`ProcessStatus::OutputsModified`] instead.
856 pub const fn outputs_modified_with_silence_mask(mask: SilenceMask) -> Self {
857 Self::OutputsModifiedWithMask(MaskType::Silence(mask))
858 }
859
860 /// All output buffers were filled with data. Additionally,
861 /// a constant/silence mask is provided for optimizations.
862 ///
863 /// WARNING: The node must fill all audio audio output buffers
864 /// completely with data when returning this process status.
865 /// Failing to do so will result in audio glitches.
866 ///
867 /// WARNING: Incorrectly marking a channel as containing
868 /// constant values when it doesn't will result in audio
869 /// glitches. Please take great care when using this, or use
870 /// [`ProcessStatus::OutputsModified`] instead.
871 pub const fn outputs_modified_with_constant_mask(mask: ConstantMask) -> Self {
872 Self::OutputsModifiedWithMask(MaskType::Constant(mask))
873 }
874}
875
876/// A type-erased store accessible to all [`AudioNodeProcessor`]s.
877pub struct ProcStore(HashMap<TypeId, Box<dyn Any + Send>>);
878
879impl ProcStore {
880 pub fn with_capacity(capacity: usize) -> Self {
881 let mut h = HashMap::default();
882 h.reserve(capacity);
883 Self(h)
884 }
885
886 /// Insert a new resource into the store.
887 ///
888 /// If a resource with this `TypeID` already exists, then an error will
889 /// be returned instead.
890 pub fn insert<S: Send + 'static>(&mut self, resource: S) -> Result<(), S> {
891 if self.0.contains_key(&TypeId::of::<S>()) {
892 Err(resource)
893 } else {
894 self.0.insert(TypeId::of::<S>(), Box::new(resource));
895 Ok(())
896 }
897 }
898
899 /// Insert a new already type-erased resource into the store.
900 ///
901 /// If a resource with this `TypeID` already exists, then an error will
902 /// be returned instead.
903 pub fn insert_any<S: Send + 'static>(
904 &mut self,
905 resource: Box<dyn Any + Send>,
906 type_id: TypeId,
907 ) -> Result<(), Box<dyn Any + Send>> {
908 if self.0.contains_key(&type_id) {
909 Err(resource)
910 } else {
911 self.0.insert(type_id, resource);
912 Ok(())
913 }
914 }
915
916 /// Get the entry for the given resource.
917 pub fn entry<'a, S: Send + 'static>(&'a mut self) -> ProcStoreEntry<'a, S> {
918 ProcStoreEntry {
919 boxed_entry: self.0.entry(TypeId::of::<S>()),
920 type_: PhantomData::default(),
921 }
922 }
923
924 /// Returns `true` if a resource with the given `TypeID` exists in this
925 /// store.
926 pub fn contains<S: Send + 'static>(&self) -> bool {
927 self.0.contains_key(&TypeId::of::<S>())
928 }
929
930 /// Get an immutable reference to a resource in the store.
931 ///
932 /// # Panics
933 /// Panics if the given resource does not exist.
934 pub fn get<S: Send + 'static>(&self) -> &S {
935 self.try_get().unwrap()
936 }
937
938 /// Get a mutable reference to a resource in the store.
939 ///
940 /// # Panics
941 /// Panics if the given resource does not exist.
942 pub fn get_mut<S: Send + 'static>(&mut self) -> &mut S {
943 self.try_get_mut().unwrap()
944 }
945
946 /// Get an immutable reference to a resource in the store.
947 ///
948 /// Returns `None` if the given resource does not exist.
949 pub fn try_get<S: Send + 'static>(&self) -> Option<&S> {
950 self.0
951 .get(&TypeId::of::<S>())
952 .map(|s| s.downcast_ref().unwrap())
953 }
954
955 /// Get a mutable reference to a resource in the store.
956 ///
957 /// Returns `None` if the given resource does not exist.
958 pub fn try_get_mut<S: Send + 'static>(&mut self) -> Option<&mut S> {
959 self.0
960 .get_mut(&TypeId::of::<S>())
961 .map(|s| s.downcast_mut().unwrap())
962 }
963}
964
965pub struct ProcStoreEntry<'a, S: Send + 'static> {
966 pub boxed_entry: Entry<'a, TypeId, Box<dyn Any + Send>>,
967 type_: PhantomData<S>,
968}
969
970impl<'a, S: Send + 'static> ProcStoreEntry<'a, S> {
971 pub fn or_insert_with(self, default: impl FnOnce() -> S) -> &'a mut S {
972 self.boxed_entry
973 .or_insert_with(|| Box::new((default)()))
974 .downcast_mut()
975 .unwrap()
976 }
977
978 pub fn or_insert_with_any(self, default: impl FnOnce() -> Box<dyn Any + Send>) -> &'a mut S {
979 self.boxed_entry
980 .or_insert_with(default)
981 .downcast_mut()
982 .unwrap()
983 }
984
985 pub fn and_modify(self, f: impl FnOnce(&mut S)) -> Self {
986 let entry = self
987 .boxed_entry
988 .and_modify(|e| (f)(e.downcast_mut().unwrap()));
989 Self {
990 boxed_entry: entry,
991 type_: PhantomData::default(),
992 }
993 }
994}