use bevy_asset::{Asset, AssetLoader};
use bevy_reflect::TypePath;
use firewheel::{DecodedAudio, DecodedAudioF32, collector::ArcGc, sample_resource::SampleResource};
use std::{num::NonZeroU32, sync::Arc};
#[derive(Asset, TypePath, Clone)]
pub struct AudioSample {
sample: ArcGc<dyn SampleResource>,
original_sample_rate: NonZeroU32,
}
impl AudioSample {
pub fn new<S: SampleResource>(sample: S, original_sample_rate: NonZeroU32) -> Self {
Self {
sample: ArcGc::new_unsized(|| Arc::new(sample) as _),
original_sample_rate,
}
}
pub fn get(&self) -> ArcGc<dyn SampleResource> {
self.sample.clone()
}
pub fn original_sample_rate(&self) -> NonZeroU32 {
self.original_sample_rate
}
}
impl From<DecodedAudioF32> for AudioSample {
fn from(source: DecodedAudioF32) -> Self {
Self {
original_sample_rate: source.original_sample_rate(),
sample: ArcGc::new_unsized(|| Arc::new(source) as _),
}
}
}
impl From<DecodedAudio> for AudioSample {
fn from(source: DecodedAudio) -> Self {
Self {
original_sample_rate: source.original_sample_rate(),
sample: ArcGc::new_unsized(|| Arc::new(source) as _),
}
}
}
impl core::fmt::Debug for AudioSample {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Sample").finish_non_exhaustive()
}
}
#[derive(Debug, TypePath)]
pub struct SampleLoader {
pub(crate) sample_rate: crate::context::SampleRate,
}
#[derive(Debug)]
pub enum SampleLoaderError {
StdIo(std::io::Error),
Symphonium(String),
}
impl From<std::io::Error> for SampleLoaderError {
fn from(value: std::io::Error) -> Self {
Self::StdIo(value)
}
}
impl From<symphonium::error::LoadError> for SampleLoaderError {
fn from(value: symphonium::error::LoadError) -> Self {
Self::Symphonium(value.to_string())
}
}
impl std::error::Error for SampleLoaderError {}
impl std::fmt::Display for SampleLoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StdIo(stdio) => stdio.fmt(f),
Self::Symphonium(sy) => f.write_str(sy),
}
}
}
impl SampleLoader {
pub(crate) const fn extensions() -> &'static [&'static str] {
&[
#[cfg(feature = "wav")]
"wav",
#[cfg(feature = "ogg")]
"ogg",
#[cfg(feature = "mp3")]
"mp3",
#[cfg(feature = "flac")]
"flac",
#[cfg(feature = "mkv")]
"mkv",
]
}
}
impl AssetLoader for SampleLoader {
type Asset = AudioSample;
type Settings = ();
type Error = SampleLoaderError;
async fn load(
&self,
reader: &mut dyn bevy_asset::io::Reader,
_settings: &Self::Settings,
load_context: &mut bevy_asset::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut hint = symphonia::core::probe::Hint::new();
hint.with_extension(&load_context.path().to_string());
let mut loader = symphonium::SymphoniumLoader::new();
let source = firewheel::load_audio_file_from_source(
&mut loader,
Box::new(std::io::Cursor::new(bytes)),
Some(hint),
Some(self.sample_rate.get()),
Default::default(),
)?;
Ok(source.into())
}
fn extensions(&self) -> &[&str] {
Self::extensions()
}
}