firewheel_symphonium/
lib.rs1use std::{
2 num::{NonZeroU32, NonZeroUsize},
3 ops::Range,
4};
5
6use firewheel_core::{
7 collector::ArcGc,
8 sample_resource::{SampleResource, SampleResourceInfo},
9};
10
11#[derive(Debug, Clone)]
14pub struct DecodedAudio(pub symphonium::DecodedAudio);
15
16impl DecodedAudio {
17 pub fn duration_seconds(&self) -> f64 {
18 self.0.frames() as f64 / self.0.sample_rate().get() as f64
19 }
20
21 pub fn into_dyn_resource(self) -> ArcGc<dyn SampleResource> {
22 ArcGc::new_unsized(|| {
23 bevy_platform::sync::Arc::new(self) as bevy_platform::sync::Arc<dyn SampleResource>
24 })
25 }
26
27 pub fn sample_rate(&self) -> NonZeroU32 {
29 self.0.sample_rate()
30 }
31
32 pub fn original_sample_rate(&self) -> NonZeroU32 {
34 self.0.original_sample_rate()
35 }
36}
37
38impl From<DecodedAudio> for ArcGc<dyn SampleResource> {
39 fn from(value: DecodedAudio) -> Self {
40 value.into_dyn_resource()
41 }
42}
43
44impl SampleResourceInfo for DecodedAudio {
45 fn num_channels(&self) -> NonZeroUsize {
46 NonZeroUsize::new(self.0.channels()).unwrap()
47 }
48
49 fn len_frames(&self) -> u64 {
50 self.0.frames() as u64
51 }
52
53 fn sample_rate(&self) -> Option<NonZeroU32> {
54 Some(self.0.sample_rate())
55 }
56}
57
58impl SampleResource for DecodedAudio {
59 fn fill_buffers(
60 &self,
61 buffers: &mut [&mut [f32]],
62 buffer_range: Range<usize>,
63 start_frame: u64,
64 ) {
65 let channels = self.0.channels().min(buffers.len());
66
67 if channels == 2 {
68 let (b1, b2) = buffers.split_first_mut().unwrap();
69
70 self.0.fill_stereo(
71 start_frame as usize,
72 &mut b1[buffer_range.clone()],
73 &mut b2[0][buffer_range.clone()],
74 );
75 } else {
76 for (ch_i, b) in buffers[0..channels].iter_mut().enumerate() {
77 self.0
78 .fill_channel(ch_i, start_frame as usize, &mut b[buffer_range.clone()])
79 .unwrap();
80 }
81 }
82 }
83}
84
85impl From<symphonium::DecodedAudio> for DecodedAudio {
86 fn from(data: symphonium::DecodedAudio) -> Self {
87 Self(data)
88 }
89}
90
91#[derive(Debug, Clone)]
94pub struct DecodedAudioF32(pub symphonium::DecodedAudioF32);
95
96impl DecodedAudioF32 {
97 pub fn duration_seconds(&self, sample_rate: NonZeroU32) -> f64 {
98 self.0.frames() as f64 / sample_rate.get() as f64
99 }
100
101 pub fn sample_rate(&self) -> NonZeroU32 {
103 self.0.sample_rate
104 }
105
106 pub fn original_sample_rate(&self) -> NonZeroU32 {
108 self.0.original_sample_rate
109 }
110}
111
112impl SampleResourceInfo for DecodedAudioF32 {
113 fn num_channels(&self) -> NonZeroUsize {
114 NonZeroUsize::new(self.0.channels()).unwrap()
115 }
116
117 fn len_frames(&self) -> u64 {
118 self.0.frames() as u64
119 }
120
121 fn sample_rate(&self) -> Option<NonZeroU32> {
122 Some(self.0.sample_rate)
123 }
124}
125
126impl SampleResource for DecodedAudioF32 {
127 fn fill_buffers(
128 &self,
129 buffers: &mut [&mut [f32]],
130 buffer_range: Range<usize>,
131 start_frame: u64,
132 ) {
133 firewheel_core::sample_resource::fill_buffers_deinterleaved_f32(
134 buffers,
135 buffer_range,
136 start_frame as usize,
137 &self.0.data,
138 );
139 }
140}
141
142impl From<symphonium::DecodedAudioF32> for DecodedAudioF32 {
143 fn from(data: symphonium::DecodedAudioF32) -> Self {
144 Self(data)
145 }
146}
147
148pub fn load_audio_file<P: AsRef<std::path::Path>>(
160 loader: &mut symphonium::SymphoniumLoader,
161 path: P,
162 #[cfg(feature = "resample")] target_sample_rate: Option<core::num::NonZeroU32>,
163 #[cfg(feature = "resample")] resample_quality: symphonium::ResampleQuality,
164) -> Result<DecodedAudio, symphonium::error::LoadError> {
165 loader
166 .load(
167 path,
168 #[cfg(feature = "resample")]
169 target_sample_rate,
170 #[cfg(feature = "resample")]
171 resample_quality,
172 None,
173 )
174 .map(|d| DecodedAudio(d))
175}
176
177pub fn load_audio_file_from_source(
192 loader: &mut symphonium::SymphoniumLoader,
193 source: Box<dyn symphonium::symphonia::core::io::MediaSource>,
194 hint: Option<symphonium::symphonia::core::probe::Hint>,
195 #[cfg(feature = "resample")] target_sample_rate: Option<core::num::NonZeroU32>,
196 #[cfg(feature = "resample")] resample_quality: symphonium::ResampleQuality,
197) -> Result<DecodedAudio, symphonium::error::LoadError> {
198 loader
199 .load_from_source(
200 source,
201 hint,
202 #[cfg(feature = "resample")]
203 target_sample_rate,
204 #[cfg(feature = "resample")]
205 resample_quality,
206 None,
207 )
208 .map(|d| DecodedAudio(d))
209}
210
211#[cfg(feature = "stretch")]
224pub fn load_audio_file_stretched<P: AsRef<std::path::Path>>(
225 loader: &mut symphonium::SymphoniumLoader,
226 path: P,
227 target_sample_rate: Option<core::num::NonZeroU32>,
228 stretch: f64,
229) -> Result<DecodedAudio, symphonium::error::LoadError> {
230 loader
231 .load_stretched(path, stretch, target_sample_rate, None)
232 .map(|d| DecodedAudio(d.into()))
233}
234
235#[cfg(feature = "stretch")]
250pub fn load_audio_file_from_source_stretched(
251 loader: &mut symphonium::SymphoniumLoader,
252 source: Box<dyn symphonium::symphonia::core::io::MediaSource>,
253 hint: Option<symphonium::symphonia::core::probe::Hint>,
254 target_sample_rate: Option<core::num::NonZeroU32>,
255 stretch: f64,
256) -> Result<DecodedAudio, symphonium::error::LoadError> {
257 loader
258 .load_from_source_stretched(source, hint, stretch, target_sample_rate, None)
259 .map(|d| DecodedAudio(d.into()))
260}
261
262pub fn decoded_to_resource(
265 data: symphonium::DecodedAudio,
266) -> bevy_platform::sync::Arc<dyn SampleResource> {
267 bevy_platform::sync::Arc::new(DecodedAudio(data))
268}
269
270pub fn decoded_f32_to_resource(
273 data: symphonium::DecodedAudioF32,
274) -> bevy_platform::sync::Arc<dyn SampleResource> {
275 bevy_platform::sync::Arc::new(DecodedAudioF32(data))
276}