firewheel_core/
event.rs

1use core::any::Any;
2
3#[cfg(not(feature = "std"))]
4use bevy_platform::prelude::{Box, Vec};
5
6use crate::{
7    clock::{DurationSamples, DurationSeconds, InstantSamples, InstantSeconds},
8    collector::{ArcGc, OwnedGc},
9    diff::{Notify, ParamPath},
10    dsp::volume::Volume,
11    node::NodeID,
12    vector::{Vec2, Vec3},
13};
14
15#[cfg(feature = "midi_events")]
16pub use wmidi;
17#[cfg(feature = "midi_events")]
18use wmidi::MidiMessage;
19
20#[cfg(feature = "scheduled_events")]
21use crate::clock::EventInstant;
22
23#[cfg(feature = "musical_transport")]
24use crate::clock::{DurationMusical, InstantMusical};
25
26/// An event sent to an [`AudioNodeProcessor`][crate::node::AudioNodeProcessor].
27#[derive(Debug)]
28pub struct NodeEvent {
29    /// The ID of the node that should receive the event.
30    pub node_id: NodeID,
31    /// Optionally, a time to schedule this event at. If `None`, the event is considered
32    /// to be at the start of the next processing period.
33    #[cfg(feature = "scheduled_events")]
34    pub time: Option<EventInstant>,
35    /// The type of event.
36    pub event: NodeEventType,
37}
38
39impl NodeEvent {
40    /// Construct an event to send to an [`AudioNodeProcessor`][crate::node::AudioNodeProcessor].
41    ///
42    /// * `node_id` - The ID of the node that should receive the event.
43    /// * `event` - The type of event.
44    pub const fn new(node_id: NodeID, event: NodeEventType) -> Self {
45        Self {
46            node_id,
47            #[cfg(feature = "scheduled_events")]
48            time: None,
49            event,
50        }
51    }
52
53    /// Construct a new scheduled event to send to an
54    /// [`AudioNodeProcessor`][crate::node::AudioNodeProcessor].
55    ///
56    /// * `node_id` - The ID of the node that should receive the event.
57    /// * `time` - The time to schedule this event at.
58    /// * `event` - The type of event.
59    #[cfg(feature = "scheduled_events")]
60    pub const fn scheduled(node_id: NodeID, time: EventInstant, event: NodeEventType) -> Self {
61        Self {
62            node_id,
63            time: Some(time),
64            event,
65        }
66    }
67}
68
69/// An event type associated with an [`AudioNodeProcessor`][crate::node::AudioNodeProcessor].
70#[non_exhaustive]
71pub enum NodeEventType {
72    Param {
73        /// Data for a specific parameter.
74        data: ParamData,
75        /// The path to the parameter.
76        path: ParamPath,
77    },
78    /// Custom event type stored on the heap.
79    Custom(OwnedGc<Box<dyn Any + Send + 'static>>),
80    /// Custom event type stored on the stack as raw bytes.
81    CustomBytes([u8; 36]),
82    #[cfg(feature = "midi_events")]
83    MIDI(MidiMessage<'static>),
84}
85
86impl NodeEventType {
87    pub fn custom<T: Send + 'static>(value: T) -> Self {
88        Self::Custom(OwnedGc::new(Box::new(value)))
89    }
90
91    pub fn custom_boxed<T: Send + 'static>(value: Box<T>) -> Self {
92        Self::Custom(OwnedGc::new(value))
93    }
94
95    /// Try to downcast the custom event to an immutable reference to `T`.
96    ///
97    /// If this does not contain [`NodeEventType::Custom`] or if the
98    /// downcast failed, then this returns `None`.
99    pub fn downcast_ref<T: Send + 'static>(&self) -> Option<&T> {
100        if let Self::Custom(owned) = self {
101            owned.as_ref().downcast_ref()
102        } else {
103            None
104        }
105    }
106
107    /// Try to downcast the custom event to a mutable reference to `T`.
108    ///
109    /// If this does not contain [`NodeEventType::Custom`] or if the
110    /// downcast failed, then this returns `None`.
111    pub fn downcast_mut<T: Send + 'static>(&mut self) -> Option<&mut T> {
112        if let Self::Custom(owned) = self {
113            owned.as_mut().downcast_mut()
114        } else {
115            None
116        }
117    }
118
119    /// Try to swap the contents of the custom event with the contents of
120    /// the given value.
121    ///
122    /// If successful, the old contents that were stored in `value` will
123    /// safely be dropped and deallocated on another non-realtime thread.
124    ///
125    /// Returns `true` if the value has been successfully swapped, `false`
126    /// otherwise (i.e. the event did not contain [`NodeEventType::Custom`]
127    /// or the downcast failed).
128    pub fn downcast_swap<T: Send + 'static>(&mut self, value: &mut T) -> bool {
129        if let Some(v) = self.downcast_mut::<T>() {
130            core::mem::swap(v, value);
131            true
132        } else {
133            false
134        }
135    }
136
137    /// Try to swap the contents of the custom event with the contents of
138    /// the given value wrapped in an [`OwnedGc`].
139    ///
140    /// If successful, the old contents that were stored in `value` will
141    /// safely be dropped and deallocated on another non-realtime thread.
142    ///
143    /// Returns `true` if the value has been successfully swapped, `false`
144    /// otherwise (i.e. the event did not contain [`NodeEventType::Custom`]
145    /// or the downcast failed).
146    pub fn downcast_into_owned<T: Send + 'static>(&mut self, value: &mut OwnedGc<T>) -> bool {
147        if let Some(v) = self.downcast_mut::<T>() {
148            value.swap(v);
149            true
150        } else {
151            false
152        }
153    }
154}
155
156impl core::fmt::Debug for NodeEventType {
157    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
158        match self {
159            NodeEventType::Param { data, path } => f
160                .debug_struct("Param")
161                .field("data", &data)
162                .field("path", &path)
163                .finish(),
164            NodeEventType::Custom(_) => f.debug_tuple("Custom").finish_non_exhaustive(),
165            NodeEventType::CustomBytes(f0) => f.debug_tuple("CustomBytes").field(&f0).finish(),
166            #[cfg(feature = "midi_events")]
167            NodeEventType::MIDI(f0) => f.debug_tuple("MIDI").field(&f0).finish(),
168        }
169    }
170}
171
172/// Data that can be used to patch an individual parameter.
173#[derive(Clone, Debug)]
174#[non_exhaustive]
175pub enum ParamData {
176    F32(f32),
177    F64(f64),
178    I32(i32),
179    U32(u32),
180    I64(i64),
181    U64(u64),
182    Bool(bool),
183    Volume(Volume),
184    Vector2D(Vec2),
185    Vector3D(Vec3),
186
187    #[cfg(feature = "scheduled_events")]
188    EventInstant(EventInstant),
189    InstantSeconds(InstantSeconds),
190    DurationSeconds(DurationSeconds),
191    InstantSamples(InstantSamples),
192    DurationSamples(DurationSamples),
193    #[cfg(feature = "musical_transport")]
194    InstantMusical(InstantMusical),
195    #[cfg(feature = "musical_transport")]
196    DurationMusical(DurationMusical),
197
198    /// Custom type stored on the heap.
199    Any(ArcGc<dyn Any + Send + Sync>),
200
201    /// Custom type stored on the stack as raw bytes.
202    CustomBytes([u8; 20]),
203
204    /// No data (i.e. the type is `None`).
205    None,
206}
207
208impl ParamData {
209    /// Construct a [`ParamData::Any`] variant.
210    pub fn any<T: Send + Sync + 'static>(value: T) -> Self {
211        Self::Any(ArcGc::new_any(value))
212    }
213
214    /// Construct an optional [`ParamData::Any`] variant.
215    pub fn opt_any<T: Any + Send + Sync + 'static>(value: Option<T>) -> Self {
216        if let Some(value) = value {
217            Self::any(value)
218        } else {
219            Self::None
220        }
221    }
222
223    /// Try to downcast [`ParamData::Any`] into `T`.
224    ///
225    /// If this enum doesn't hold [`ParamData::Any`] or the downcast fails,
226    /// then this returns `None`.
227    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
228        match self {
229            Self::Any(any) => any.downcast_ref(),
230            _ => None,
231        }
232    }
233}
234
235macro_rules! param_data_from {
236    ($ty:ty, $variant:ident) => {
237        impl From<$ty> for ParamData {
238            fn from(value: $ty) -> Self {
239                Self::$variant(value.into())
240            }
241        }
242
243        impl TryInto<$ty> for &ParamData {
244            type Error = crate::diff::PatchError;
245
246            fn try_into(self) -> Result<$ty, crate::diff::PatchError> {
247                match self {
248                    ParamData::$variant(value) => Ok((*value).into()),
249                    _ => Err(crate::diff::PatchError::InvalidData),
250                }
251            }
252        }
253
254        impl From<Option<$ty>> for ParamData {
255            fn from(value: Option<$ty>) -> Self {
256                if let Some(value) = value {
257                    Self::$variant(value.into())
258                } else {
259                    Self::None
260                }
261            }
262        }
263
264        impl TryInto<Option<$ty>> for &ParamData {
265            type Error = crate::diff::PatchError;
266
267            fn try_into(self) -> Result<Option<$ty>, crate::diff::PatchError> {
268                match self {
269                    ParamData::$variant(value) => Ok(Some((*value).into())),
270                    ParamData::None => Ok(None),
271                    _ => Err(crate::diff::PatchError::InvalidData),
272                }
273            }
274        }
275
276        impl From<Notify<$ty>> for ParamData {
277            fn from(value: Notify<$ty>) -> Self {
278                Self::$variant((*value).into())
279            }
280        }
281
282        impl TryInto<Notify<$ty>> for &ParamData {
283            type Error = crate::diff::PatchError;
284
285            fn try_into(self) -> Result<Notify<$ty>, crate::diff::PatchError> {
286                match self {
287                    ParamData::$variant(value) => Ok(Notify::new((*value).into())),
288                    _ => Err(crate::diff::PatchError::InvalidData),
289                }
290            }
291        }
292    };
293}
294
295param_data_from!(Volume, Volume);
296param_data_from!(f32, F32);
297param_data_from!(f64, F64);
298param_data_from!(i32, I32);
299param_data_from!(u32, U32);
300param_data_from!(i64, I64);
301param_data_from!(u64, U64);
302param_data_from!(bool, Bool);
303param_data_from!(Vec2, Vector2D);
304param_data_from!(Vec3, Vector3D);
305#[cfg(feature = "scheduled_events")]
306param_data_from!(EventInstant, EventInstant);
307param_data_from!(InstantSeconds, InstantSeconds);
308param_data_from!(DurationSeconds, DurationSeconds);
309param_data_from!(InstantSamples, InstantSamples);
310param_data_from!(DurationSamples, DurationSamples);
311#[cfg(feature = "musical_transport")]
312param_data_from!(InstantMusical, InstantMusical);
313#[cfg(feature = "musical_transport")]
314param_data_from!(DurationMusical, DurationMusical);
315
316#[cfg(feature = "glam-29")]
317param_data_from!(glam_29::Vec2, Vector2D);
318#[cfg(feature = "glam-29")]
319param_data_from!(glam_29::Vec3, Vector3D);
320
321#[cfg(feature = "glam-30")]
322param_data_from!(glam_30::Vec2, Vector2D);
323#[cfg(feature = "glam-30")]
324param_data_from!(glam_30::Vec3, Vector3D);
325
326impl From<()> for ParamData {
327    fn from(_value: ()) -> Self {
328        Self::None
329    }
330}
331
332impl TryInto<()> for &ParamData {
333    type Error = crate::diff::PatchError;
334
335    fn try_into(self) -> Result<(), crate::diff::PatchError> {
336        match self {
337            ParamData::None => Ok(()),
338            _ => Err(crate::diff::PatchError::InvalidData),
339        }
340    }
341}
342
343impl From<Notify<()>> for ParamData {
344    fn from(_value: Notify<()>) -> Self {
345        Self::None
346    }
347}
348
349impl TryInto<Notify<()>> for &ParamData {
350    type Error = crate::diff::PatchError;
351
352    fn try_into(self) -> Result<Notify<()>, crate::diff::PatchError> {
353        match self {
354            ParamData::None => Ok(Notify::new(())),
355            _ => Err(crate::diff::PatchError::InvalidData),
356        }
357    }
358}
359
360/// Used internally by the Firewheel processor
361#[cfg(feature = "scheduled_events")]
362pub struct ScheduledEventEntry {
363    pub event: NodeEvent,
364    pub is_pre_process: bool,
365}
366
367/// A list of events for an [`AudioNodeProcessor`][crate::node::AudioNodeProcessor].
368pub struct ProcEvents<'a> {
369    immediate_event_buffer: &'a mut [Option<NodeEvent>],
370    #[cfg(feature = "scheduled_events")]
371    scheduled_event_arena: &'a mut [Option<ScheduledEventEntry>],
372    indices: &'a mut Vec<ProcEventsIndex>,
373}
374
375impl<'a> ProcEvents<'a> {
376    pub fn new(
377        immediate_event_buffer: &'a mut [Option<NodeEvent>],
378        #[cfg(feature = "scheduled_events")] scheduled_event_arena: &'a mut [Option<
379            ScheduledEventEntry,
380        >],
381        indices: &'a mut Vec<ProcEventsIndex>,
382    ) -> Self {
383        Self {
384            immediate_event_buffer,
385            #[cfg(feature = "scheduled_events")]
386            scheduled_event_arena,
387            indices,
388        }
389    }
390
391    pub fn num_events(&self) -> usize {
392        self.indices.len()
393    }
394
395    /// Iterate over all events, draining the events from the list.
396    pub fn drain<'b>(&'b mut self) -> impl IntoIterator<Item = NodeEventType> + use<'b> {
397        self.indices.drain(..).map(|index_type| match index_type {
398            ProcEventsIndex::Immediate(i) => {
399                self.immediate_event_buffer[i as usize]
400                    .take()
401                    .unwrap()
402                    .event
403            }
404            #[cfg(feature = "scheduled_events")]
405            ProcEventsIndex::Scheduled(i) => {
406                self.scheduled_event_arena[i as usize]
407                    .take()
408                    .unwrap()
409                    .event
410                    .event
411            }
412        })
413    }
414
415    /// Iterate over all events and their timestamps, draining the
416    /// events from the list.
417    ///
418    /// The iterator returns `(event_type, Option<event_instant>)`
419    /// where `event_type` is the event, `event_instant` is the instant the
420    /// event was schedueld for. If the event was not scheduled, then
421    /// the latter will be `None`.
422    #[cfg(feature = "scheduled_events")]
423    pub fn drain_with_timestamps<'b>(
424        &'b mut self,
425    ) -> impl IntoIterator<Item = (NodeEventType, Option<EventInstant>)> + use<'b> {
426        self.indices.drain(..).map(|index_type| match index_type {
427            ProcEventsIndex::Immediate(i) => {
428                let event = self.immediate_event_buffer[i as usize].take().unwrap();
429
430                (event.event, event.time)
431            }
432            ProcEventsIndex::Scheduled(i) => {
433                let event = self.scheduled_event_arena[i as usize].take().unwrap();
434
435                (event.event.event, event.event.time)
436            }
437        })
438    }
439
440    /// Iterate over patches for `T`, draining the events from the list.
441    ///
442    /// ```
443    /// # use firewheel_core::{diff::*, event::ProcEvents};
444    /// # fn for_each_example(mut event_list: ProcEvents) {
445    /// #[derive(Patch, Default)]
446    /// struct FilterNode {
447    ///     frequency: f32,
448    ///     quality: f32,
449    /// }
450    ///
451    /// let mut node = FilterNode::default();
452    ///
453    /// // You can match on individual patch variants.
454    /// for patch in event_list.drain_patches::<FilterNode>() {
455    ///     match patch {
456    ///         FilterNodePatch::Frequency(frequency) => {
457    ///             node.frequency = frequency;
458    ///         }
459    ///         FilterNodePatch::Quality(quality) => {
460    ///             node.quality = quality;
461    ///         }
462    ///     }
463    /// }
464    ///
465    /// // Or simply apply all of them.
466    /// for patch in event_list.drain_patches::<FilterNode>() { node.apply(patch); }
467    /// # }
468    /// ```
469    ///
470    /// Errors produced while constructing patches are simply skipped.
471    pub fn drain_patches<'b, T: crate::diff::Patch>(
472        &'b mut self,
473    ) -> impl IntoIterator<Item = <T as crate::diff::Patch>::Patch> + use<'b, T> {
474        // Ideally this would parameterise the `FnMut` over some `impl From<PatchEvent<T>>`
475        // but it would require a marker trait for the `diff::Patch::Patch` assoc type to
476        // prevent overlapping impls.
477        self.drain().into_iter().filter_map(|e| T::patch_event(&e))
478    }
479
480    /// Iterate over patches for `T`, draining the events from the list, while also
481    /// returning the timestamp the event was scheduled for.
482    ///
483    /// The iterator returns `(patch, Option<event_instant>)`
484    /// where `event_instant` is the instant the event was schedueld for. If the event
485    /// was not scheduled, then the latter will be `None`.
486    ///
487    /// ```
488    /// # use firewheel_core::{diff::*, event::ProcEvents};
489    /// # fn for_each_example(mut event_list: ProcEvents) {
490    /// #[derive(Patch, Default)]
491    /// struct FilterNode {
492    ///     frequency: f32,
493    ///     quality: f32,
494    /// }
495    ///
496    /// let mut node = FilterNode::default();
497    ///
498    /// // You can match on individual patch variants.
499    /// for (patch, timestamp) in event_list.drain_patches_with_timestamps::<FilterNode>() {
500    ///     match patch {
501    ///         FilterNodePatch::Frequency(frequency) => {
502    ///             node.frequency = frequency;
503    ///         }
504    ///         FilterNodePatch::Quality(quality) => {
505    ///             node.quality = quality;
506    ///         }
507    ///     }
508    /// }
509    ///
510    /// // Or simply apply all of them.
511    /// for (patch, timestamp) in event_list.drain_patches_with_timestamps::<FilterNode>() { node.apply(patch); }
512    /// # }
513    /// ```
514    ///
515    /// Errors produced while constructing patches are simply skipped.
516    #[cfg(feature = "scheduled_events")]
517    pub fn drain_patches_with_timestamps<'b, T: crate::diff::Patch>(
518        &'b mut self,
519    ) -> impl IntoIterator<Item = (<T as crate::diff::Patch>::Patch, Option<EventInstant>)> + use<'b, T>
520    {
521        // Ideally this would parameterise the `FnMut` over some `impl From<PatchEvent<T>>`
522        // but it would require a marker trait for the `diff::Patch::Patch` assoc type to
523        // prevent overlapping impls.
524        self.drain_with_timestamps()
525            .into_iter()
526            .filter_map(|(e, timestamp)| T::patch_event(&e).map(|patch| (patch, timestamp)))
527    }
528}
529
530/// Used internally by the Firewheel processor.
531#[derive(Debug, Clone, Copy, PartialEq, Eq)]
532pub enum ProcEventsIndex {
533    Immediate(u32),
534    #[cfg(feature = "scheduled_events")]
535    Scheduled(u32),
536}