1use audioadapter::{Adapter, AdapterMut};
2use bevy_platform::sync::{
3 Arc,
4 atomic::{AtomicBool, Ordering},
5};
6use core::num::NonZeroU32;
7use ringbuf::traits::Producer;
8use thunderdome::Arena;
9
10#[cfg(not(feature = "std"))]
11use bevy_platform::prelude::{Box, Vec};
12
13use bevy_platform::time::Instant;
14
15use firewheel_core::{
16 StreamInfo,
17 clock::InstantSamples,
18 dsp::{
19 buffer::ConstSequentialBuffer,
20 declick::{DeclickValues, Declicker},
21 },
22 event::{NodeEvent, ProcEventsIndex},
23 node::{AudioNodeProcessor, ProcExtra},
24};
25
26use crate::{
27 backend::BackendProcessInfo,
28 context::{FirewheelBitFlags, ProcessorChannel},
29 graph::ScheduleHeapData,
30 processor::{
31 event_scheduler::{EventScheduler, NodeEventSchedulerData},
32 profiling::ProfilerTx,
33 },
34};
35
36pub use profiling::ProfilingData;
37
38#[cfg(feature = "scheduled_events")]
39use crate::context::ClearScheduledEventsType;
40#[cfg(feature = "scheduled_events")]
41use firewheel_core::node::NodeID;
42#[cfg(feature = "scheduled_events")]
43use smallvec::SmallVec;
44
45#[cfg(feature = "musical_transport")]
46use firewheel_core::clock::{InstantMusical, TransportState};
47
48mod event_scheduler;
49mod handle_messages;
50mod process;
51pub(crate) mod profiling;
52
53#[cfg(feature = "musical_transport")]
54mod transport;
55#[cfg(feature = "musical_transport")]
56use transport::ProcTransportState;
57
58pub struct FirewheelProcessor {
59 inner: Option<FirewheelProcessorInner>,
60 drop_tx: ringbuf::HeapProd<FirewheelProcessorInner>,
61 drop_flag: Arc<AtomicBool>,
62}
63
64impl Drop for FirewheelProcessor {
65 fn drop(&mut self) {
66 self.drop_inner();
67 }
68}
69
70impl FirewheelProcessor {
71 pub(crate) fn new(
72 processor: FirewheelProcessorInner,
73 drop_tx: ringbuf::HeapProd<FirewheelProcessorInner>,
74 drop_flag: Arc<AtomicBool>,
75 ) -> Self {
76 Self {
77 inner: Some(processor),
78 drop_tx,
79 drop_flag,
80 }
81 }
82
83 pub fn process(
84 &mut self,
85 input: &dyn Adapter<'_, f32>,
86 output: &mut dyn AdapterMut<'_, f32>,
87 info: BackendProcessInfo,
88 ) {
89 self.poll_drop_flag();
90
91 if let Some(inner) = &mut self.inner {
92 inner.process(input, output, info);
93 } else {
94 output.fill_frames_with(0, info.frames, &0.0);
95 }
96 }
97
98 fn poll_drop_flag(&mut self) {
99 if self.inner.is_some() && self.drop_flag.load(Ordering::Relaxed) {
100 self.drop_inner();
101 }
102 }
103
104 fn drop_inner(&mut self) {
105 let Some(mut inner) = self.inner.take() else {
106 return;
107 };
108
109 inner.stream_stopped();
110
111 #[cfg(feature = "std")]
113 if std::thread::panicking() {
114 inner.poisoned = true;
115 }
116
117 let _ = self.drop_tx.try_push(inner);
118 }
119}
120
121pub(crate) struct FirewheelProcessorInner {
122 nodes: Arena<NodeEntry>,
123 schedule_data: Option<Box<ScheduleHeapData>>,
124
125 from_graph_rx: ringbuf::HeapCons<ContextToProcessorMsg>,
126 to_graph_tx: ringbuf::HeapProd<ProcessorToContextMsg>,
127
128 event_scheduler: EventScheduler,
129 proc_event_queue: Vec<ProcEventsIndex>,
130
131 sample_rate: NonZeroU32,
132 sample_rate_recip: f64,
133 max_block_frames: usize,
134
135 clock_samples: InstantSamples,
136 #[cfg(feature = "scheduled_events")]
137 shared_clock_input: triple_buffer::Input<SharedClock>,
138 profiler_tx: ProfilerTx,
139
140 #[cfg(feature = "musical_transport")]
141 proc_transport_state: ProcTransportState,
142
143 flags: FirewheelBitFlags,
144 shared_flags: Arc<SharedFlags>,
145 clamp_graph_inputs_below_amp: Option<f32>,
146
147 last_input_overflow_log_instant: Option<Instant>,
148 last_output_underflow_log_instant: Option<Instant>,
149
150 pub(crate) extra: ProcExtra,
151
152 pub(crate) poisoned: bool,
156}
157
158pub(crate) struct FirewheelProcessorConfig {
159 pub flags: FirewheelBitFlags,
160 pub immediate_event_buffer_capacity: usize,
161 pub buffer_out_of_space_mode: BufferOutOfSpaceMode,
162 pub clamp_graph_inputs_below_amp: Option<f32>,
163 pub node_event_buffer_capacity: usize,
164 #[cfg(feature = "scheduled_events")]
165 pub scheduled_event_buffer_capacity: usize,
166}
167
168impl FirewheelProcessorInner {
169 pub(crate) fn new(
171 config: FirewheelProcessorConfig,
172 proc_channel: ProcessorChannel,
173 stream_info: &StreamInfo,
174 ) -> Self {
175 let FirewheelProcessorConfig {
176 flags,
177 immediate_event_buffer_capacity,
178 buffer_out_of_space_mode,
179 clamp_graph_inputs_below_amp,
180 node_event_buffer_capacity,
181 #[cfg(feature = "scheduled_events")]
182 scheduled_event_buffer_capacity,
183 } = config;
184
185 let ProcessorChannel {
186 shared_flags,
187 from_context_rx,
188 to_context_tx,
189 logger,
190 store,
191 profiler_tx,
192 #[cfg(feature = "scheduled_events")]
193 shared_clock_input,
194 } = proc_channel;
195
196 Self {
197 nodes: Arena::new(),
198 schedule_data: None,
199 from_graph_rx: from_context_rx,
200 to_graph_tx: to_context_tx,
201 event_scheduler: EventScheduler::new(
202 immediate_event_buffer_capacity,
203 #[cfg(feature = "scheduled_events")]
204 scheduled_event_buffer_capacity,
205 buffer_out_of_space_mode,
206 ),
207 proc_event_queue: Vec::with_capacity(node_event_buffer_capacity),
208 sample_rate: stream_info.sample_rate,
209 sample_rate_recip: stream_info.sample_rate_recip,
210 max_block_frames: stream_info.max_block_frames.get() as usize,
211 clock_samples: InstantSamples(0),
212 #[cfg(feature = "scheduled_events")]
213 shared_clock_input,
214 profiler_tx,
215 #[cfg(feature = "musical_transport")]
216 proc_transport_state: ProcTransportState::new(),
217 flags,
218 shared_flags,
219 clamp_graph_inputs_below_amp,
220 last_input_overflow_log_instant: None,
221 last_output_underflow_log_instant: None,
222 extra: ProcExtra {
223 scratch_buffers: ConstSequentialBuffer::new(
224 stream_info.max_block_frames.get() as usize
225 ),
226 declick_values: DeclickValues::new(stream_info.declick_frames),
227 logger,
228 store,
229 },
230 poisoned: false,
231 }
232 }
233}
234
235pub(crate) struct NodeEntry {
236 pub processor: Box<dyn AudioNodeProcessor>,
237 pub bypass_declick: Declicker,
238 pub is_bypassed: bool,
239 pub is_first_process: bool,
240 pub in_place_buffers: bool,
241
242 event_data: NodeEventSchedulerData,
243}
244
245pub(crate) enum ContextToProcessorMsg {
246 EventGroup(Vec<NodeEvent>),
247 NewSchedule(Box<ScheduleHeapData>),
248 SetFlags(FirewheelBitFlags),
249 #[cfg(feature = "musical_transport")]
250 SetTransportState(Box<TransportState>),
251 #[cfg(feature = "scheduled_events")]
252 ClearScheduledEvents(SmallVec<[ClearScheduledEventsEvent; 1]>),
253}
254
255#[allow(clippy::enum_variant_names)]
256pub(crate) enum ProcessorToContextMsg {
257 DropEventGroup(Vec<NodeEvent>),
258 DropSchedule(Box<ScheduleHeapData>),
259 #[cfg(feature = "musical_transport")]
260 DropTransportState(Box<TransportState>),
261 #[cfg(feature = "scheduled_events")]
262 DropClearScheduledEvents(SmallVec<[ClearScheduledEventsEvent; 1]>),
263}
264
265#[cfg(feature = "scheduled_events")]
266pub(crate) struct ClearScheduledEventsEvent {
267 pub node_id: Option<NodeID>,
269 pub event_type: ClearScheduledEventsType,
270}
271
272#[cfg(feature = "scheduled_events")]
273#[derive(Clone)]
274pub(crate) struct SharedClock {
275 pub clock_samples: InstantSamples,
276 #[cfg(feature = "musical_transport")]
277 pub current_playhead: Option<InstantMusical>,
278 #[cfg(feature = "musical_transport")]
279 pub speed_multiplier: f64,
280 #[cfg(feature = "musical_transport")]
281 pub transport_is_playing: bool,
282 pub update_instant: Instant,
283}
284
285#[cfg(feature = "scheduled_events")]
286impl Default for SharedClock {
287 fn default() -> Self {
288 Self {
289 clock_samples: InstantSamples(0),
290 #[cfg(feature = "musical_transport")]
291 current_playhead: None,
292 #[cfg(feature = "musical_transport")]
293 speed_multiplier: 1.0,
294 #[cfg(feature = "musical_transport")]
295 transport_is_playing: false,
296 update_instant: Instant::now(),
297 }
298 }
299}
300
301#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
303#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
304#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
305pub enum BufferOutOfSpaceMode {
306 #[default]
307 AllocateOnAudioThread,
312 Panic,
315 DropEvents,
321}
322
323#[derive(Default)]
324pub(crate) struct SharedFlags {
325 pub clipping_occurred: AtomicBool,
326}