firewheel_core/node.rs
1use std::{any::Any, fmt::Debug, hash::Hash, ops::Range};
2
3use crate::{
4 channel_config::{ChannelConfig, ChannelCount},
5 clock::{ClockSamples, ClockSeconds, MusicalTime, MusicalTransport},
6 dsp::declick::DeclickValues,
7 event::{NodeEvent, NodeEventList, NodeEventType},
8 SilenceMask, StreamInfo,
9};
10
11pub mod dummy;
12
13/// A globally unique identifier for a node.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct NodeID(pub thunderdome::Index);
16
17impl NodeID {
18 pub const DANGLING: Self = Self(thunderdome::Index::DANGLING);
19}
20
21impl Default for NodeID {
22 fn default() -> Self {
23 Self::DANGLING
24 }
25}
26
27/// Information about an [`AudioNode`].
28///
29/// This struct enforces the use of the builder pattern for future-proofness, as
30/// it is likely that more fields will be added in the future.
31#[derive(Debug)]
32pub struct AudioNodeInfo {
33 debug_name: &'static str,
34 channel_config: ChannelConfig,
35 uses_events: bool,
36 call_update_method: bool,
37 custom_state: Option<Box<dyn Any>>,
38}
39
40impl AudioNodeInfo {
41 /// Construct a new [`AudioNodeInfo`] builder struct.
42 pub const fn new() -> Self {
43 Self {
44 debug_name: "unnamed",
45 channel_config: ChannelConfig {
46 num_inputs: ChannelCount::ZERO,
47 num_outputs: ChannelCount::ZERO,
48 },
49 uses_events: false,
50 call_update_method: false,
51 custom_state: None,
52 }
53 }
54
55 /// A unique name for this type of node, used for debugging purposes.
56 pub const fn debug_name(mut self, debug_name: &'static str) -> Self {
57 self.debug_name = debug_name;
58 self
59 }
60
61 /// The channel configuration of this node.
62 ///
63 /// By default this has a channel configuration with zero input and output
64 /// channels.
65 pub const fn channel_config(mut self, channel_config: ChannelConfig) -> Self {
66 self.channel_config = channel_config;
67 self
68 }
69
70 /// Set to `true` if this node type uses events, `false` otherwise.
71 ///
72 /// Setting to `false` will help the system save some memory by not
73 /// allocating an event buffer for this node.
74 ///
75 /// By default this is set to `false`.
76 pub const fn uses_events(mut self, uses_events: bool) -> Self {
77 self.uses_events = uses_events;
78 self
79 }
80
81 /// Set to `true` if this node wishes to have the Firewheel context call
82 /// [`AudioNode::update`] on every update cycle.
83 ///
84 /// By default this is set to `false`.
85 pub const fn call_update_method(mut self, call_update_method: bool) -> Self {
86 self.call_update_method = call_update_method;
87 self
88 }
89
90 /// Custom `!Send` state that can be stored in the Firewheel context and accessed
91 /// by the user.
92 ///
93 /// The user accesses this state via `FirewheelCtx::node_state` and
94 /// `FirewheelCtx::node_state_mut`.
95 pub fn custom_state<T: 'static>(mut self, custom_state: T) -> Self {
96 self.custom_state = Some(Box::new(custom_state));
97 self
98 }
99}
100
101/// Information about an [`AudioNode`]. Used internally by the Firewheel context.
102#[derive(Debug)]
103pub struct AudioNodeInfoInner {
104 pub debug_name: &'static str,
105 pub channel_config: ChannelConfig,
106 pub uses_events: bool,
107 pub call_update_method: bool,
108 pub custom_state: Option<Box<dyn Any>>,
109}
110
111impl Into<AudioNodeInfoInner> for AudioNodeInfo {
112 fn into(self) -> AudioNodeInfoInner {
113 AudioNodeInfoInner {
114 debug_name: self.debug_name,
115 channel_config: self.channel_config,
116 uses_events: self.uses_events,
117 call_update_method: self.call_update_method,
118 custom_state: self.custom_state,
119 }
120 }
121}
122
123/// A trait representing a node in a Firewheel audio graph.
124///
125/// # Notes about ECS
126///
127/// In order to be friendlier to ECS's (entity component systems), it is encouraged
128/// that any struct deriving this trait be POD (plain ol' data). If you want your
129/// audio node to be usable in the Bevy game engine, also derive
130/// `bevy_ecs::prelude::Component`. (You can hide this derive behind a feature flag
131/// by using `#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]`).
132///
133/// # Audio Node Lifecycle
134///
135/// 1. The user constructs the node as POD or from a custom constructor method for
136/// that node.
137/// 2. The user adds the node to the graph using `FirewheelCtx::add_node`. If the
138/// node has any custom configuration, then the user passes that configuration to this
139/// method as well. In this method, the Firewheel context calls [`AudioNode::info`] to
140/// get information about the node. The node can also store any custom state in the
141/// [`AudioNodeInfo`] struct.
142/// 3. At this point the user may now call `FirewheelCtx::node_state` and
143/// `FirewheelCtx::node_state_mut` to retrieve the node's custom state.
144/// 4. If [`AudioNodeInfo::call_update_method`] was set to `true`, then
145/// [`AudioNode::update`] will be called every time the Firewheel context updates.
146/// The node's custom state is also accessible in this method.
147/// 5. When the Firewheel context is ready for the node to start processing data,
148/// it calls [`AudioNode::construct_processor`] to retrieve the realtime
149/// [`AudioNodeProcessor`] counterpart of the node. This processor counterpart is
150/// then sent to the audio thread.
151/// 6. The Firewheel processor calls [`AudioNodeProcessor::process`] whenever there
152/// is a new block of audio data to process.
153/// 7. (Graceful shutdown)
154///
155/// 7a. The Firewheel processor calls [`AudioNodeProcessor::stream_stopped`].
156/// The processor is then sent back to the main thread.
157///
158/// 7b. If a new audio stream is started, then the context will call
159/// [`AudioNodeProcessor::new_stream`] on the main thread, and then send the
160/// processor back to the audio thread for processing.
161///
162/// 7c. If the Firewheel context is dropped before a new stream is started, then
163/// both the node and the processor counterpart are dropped.
164/// 8. (Audio thread crashes or stops unexpectedly) - The node's processor counterpart
165/// may or may not be dropped. The user may try to create a new audio stream, in which
166/// case [`AudioNode::construct_processor`] might be called again. If a second processor
167/// instance is not able to be created, then the node may panic.
168pub trait AudioNode {
169 /// A type representing this constructor's configuration.
170 ///
171 /// This is intended as a one-time configuration to be used
172 /// when constructing an audio node. When no configuration
173 /// is required, [`EmptyConfig`] should be used.
174 type Configuration: Default;
175
176 /// Get information about this node.
177 ///
178 /// This method is only called once after the node is added to the audio graph.
179 fn info(&self, configuration: &Self::Configuration) -> AudioNodeInfo;
180
181 /// Construct a realtime processor for this node.
182 ///
183 /// * `configuration` - The custom configuration of this node.
184 /// * `cx` - A context for interacting with the Firewheel context. This context
185 /// also includes information about the audio stream.
186 fn construct_processor(
187 &self,
188 configuration: &Self::Configuration,
189 cx: ConstructProcessorContext,
190 ) -> impl AudioNodeProcessor;
191
192 /// If [`AudioNodeInfo::call_update_method`] was set to `true`, then the Firewheel
193 /// context will call this method on every update cycle.
194 ///
195 /// * `configuration` - The custom configuration of this node.
196 /// * `cx` - A context for interacting with the Firewheel context.
197 fn update(&mut self, configuration: &Self::Configuration, cx: UpdateContext) {
198 let _ = configuration;
199 let _ = cx;
200 }
201}
202
203/// A context for [`AudioNode::construct_processor`].
204pub struct ConstructProcessorContext<'a> {
205 /// The ID of this audio node.
206 pub node_id: NodeID,
207 /// Information about the running audio stream.
208 pub stream_info: &'a StreamInfo,
209 custom_state: &'a mut Option<Box<dyn Any>>,
210}
211
212impl<'a> ConstructProcessorContext<'a> {
213 pub fn new(
214 node_id: NodeID,
215 stream_info: &'a StreamInfo,
216 custom_state: &'a mut Option<Box<dyn Any>>,
217 ) -> Self {
218 Self {
219 node_id,
220 stream_info,
221 custom_state,
222 }
223 }
224
225 /// Get an immutable reference to the custom state that was created in
226 /// [`AudioNodeInfo::custom_state`].
227 pub fn custom_state<T: 'static>(&self) -> Option<&T> {
228 self.custom_state
229 .as_ref()
230 .and_then(|s| s.downcast_ref::<T>())
231 }
232
233 /// Get a mutable reference to the custom state that was created in
234 /// [`AudioNodeInfo::custom_state`].
235 pub fn custom_state_mut<T: 'static>(&mut self) -> Option<&mut T> {
236 self.custom_state
237 .as_mut()
238 .and_then(|s| s.downcast_mut::<T>())
239 }
240}
241
242/// A context for [`AudioNode::update`].
243pub struct UpdateContext<'a> {
244 /// The ID of this audio node.
245 pub node_id: NodeID,
246 /// Information about the running audio stream. If no audio stream is running,
247 /// then this will be `None`.
248 pub stream_info: Option<&'a StreamInfo>,
249 custom_state: &'a mut Option<Box<dyn Any>>,
250 event_queue: &'a mut Vec<NodeEvent>,
251}
252
253impl<'a> UpdateContext<'a> {
254 pub fn new(
255 node_id: NodeID,
256 stream_info: Option<&'a StreamInfo>,
257 custom_state: &'a mut Option<Box<dyn Any>>,
258 event_queue: &'a mut Vec<NodeEvent>,
259 ) -> Self {
260 Self {
261 node_id,
262 stream_info,
263 custom_state,
264 event_queue,
265 }
266 }
267
268 /// Queue an event to send to this node's processor counterpart.
269 pub fn queue_event(&mut self, event: NodeEventType) {
270 self.event_queue.push(NodeEvent {
271 node_id: self.node_id,
272 event,
273 });
274 }
275
276 /// Get an immutable reference to the custom state that was created in
277 /// [`AudioNodeInfo::custom_state`].
278 pub fn custom_state<T: 'static>(&self) -> Option<&T> {
279 self.custom_state
280 .as_ref()
281 .and_then(|s| s.downcast_ref::<T>())
282 }
283
284 /// Get a mutable reference to the custom state that was created in
285 /// [`AudioNodeInfo::custom_state`].
286 pub fn custom_state_mut<T: 'static>(&mut self) -> Option<&mut T> {
287 self.custom_state
288 .as_mut()
289 .and_then(|s| s.downcast_mut::<T>())
290 }
291}
292
293/// An empty constructor configuration.
294///
295/// This should be preferred over `()` because it implements
296/// Bevy's `Component` trait, making the
297/// [`AudioNode`] implementor trivially Bevy-compatible.
298#[derive(Debug, Default, Clone, Copy)]
299#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
300pub struct EmptyConfig;
301
302/// A type-erased dyn-compatible [`AudioNode`].
303pub trait DynAudioNode {
304 /// Get information about this node.
305 ///
306 /// This method is only called once after the node is added to the audio graph.
307 fn info(&self) -> AudioNodeInfo;
308
309 /// Construct a realtime processor for this node.
310 ///
311 /// * `cx` - A context for interacting with the Firewheel context. This context
312 /// also includes information about the audio stream.
313 fn construct_processor(&self, cx: ConstructProcessorContext) -> Box<dyn AudioNodeProcessor>;
314
315 /// If [`AudioNodeInfo::call_update_method`] was set to `true`, then the Firewheel
316 /// context will call this method on every update cycle.
317 ///
318 /// * `cx` - A context for interacting with the Firewheel context.
319 fn update(&mut self, cx: UpdateContext) {
320 let _ = cx;
321 }
322}
323
324/// Pairs constructors with their configurations.
325///
326/// This is useful for type-erasing an [`AudioNode`].
327pub struct Constructor<T, C> {
328 constructor: T,
329 configuration: C,
330}
331
332impl<T: AudioNode> Constructor<T, T::Configuration> {
333 pub fn new(constructor: T, configuration: Option<T::Configuration>) -> Self {
334 Self {
335 constructor,
336 configuration: configuration.unwrap_or_default(),
337 }
338 }
339}
340
341impl<T: AudioNode> DynAudioNode for Constructor<T, T::Configuration> {
342 fn info(&self) -> AudioNodeInfo {
343 self.constructor.info(&self.configuration)
344 }
345
346 fn construct_processor(&self, cx: ConstructProcessorContext) -> Box<dyn AudioNodeProcessor> {
347 Box::new(
348 self.constructor
349 .construct_processor(&self.configuration, cx),
350 )
351 }
352
353 fn update(&mut self, cx: UpdateContext) {
354 self.constructor.update(&self.configuration, cx);
355 }
356}
357
358/// The trait describing the realtime processor counterpart to an
359/// audio node.
360pub trait AudioNodeProcessor: 'static + Send {
361 /// Process the given block of audio. Only process data in the
362 /// buffers up to `samples`.
363 ///
364 /// The node *MUST* either return `ProcessStatus::ClearAllOutputs`
365 /// or fill all output buffers with data.
366 ///
367 /// If any output buffers contain all zeros up to `samples` (silent),
368 /// then mark that buffer as silent in [`ProcInfo::out_silence_mask`].
369 ///
370 /// * `buffers` - The buffers of data to process.
371 /// * `proc_info` - Additional information about the process.
372 /// * `events` - A list of events for this node to process.
373 fn process(
374 &mut self,
375 buffers: ProcBuffers,
376 proc_info: &ProcInfo,
377 events: NodeEventList,
378 ) -> ProcessStatus;
379
380 /// Called when the audio stream has been stopped.
381 fn stream_stopped(&mut self) {}
382
383 /// Called when a new audio stream has been started after a previous
384 /// call to [`AudioNodeProcessor::stream_stopped`].
385 ///
386 /// Note, this method gets called on the main thread, not the audio
387 /// thread. So it is safe to allocate/deallocate here.
388 fn new_stream(&mut self, stream_info: &StreamInfo) {
389 let _ = stream_info;
390 }
391}
392
393pub const NUM_SCRATCH_BUFFERS: usize = 8;
394
395/// The buffers used in [`AudioNodeProcessor::process`].
396pub struct ProcBuffers<'a, 'b, 'c, 'd> {
397 /// The audio input buffers.
398 ///
399 /// The number of channels will always equal the [`ChannelConfig::num_inputs`]
400 /// value that was returned in [`AudioNode::info`].
401 ///
402 /// Each channel slice will have a length of [`ProcInfo::frames`].
403 pub inputs: &'a [&'b [f32]],
404
405 /// The audio input buffers.
406 ///
407 /// The number of channels will always equal the [`ChannelConfig::num_outputs`]
408 /// value that was returned in [`AudioNode::info`].
409 ///
410 /// Each channel slice will have a length of [`ProcInfo::frames`].
411 ///
412 /// These buffers may contain junk data.
413 pub outputs: &'a mut [&'b mut [f32]],
414
415 /// A list of extra scratch buffers that can be used for processing.
416 /// This removes the need for nodes to allocate their own scratch buffers.
417 /// Each buffer has a length of [`StreamInfo::max_block_frames`]. These
418 /// buffers are shared across all nodes, so assume that they contain junk
419 /// data.
420 pub scratch_buffers: &'c mut [&'d mut [f32]; NUM_SCRATCH_BUFFERS],
421}
422
423/// Additional information for processing audio
424pub struct ProcInfo<'a> {
425 /// The number of samples (in a single channel of audio) in this
426 /// processing block.
427 ///
428 /// Not to be confused with video frames.
429 pub frames: usize,
430
431 /// An optional optimization hint on which input channels contain
432 /// all zeros (silence). The first bit (`0b1`) is the first channel,
433 /// the second bit is the second channel, and so on.
434 pub in_silence_mask: SilenceMask,
435
436 /// An optional optimization hint on which output channels contain
437 /// all zeros (silence). The first bit (`0b1`) is the first channel,
438 /// the second bit is the second channel, and so on.
439 pub out_silence_mask: SilenceMask,
440
441 /// The current interval of time of the internal clock in units of
442 /// seconds. The start of the range is the instant of time at the
443 /// first sample in the block (inclusive), and the end of the range
444 /// is the instant of time at the end of the block (exclusive).
445 ///
446 /// This uses the clock from the OS's audio API so it should be quite
447 /// accurate, and it correctly accounts for any output underflows that
448 /// may occur.
449 pub clock_seconds: Range<ClockSeconds>,
450
451 /// The total number of samples (in a single channel of audio) that
452 /// have been processed since the start of the audio stream.
453 ///
454 /// This value can be used for more accurate timing than
455 /// [`ProcInfo::clock_seconds`], but note it does *NOT* account for any
456 /// output underflows that may occur.
457 pub clock_samples: ClockSamples,
458
459 /// Information about the musical transport.
460 ///
461 /// This will be `None` if no musical transport is currently active,
462 /// or if the current transport is currently paused.
463 pub transport_info: Option<TransportInfo<'a>>,
464
465 /// Flags indicating the current status of the audio stream
466 pub stream_status: StreamStatus,
467
468 /// A buffer of values that linearly ramp up/down between `0.0` and `1.0`
469 /// which can be used to implement efficient declicking when
470 /// pausing/resuming/stopping.
471 pub declick_values: &'a DeclickValues,
472}
473
474#[derive(Debug, Clone, PartialEq)]
475pub struct TransportInfo<'a> {
476 /// The current transport.
477 pub transport: &'a MusicalTransport,
478
479 /// The current interval of time of the internal clock in units of
480 /// musical time. The start of the range is the instant of time at the
481 /// first sample in the block (inclusive), and the end of the range
482 /// is the instant of time at the end of the block (exclusive).
483 ///
484 /// This will be `None` if no musical clock is currently present.
485 pub musical_clock: Range<MusicalTime>,
486
487 /// Whether or not the transport is currently paused.
488 pub paused: bool,
489}
490
491bitflags::bitflags! {
492 /// Flags indicating the current status of the audio stream
493 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
494 pub struct StreamStatus: u32 {
495 /// Some input data was discarded because of an overflow condition
496 /// at the audio driver.
497 const INPUT_OVERFLOW = 0b01;
498
499 /// The output buffer ran low, likely producing a break in the
500 /// output sound.
501 const OUTPUT_UNDERFLOW = 0b10;
502 }
503}
504
505/// The status of processing buffers in an audio node.
506#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
507pub enum ProcessStatus {
508 /// No output buffers were modified. If this is returned, then
509 /// the engine will automatically clear all output buffers
510 /// for you as efficiently as possible.
511 #[default]
512 ClearAllOutputs,
513 /// No output buffers were modified. If this is returned, then
514 /// the engine will automatically copy the input buffers to
515 /// their corresponding output buffers for you as efficiently
516 /// as possible.
517 Bypass,
518 /// All output buffers were filled with data.
519 OutputsModified { out_silence_mask: SilenceMask },
520}
521
522impl ProcessStatus {
523 /// All output buffers were filled with non-silence.
524 pub const fn outputs_not_silent() -> Self {
525 Self::OutputsModified {
526 out_silence_mask: SilenceMask::NONE_SILENT,
527 }
528 }
529
530 /// All output buffers were filled with data.
531 pub const fn outputs_modified(out_silence_mask: SilenceMask) -> Self {
532 Self::OutputsModified { out_silence_mask }
533 }
534}