1use core::{num::NonZeroU32, usize};
2
3use ringbuf::traits::Producer;
4use thunderdome::Arena;
5
6#[cfg(not(feature = "std"))]
7use bevy_platform::prelude::{Box, Vec};
8
9use firewheel_core::{
10 clock::InstantSamples,
11 dsp::{buffer::ChannelBuffer, declick::DeclickValues},
12 event::{NodeEvent, ProcEventsIndex},
13 log::RealtimeLogger,
14 node::{AudioNodeProcessor, ProcExtra},
15 StreamInfo,
16};
17
18use crate::{
19 backend::{AudioBackend, BackendProcessInfo},
20 graph::ScheduleHeapData,
21 processor::event_scheduler::{EventScheduler, NodeEventSchedulerData},
22};
23
24#[cfg(feature = "scheduled_events")]
25use crate::context::ClearScheduledEventsType;
26#[cfg(feature = "scheduled_events")]
27use firewheel_core::node::NodeID;
28#[cfg(feature = "scheduled_events")]
29use smallvec::SmallVec;
30
31#[cfg(feature = "musical_transport")]
32use firewheel_core::clock::{InstantMusical, TransportState};
33
34mod event_scheduler;
35mod handle_messages;
36mod process;
37
38#[cfg(feature = "musical_transport")]
39mod transport;
40#[cfg(feature = "musical_transport")]
41use transport::ProcTransportState;
42
43pub struct FirewheelProcessor<B: AudioBackend> {
44 inner: Option<FirewheelProcessorInner<B>>,
45 drop_tx: ringbuf::HeapProd<FirewheelProcessorInner<B>>,
46}
47
48impl<B: AudioBackend> Drop for FirewheelProcessor<B> {
49 fn drop(&mut self) {
50 let Some(mut inner) = self.inner.take() else {
51 return;
52 };
53
54 inner.stream_stopped();
55
56 #[cfg(feature = "std")]
58 if std::thread::panicking() {
59 inner.poisoned = true;
60 }
61
62 let _ = self.drop_tx.try_push(inner);
63 }
64}
65
66impl<B: AudioBackend> FirewheelProcessor<B> {
67 pub(crate) fn new(
68 processor: FirewheelProcessorInner<B>,
69 drop_tx: ringbuf::HeapProd<FirewheelProcessorInner<B>>,
70 ) -> Self {
71 Self {
72 inner: Some(processor),
73 drop_tx,
74 }
75 }
76
77 pub fn process_interleaved(
78 &mut self,
79 input: &[f32],
80 output: &mut [f32],
81 info: BackendProcessInfo<B>,
82 ) {
83 if let Some(inner) = &mut self.inner {
84 inner.process_interleaved(input, output, info);
85 }
86 }
87}
88
89pub(crate) struct FirewheelProcessorInner<B: AudioBackend> {
90 nodes: Arena<NodeEntry>,
91 schedule_data: Option<Box<ScheduleHeapData>>,
92
93 from_graph_rx: ringbuf::HeapCons<ContextToProcessorMsg>,
94 to_graph_tx: ringbuf::HeapProd<ProcessorToContextMsg>,
95
96 event_scheduler: EventScheduler,
97 proc_event_queue: Vec<ProcEventsIndex>,
98
99 sample_rate: NonZeroU32,
100 sample_rate_recip: f64,
101 max_block_frames: usize,
102
103 clock_samples: InstantSamples,
104 shared_clock_input: triple_buffer::Input<SharedClock<B::Instant>>,
105
106 #[cfg(feature = "musical_transport")]
107 proc_transport_state: ProcTransportState,
108
109 hard_clip_outputs: bool,
110
111 extra: ProcExtra,
112
113 pub(crate) poisoned: bool,
117}
118
119impl<B: AudioBackend> FirewheelProcessorInner<B> {
120 pub(crate) fn new(
122 from_graph_rx: ringbuf::HeapCons<ContextToProcessorMsg>,
123 to_graph_tx: ringbuf::HeapProd<ProcessorToContextMsg>,
124 shared_clock_input: triple_buffer::Input<SharedClock<B::Instant>>,
125 immediate_event_buffer_capacity: usize,
126 #[cfg(feature = "scheduled_events")] scheduled_event_buffer_capacity: usize,
127 node_event_buffer_capacity: usize,
128 stream_info: &StreamInfo,
129 hard_clip_outputs: bool,
130 buffer_out_of_space_mode: BufferOutOfSpaceMode,
131 logger: RealtimeLogger,
132 ) -> Self {
133 Self {
134 nodes: Arena::new(),
135 schedule_data: None,
136 from_graph_rx,
137 to_graph_tx,
138 event_scheduler: EventScheduler::new(
139 immediate_event_buffer_capacity,
140 #[cfg(feature = "scheduled_events")]
141 scheduled_event_buffer_capacity,
142 buffer_out_of_space_mode,
143 ),
144 proc_event_queue: Vec::with_capacity(node_event_buffer_capacity),
145 sample_rate: stream_info.sample_rate,
146 sample_rate_recip: stream_info.sample_rate_recip,
147 max_block_frames: stream_info.max_block_frames.get() as usize,
148 clock_samples: InstantSamples(0),
149 shared_clock_input,
150 #[cfg(feature = "musical_transport")]
151 proc_transport_state: ProcTransportState::new(),
152 hard_clip_outputs,
153 extra: ProcExtra {
154 scratch_buffers: ChannelBuffer::new(stream_info.max_block_frames.get() as usize),
155 declick_values: DeclickValues::new(stream_info.declick_frames),
156 logger,
157 },
158 poisoned: false,
159 }
160 }
161}
162
163pub(crate) struct NodeEntry {
164 pub processor: Box<dyn AudioNodeProcessor>,
165
166 event_data: NodeEventSchedulerData,
167}
168
169pub(crate) enum ContextToProcessorMsg {
170 EventGroup(Vec<NodeEvent>),
171 NewSchedule(Box<ScheduleHeapData>),
172 HardClipOutputs(bool),
173 #[cfg(feature = "musical_transport")]
174 SetTransportState(Box<TransportState>),
175 #[cfg(feature = "scheduled_events")]
176 ClearScheduledEvents(SmallVec<[ClearScheduledEventsEvent; 1]>),
177}
178
179pub(crate) enum ProcessorToContextMsg {
180 ReturnEventGroup(Vec<NodeEvent>),
181 ReturnSchedule(Box<ScheduleHeapData>),
182 #[cfg(feature = "musical_transport")]
183 ReturnTransportState(Box<TransportState>),
184 #[cfg(feature = "scheduled_events")]
185 ReturnClearScheduledEvents(SmallVec<[ClearScheduledEventsEvent; 1]>),
186}
187
188#[cfg(feature = "scheduled_events")]
189pub(crate) struct ClearScheduledEventsEvent {
190 pub node_id: Option<NodeID>,
192 pub event_type: ClearScheduledEventsType,
193}
194
195#[derive(Clone)]
196pub(crate) struct SharedClock<I: Clone> {
197 pub clock_samples: InstantSamples,
198 #[cfg(feature = "musical_transport")]
199 pub current_playhead: Option<InstantMusical>,
200 #[cfg(feature = "musical_transport")]
201 pub speed_multiplier: f64,
202 #[cfg(feature = "musical_transport")]
203 pub transport_is_playing: bool,
204 pub process_timestamp: Option<I>,
205}
206
207impl<I: Clone> Default for SharedClock<I> {
208 fn default() -> Self {
209 Self {
210 clock_samples: InstantSamples(0),
211 #[cfg(feature = "musical_transport")]
212 current_playhead: None,
213 #[cfg(feature = "musical_transport")]
214 speed_multiplier: 1.0,
215 #[cfg(feature = "musical_transport")]
216 transport_is_playing: false,
217 process_timestamp: None,
218 }
219 }
220}
221
222#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
224pub enum BufferOutOfSpaceMode {
225 #[default]
226 AllocateOnAudioThread,
231 Panic,
234 DropEvents,
240}