use std::{
num::{NonZeroU32, NonZeroUsize},
ops::Range,
};
use firewheel_core::{
collector::ArcGc,
sample_resource::{SampleResource, SampleResourceInfo},
};
#[derive(Debug, Clone)]
pub struct DecodedAudio(pub symphonium::DecodedAudio);
impl DecodedAudio {
pub fn duration_seconds(&self) -> f64 {
self.0.frames() as f64 / self.0.sample_rate().get() as f64
}
pub fn into_dyn_resource(self) -> ArcGc<dyn SampleResource> {
ArcGc::new_unsized(|| {
bevy_platform::sync::Arc::new(self) as bevy_platform::sync::Arc<dyn SampleResource>
})
}
pub fn sample_rate(&self) -> NonZeroU32 {
self.0.sample_rate()
}
pub fn original_sample_rate(&self) -> NonZeroU32 {
self.0.original_sample_rate()
}
}
impl From<DecodedAudio> for ArcGc<dyn SampleResource> {
fn from(value: DecodedAudio) -> Self {
value.into_dyn_resource()
}
}
impl SampleResourceInfo for DecodedAudio {
fn num_channels(&self) -> NonZeroUsize {
NonZeroUsize::new(self.0.channels()).unwrap()
}
fn len_frames(&self) -> u64 {
self.0.frames() as u64
}
fn sample_rate(&self) -> Option<NonZeroU32> {
Some(self.0.sample_rate())
}
}
impl SampleResource for DecodedAudio {
fn fill_buffers(
&self,
buffers: &mut [&mut [f32]],
buffer_range: Range<usize>,
start_frame: u64,
) {
let channels = self.0.channels().min(buffers.len());
if channels == 2 {
let (b1, b2) = buffers.split_first_mut().unwrap();
self.0.fill_stereo(
start_frame as usize,
&mut b1[buffer_range.clone()],
&mut b2[0][buffer_range.clone()],
);
} else {
for (ch_i, b) in buffers[0..channels].iter_mut().enumerate() {
self.0
.fill_channel(ch_i, start_frame as usize, &mut b[buffer_range.clone()])
.unwrap();
}
}
}
}
impl From<symphonium::DecodedAudio> for DecodedAudio {
fn from(data: symphonium::DecodedAudio) -> Self {
Self(data)
}
}
#[derive(Debug, Clone)]
pub struct DecodedAudioF32(pub symphonium::DecodedAudioF32);
impl DecodedAudioF32 {
pub fn duration_seconds(&self, sample_rate: NonZeroU32) -> f64 {
self.0.frames() as f64 / sample_rate.get() as f64
}
pub fn sample_rate(&self) -> NonZeroU32 {
self.0.sample_rate
}
pub fn original_sample_rate(&self) -> NonZeroU32 {
self.0.original_sample_rate
}
}
impl SampleResourceInfo for DecodedAudioF32 {
fn num_channels(&self) -> NonZeroUsize {
NonZeroUsize::new(self.0.channels()).unwrap()
}
fn len_frames(&self) -> u64 {
self.0.frames() as u64
}
fn sample_rate(&self) -> Option<NonZeroU32> {
Some(self.0.sample_rate)
}
}
impl SampleResource for DecodedAudioF32 {
fn fill_buffers(
&self,
buffers: &mut [&mut [f32]],
buffer_range: Range<usize>,
start_frame: u64,
) {
firewheel_core::sample_resource::fill_buffers_deinterleaved_f32(
buffers,
buffer_range,
start_frame as usize,
&self.0.data,
);
}
}
impl From<symphonium::DecodedAudioF32> for DecodedAudioF32 {
fn from(data: symphonium::DecodedAudioF32) -> Self {
Self(data)
}
}
pub fn load_audio_file<P: AsRef<std::path::Path>>(
loader: &mut symphonium::SymphoniumLoader,
path: P,
#[cfg(feature = "resample")] target_sample_rate: Option<core::num::NonZeroU32>,
#[cfg(feature = "resample")] resample_quality: symphonium::ResampleQuality,
) -> Result<DecodedAudio, symphonium::error::LoadError> {
loader
.load(
path,
#[cfg(feature = "resample")]
target_sample_rate,
#[cfg(feature = "resample")]
resample_quality,
None,
)
.map(|d| DecodedAudio(d))
}
pub fn load_audio_file_from_source(
loader: &mut symphonium::SymphoniumLoader,
source: Box<dyn symphonium::symphonia::core::io::MediaSource>,
hint: Option<symphonium::symphonia::core::probe::Hint>,
#[cfg(feature = "resample")] target_sample_rate: Option<core::num::NonZeroU32>,
#[cfg(feature = "resample")] resample_quality: symphonium::ResampleQuality,
) -> Result<DecodedAudio, symphonium::error::LoadError> {
loader
.load_from_source(
source,
hint,
#[cfg(feature = "resample")]
target_sample_rate,
#[cfg(feature = "resample")]
resample_quality,
None,
)
.map(|d| DecodedAudio(d))
}
#[cfg(feature = "stretch")]
pub fn load_audio_file_stretched<P: AsRef<std::path::Path>>(
loader: &mut symphonium::SymphoniumLoader,
path: P,
target_sample_rate: Option<core::num::NonZeroU32>,
stretch: f64,
) -> Result<DecodedAudio, symphonium::error::LoadError> {
loader
.load_stretched(path, stretch, target_sample_rate, None)
.map(|d| DecodedAudio(d.into()))
}
#[cfg(feature = "stretch")]
pub fn load_audio_file_from_source_stretched(
loader: &mut symphonium::SymphoniumLoader,
source: Box<dyn symphonium::symphonia::core::io::MediaSource>,
hint: Option<symphonium::symphonia::core::probe::Hint>,
target_sample_rate: Option<core::num::NonZeroU32>,
stretch: f64,
) -> Result<DecodedAudio, symphonium::error::LoadError> {
loader
.load_from_source_stretched(source, hint, stretch, target_sample_rate, None)
.map(|d| DecodedAudio(d.into()))
}
pub fn decoded_to_resource(
data: symphonium::DecodedAudio,
) -> bevy_platform::sync::Arc<dyn SampleResource> {
bevy_platform::sync::Arc::new(DecodedAudio(data))
}
pub fn decoded_f32_to_resource(
data: symphonium::DecodedAudioF32,
) -> bevy_platform::sync::Arc<dyn SampleResource> {
bevy_platform::sync::Arc::new(DecodedAudioF32(data))
}