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