#![cfg_attr(not(feature = "std"), no_std)]
pub mod backend;
mod context;
pub mod error;
pub mod graph;
pub mod processor;
mod time;
#[cfg(feature = "unsafe_flush_denormals_to_zero")]
mod ftz;
#[cfg(feature = "scheduled_events")]
pub use context::ClearScheduledEventsType;
pub use context::{ActivateInfo, ContextQueue, FirewheelConfig, FirewheelContext, FirewheelFlags};
extern crate alloc;
#[cfg(test)]
mod tests {
use crate::{backend::BackendProcessInfo, processor::FirewheelProcessor};
use audioadapter_buffers::direct::InterleavedSlice;
use bevy_platform::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use core::{num::NonZeroU32, time::Duration};
use firewheel_core::node::{
AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
NodeError, StreamStatus,
};
use super::*;
#[test]
fn clap_drop_ordering() {
struct DummyClapPlugin {}
struct DummyClapPluginProcessor {
state: CustomState,
}
#[derive(Clone)]
struct CustomState {
plugin_main_dropped: Arc<AtomicBool>,
plugin_processor_dropped: Arc<AtomicBool>,
}
impl Drop for DummyClapPluginProcessor {
fn drop(&mut self) {
assert!(!self.state.plugin_main_dropped.load(Ordering::SeqCst));
self.state
.plugin_processor_dropped
.store(true, Ordering::SeqCst);
}
}
impl Drop for CustomState {
fn drop(&mut self) {
assert!(self.plugin_processor_dropped.load(Ordering::SeqCst));
self.plugin_main_dropped.store(true, Ordering::SeqCst);
}
}
impl AudioNode for DummyClapPlugin {
type Configuration = EmptyConfig;
fn info(&self, _: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
Ok(AudioNodeInfo::new().custom_state(CustomState {
plugin_main_dropped: Arc::new(AtomicBool::new(false)),
plugin_processor_dropped: Arc::new(AtomicBool::new(false)),
}))
}
fn construct_processor(
&self,
_: &Self::Configuration,
cx: ConstructProcessorContext,
) -> Result<impl AudioNodeProcessor, NodeError> {
let state = cx.custom_state::<CustomState>().unwrap().clone();
Ok(DummyClapPluginProcessor { state })
}
}
impl AudioNodeProcessor for DummyClapPluginProcessor {}
const DUMMY_OUT_LEN: usize = 1024;
let mut dummy_out_buffer = vec![0.0; DUMMY_OUT_LEN];
let activate_info = ActivateInfo {
sample_rate: NonZeroU32::new(44100).unwrap(),
max_block_frames: NonZeroU32::new(DUMMY_OUT_LEN as u32).unwrap(),
num_stream_in_channels: 0,
num_stream_out_channels: 1,
input_to_output_latency_seconds: 0.0,
};
let process_info = BackendProcessInfo {
frames: DUMMY_OUT_LEN,
process_timestamp: None,
duration_since_stream_start: Duration::default(),
input_stream_status: StreamStatus::empty(),
output_stream_status: StreamStatus::empty(),
dropped_frames: 0,
process_to_playback_delay: None,
};
let mut process = |processor: &mut FirewheelProcessor| {
processor.process(
&InterleavedSlice::new(&[], 0, 0).unwrap(),
&mut InterleavedSlice::new_mut(&mut dummy_out_buffer, 1, DUMMY_OUT_LEN).unwrap(),
process_info.clone(),
);
};
{
let mut context = FirewheelContext::new(Default::default());
let node_id = context.add_node(DummyClapPlugin {}, None).unwrap();
let plugin_main_dropped = context
.node_state::<CustomState>(node_id)
.unwrap()
.plugin_main_dropped
.clone();
let mut processor = context.activate(activate_info.clone()).unwrap();
context.update().unwrap();
process(&mut processor);
context.remove_node(node_id).unwrap();
context.update().unwrap();
process(&mut processor);
context.update().unwrap();
assert!(plugin_main_dropped.load(Ordering::SeqCst));
}
{
let mut context = FirewheelContext::new(Default::default());
context.add_node(DummyClapPlugin {}, None).unwrap();
let mut processor = context.activate(activate_info.clone()).unwrap();
context.update().unwrap();
process(&mut processor);
context.update().unwrap();
let _ = processor;
let _ = context;
}
{
let mut context = FirewheelContext::new(Default::default());
context.add_node(DummyClapPlugin {}, None).unwrap();
let mut processor = context.activate(activate_info.clone()).unwrap();
context.update().unwrap();
process(&mut processor);
context.update().unwrap();
context.request_deactivate();
process(&mut processor);
let _ = context;
let _ = processor;
}
}
}