use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_platform::sync;
use firewheel::{FirewheelConfig, FirewheelContext, clock::AudioClock};
use std::{
any::{Any, TypeId},
collections::HashMap,
num::NonZeroU32,
};
pub mod graph;
pub(crate) struct ContextPlugin;
impl Plugin for ContextPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AudioContextConfig>()
.add_plugins(graph::GraphPlugin)
.add_systems(PreStartup, initialize_context);
}
}
#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(target_arch = "wasm32")]
use web::InnerContext;
#[cfg(not(target_arch = "wasm32"))]
mod os;
#[cfg(not(target_arch = "wasm32"))]
use os::InnerContext;
#[derive(Debug, Resource)]
pub struct AudioContext(InnerContext);
impl AudioContext {
pub fn new(settings: FirewheelConfig) -> Self {
AudioContext(InnerContext::new(settings))
}
pub fn now(&mut self) -> AudioClock {
self.with(|c| c.audio_clock_corrected())
}
pub fn with<F, O>(&mut self, f: F) -> O
where
F: FnOnce(&mut FirewheelContext) -> O + Send,
O: Send + 'static,
{
self.with_store(|context, _| f(context))
}
pub(crate) fn with_store<F, O>(&mut self, f: F) -> O
where
F: FnOnce(&mut FirewheelContext, &mut LocalStore) -> O + Send,
O: Send + 'static,
{
self.0.with_store(f)
}
}
pub(crate) struct AudioThreadState {
context: FirewheelContext,
store: LocalStore,
}
impl AudioThreadState {
fn new(settings: FirewheelConfig) -> Self {
Self {
context: FirewheelContext::new(settings),
store: LocalStore::default(),
}
}
}
#[allow(dead_code)]
#[derive(Default)]
pub(crate) struct LocalStore(HashMap<TypeId, Box<dyn Any>>);
#[allow(dead_code)]
impl LocalStore {
pub(crate) fn insert<T: 'static>(&mut self, value: T) -> Option<T> {
self.0
.insert(TypeId::of::<T>(), Box::new(value))
.map(|value| {
*value
.downcast()
.expect("stored type should match its `TypeId`")
})
}
pub(crate) fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.0.get_mut(&TypeId::of::<T>()).map(|value| {
value
.downcast_mut()
.expect("stored type should match its `TypeId`")
})
}
pub(crate) fn remove<T: 'static>(&mut self) -> Option<T> {
self.0.remove(&TypeId::of::<T>()).map(|value| {
*value
.downcast()
.expect("stored type should match its `TypeId`")
})
}
}
#[derive(Resource, Debug)]
pub struct AudioContextConfig(pub FirewheelConfig);
impl Default for AudioContextConfig {
fn default() -> Self {
Self(FirewheelConfig {
channel_capacity: 128,
..Default::default()
})
}
}
#[derive(Resource, Debug, Clone)]
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
pub struct SampleRate(pub(crate) sync::Arc<sync::atomic::AtomicU32>);
impl SampleRate {
pub fn new(rate: NonZeroU32) -> Self {
Self(sync::Arc::new(sync::atomic::AtomicU32::new(rate.get())))
}
pub fn get(&self) -> NonZeroU32 {
self.0
.load(sync::atomic::Ordering::Relaxed)
.try_into()
.unwrap()
}
pub fn set(&self, rate: NonZeroU32) {
self.0.store(rate.get(), sync::atomic::Ordering::Relaxed)
}
}
fn initialize_context(firewheel_config: Res<AudioContextConfig>, mut commands: Commands) -> Result {
let context = AudioContext::new(firewheel_config.0);
commands.insert_resource(context);
Ok(())
}
#[derive(Event, Debug)]
pub struct StreamStartEvent {
pub sample_rate: NonZeroU32,
}
#[derive(Event, Debug)]
pub struct PreStreamRestartEvent;
pub fn pre_restart_stream(mut commands: Commands) {
commands.trigger(PreStreamRestartEvent);
}
#[derive(Event, Debug)]
pub struct StreamRestartEvent {
pub previous_rate: NonZeroU32,
pub current_rate: NonZeroU32,
}