1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub mod backend;
4mod context;
5pub mod error;
6pub mod graph;
7pub mod processor;
8mod time;
9
10#[cfg(feature = "unsafe_flush_denormals_to_zero")]
11mod ftz;
12
13#[cfg(feature = "scheduled_events")]
14pub use context::ClearScheduledEventsType;
15pub use context::{ActivateInfo, ContextQueue, FirewheelConfig, FirewheelContext, FirewheelFlags};
16
17extern crate alloc;
18
19#[cfg(test)]
20mod tests {
21 use crate::{backend::BackendProcessInfo, processor::FirewheelProcessor};
22 use audioadapter_buffers::direct::InterleavedSlice;
23 use bevy_platform::sync::{
24 Arc,
25 atomic::{AtomicBool, Ordering},
26 };
27 use core::{num::NonZeroU32, time::Duration};
28 use firewheel_core::node::{
29 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
30 NodeError, StreamStatus,
31 };
32
33 use super::*;
34
35 #[test]
36 fn clap_drop_ordering() {
44 struct DummyClapPlugin {}
45 struct DummyClapPluginProcessor {
46 state: CustomState,
47 }
48 #[derive(Clone)]
49 struct CustomState {
50 plugin_main_dropped: Arc<AtomicBool>,
51 plugin_processor_dropped: Arc<AtomicBool>,
52 }
53
54 impl Drop for DummyClapPluginProcessor {
55 fn drop(&mut self) {
56 assert!(!self.state.plugin_main_dropped.load(Ordering::SeqCst));
57 self.state
58 .plugin_processor_dropped
59 .store(true, Ordering::SeqCst);
60 }
61 }
62
63 impl Drop for CustomState {
64 fn drop(&mut self) {
65 assert!(self.plugin_processor_dropped.load(Ordering::SeqCst));
66 self.plugin_main_dropped.store(true, Ordering::SeqCst);
67 }
68 }
69
70 impl AudioNode for DummyClapPlugin {
71 type Configuration = EmptyConfig;
72
73 fn info(&self, _: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
74 Ok(AudioNodeInfo::new().custom_state(CustomState {
75 plugin_main_dropped: Arc::new(AtomicBool::new(false)),
76 plugin_processor_dropped: Arc::new(AtomicBool::new(false)),
77 }))
78 }
79
80 fn construct_processor(
81 &self,
82 _: &Self::Configuration,
83 cx: ConstructProcessorContext,
84 ) -> Result<impl AudioNodeProcessor, NodeError> {
85 let state = cx.custom_state::<CustomState>().unwrap().clone();
86
87 Ok(DummyClapPluginProcessor { state })
88 }
89 }
90
91 impl AudioNodeProcessor for DummyClapPluginProcessor {}
92
93 const DUMMY_OUT_LEN: usize = 1024;
94 let mut dummy_out_buffer = vec![0.0; DUMMY_OUT_LEN];
95
96 let activate_info = ActivateInfo {
97 sample_rate: NonZeroU32::new(44100).unwrap(),
98 max_block_frames: NonZeroU32::new(DUMMY_OUT_LEN as u32).unwrap(),
99 num_stream_in_channels: 0,
100 num_stream_out_channels: 1,
101 input_to_output_latency_seconds: 0.0,
102 };
103 let process_info = BackendProcessInfo {
104 frames: DUMMY_OUT_LEN,
105 process_timestamp: None,
106 duration_since_stream_start: Duration::default(),
107 input_stream_status: StreamStatus::empty(),
108 output_stream_status: StreamStatus::empty(),
109 dropped_frames: 0,
110 process_to_playback_delay: None,
111 };
112
113 let mut process = |processor: &mut FirewheelProcessor| {
114 processor.process(
115 &InterleavedSlice::new(&[], 0, 0).unwrap(),
116 &mut InterleavedSlice::new_mut(&mut dummy_out_buffer, 1, DUMMY_OUT_LEN).unwrap(),
117 process_info.clone(),
118 );
119 };
120
121 {
123 let mut context = FirewheelContext::new(Default::default());
124 let node_id = context.add_node(DummyClapPlugin {}, None).unwrap();
125
126 let plugin_main_dropped = context
127 .node_state::<CustomState>(node_id)
128 .unwrap()
129 .plugin_main_dropped
130 .clone();
131
132 let mut processor = context.activate(activate_info.clone()).unwrap();
133
134 context.update().unwrap();
135
136 process(&mut processor);
137
138 context.remove_node(node_id).unwrap();
139
140 context.update().unwrap();
141
142 process(&mut processor);
143
144 context.update().unwrap();
145
146 assert!(plugin_main_dropped.load(Ordering::SeqCst));
147 }
148
149 {
151 let mut context = FirewheelContext::new(Default::default());
152 context.add_node(DummyClapPlugin {}, None).unwrap();
153
154 let mut processor = context.activate(activate_info.clone()).unwrap();
155
156 context.update().unwrap();
157
158 process(&mut processor);
159
160 context.update().unwrap();
161
162 let _ = processor;
163 let _ = context;
164 }
165
166 {
168 let mut context = FirewheelContext::new(Default::default());
169 context.add_node(DummyClapPlugin {}, None).unwrap();
170
171 let mut processor = context.activate(activate_info.clone()).unwrap();
172
173 context.update().unwrap();
174
175 process(&mut processor);
176
177 context.update().unwrap();
178
179 context.request_deactivate();
180
181 process(&mut processor);
183
184 let _ = context;
185 let _ = processor;
186 }
187 }
188}