#![warn(missing_docs)]
#![warn(clippy::pedantic)]
#![warn(clippy::undocumented_unsafe_blocks)]
#![allow(clippy::module_name_repetitions)]
use std::{collections::VecDeque, marker::PhantomData, sync::Arc};
use bevy::{
asset::{Asset, HandleId},
prelude::{AddAsset, App, CoreStage, Handle as BevyHandle, Plugin, Resource},
reflect::TypeUuid,
};
use frames::{FromFrame, Mono, Stereo};
use oddio::{Frame, Frames, FramesSignal, Gain, Sample, Seek, Signal, SpatialOptions, Speed};
pub use oddio;
use output::{
play_queued_audio,
spatial::{
play_queued_spatial_audio, play_queued_spatial_buffered_audio, SpatialAudioOutput,
SpatialAudioSink, SpatialAudioSinks, SpatialBufferedAudioSink, SpatialBufferedAudioSinks,
},
AudioOutput, AudioSink, AudioSinks,
};
use parking_lot::RwLock;
pub mod builtins;
pub mod frames;
pub use frames::*;
mod loader;
pub mod output;
struct AudioToPlay<Source>
where
Source: ToSignal + Asset,
{
source_handle: BevyHandle<Source>,
stop_handle: HandleId,
settings: Source::Settings,
spatial_settings: Option<SpatialSettings>,
}
struct SpatialSettings {
options: SpatialOptions,
buffered_settings: Option<BufferedSettings>,
}
struct BufferedSettings {
max_distance: f32,
rate: u32,
buffer_duration: f32,
}
#[derive(Resource)]
pub struct Audio<F, Source = AudioSource<F>>
where
Source: ToSignal + Asset,
F: Frame,
{
queue: RwLock<VecDeque<AudioToPlay<Source>>>,
_frame: PhantomData<fn() -> F>,
}
impl<F, Source> Audio<F, Source>
where
Source: ToSignal + Asset,
F: Frame,
{
pub fn play(
&mut self,
source_handle: BevyHandle<Source>,
settings: Source::Settings,
) -> BevyHandle<AudioSink<Source>> {
let stop_handle = HandleId::random::<AudioSink<Source>>();
let audio_to_play = AudioToPlay {
source_handle,
stop_handle,
settings,
spatial_settings: None,
};
self.queue.write().push_back(audio_to_play);
BevyHandle::<AudioSink<Source>>::weak(stop_handle)
}
}
impl<F, Source> Default for Audio<F, Source>
where
Source: ToSignal + Asset,
F: Frame,
{
fn default() -> Self {
Self {
queue: RwLock::default(),
_frame: PhantomData,
}
}
}
#[derive(Clone, TypeUuid)]
#[uuid = "2b024eb6-88f1-4001-b678-0446f2fab0f4"]
pub struct AudioSource<F: Frame> {
pub frames: Arc<Frames<F>>,
}
pub trait ToSignal {
type Settings: Send + Sync;
type Signal: Signal + Send;
fn to_signal(&self, settings: Self::Settings) -> Self::Signal;
}
impl<F: Frame + Send + Sync + Copy> ToSignal for AudioSource<F> {
type Settings = f64;
type Signal = Gain<Speed<FramesSignal<F>>>;
fn to_signal(&self, settings: Self::Settings) -> Self::Signal {
Gain::new(Speed::new(FramesSignal::new(self.frames.clone(), settings)))
}
}
pub struct AudioPlugin;
impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AudioOutput<1, Mono>>()
.init_resource::<AudioOutput<1, Sample>>()
.init_resource::<AudioOutput<2, Stereo>>()
.add_audio_source::<1, Mono, AudioSource<Mono>>()
.add_audio_source::<2, Stereo, AudioSource<Stereo>>()
.add_audio_source::<1, Sample, builtins::sine::Sine>()
.init_resource::<SpatialAudioOutput>()
.add_spatial_audio_source::<builtins::sine::Sine>();
#[cfg(feature = "flac")]
app.init_asset_loader::<loader::flac_loader::FlacLoader>();
#[cfg(feature = "mp3")]
app.init_asset_loader::<loader::mp3_loader::Mp3Loader>();
#[cfg(feature = "ogg")]
app.init_asset_loader::<loader::ogg_loader::OggLoader>();
#[cfg(feature = "wav")]
app.init_asset_loader::<loader::wav_loader::WavLoader>();
}
}
pub trait AudioApp {
fn add_audio_source<const N: usize, F, Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = F> + Send,
F: Frame + FromFrame<[Sample; N]> + 'static;
fn add_spatial_audio_source<Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = Sample> + Seek + Send;
fn add_spatial_buffered_audio_source<Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = Sample> + Send;
}
impl AudioApp for App {
fn add_audio_source<const N: usize, F, Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = F> + Send,
F: Frame + FromFrame<[Sample; N]> + 'static,
{
self.add_asset::<Source>()
.add_asset::<AudioSink<Source>>()
.init_resource::<Audio<F, Source>>()
.init_resource::<AudioSinks<Source>>()
.add_system_to_stage(CoreStage::PostUpdate, play_queued_audio::<N, F, Source>)
}
fn add_spatial_audio_source<Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = Sample> + Seek + Send,
{
self.add_asset::<SpatialAudioSink<Source>>()
.init_resource::<SpatialAudioSinks<Source>>()
.add_system_to_stage(CoreStage::PostUpdate, play_queued_spatial_audio::<Source>)
}
fn add_spatial_buffered_audio_source<Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = Sample> + Send,
{
self.add_asset::<SpatialBufferedAudioSink<Source>>()
.init_resource::<SpatialBufferedAudioSinks<Source>>()
.add_system_to_stage(
CoreStage::PostUpdate,
play_queued_spatial_buffered_audio::<Source>,
)
}
}
impl AudioApp for &mut App {
fn add_audio_source<const N: usize, F, Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = F> + Send,
F: Frame + FromFrame<[Sample; N]> + 'static,
{
App::add_audio_source::<N, F, Source>(self);
self
}
fn add_spatial_audio_source<Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = Sample> + Seek + Send,
{
App::add_spatial_audio_source::<Source>(self);
self
}
fn add_spatial_buffered_audio_source<Source>(&mut self) -> &mut Self
where
Source: ToSignal + Asset + Send,
Source::Signal: Signal<Frame = Sample> + Send,
{
App::add_spatial_buffered_audio_source::<Source>(self);
self
}
}
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
struct DocTestsForReadMe;