firewheel_core/
sample_resource.rs

1use core::{num::NonZeroUsize, ops::Range};
2
3/// A resource of audio samples.
4pub trait SampleResource: Send + Sync + 'static {
5    /// The number of channels in this resource.
6    fn num_channels(&self) -> NonZeroUsize;
7
8    /// The length of this resource in samples (of a single channel of audio).
9    ///
10    /// Not to be confused with video frames.
11    fn len_frames(&self) -> u64;
12
13    /// Fill the given buffers with audio data starting from the given
14    /// starting frame in the resource.
15    ///
16    /// * `buffers` - The buffers to fill with data. If the length of `buffers`
17    /// is greater than the number of channels in this resource, then ignore
18    /// the extra buffers.
19    /// * `buffer_range` - The range inside each buffer slice in which to
20    /// fill with data. Do not fill any data outside of this range.
21    /// * `start_frame` - The sample (of a single channel of audio) in the
22    /// resource at which to start copying from. Not to be confused with video
23    /// frames.
24    fn fill_buffers(
25        &self,
26        buffers: &mut [&mut [f32]],
27        buffer_range: Range<usize>,
28        start_frame: u64,
29    );
30}
31
32pub struct InterleavedResourceI16 {
33    pub data: Vec<i16>,
34    pub channels: NonZeroUsize,
35}
36
37impl SampleResource for InterleavedResourceI16 {
38    fn num_channels(&self) -> NonZeroUsize {
39        self.channels
40    }
41
42    fn len_frames(&self) -> u64 {
43        (self.data.len() / self.channels.get()) as u64
44    }
45
46    fn fill_buffers(
47        &self,
48        buffers: &mut [&mut [f32]],
49        buffer_range: Range<usize>,
50        start_frame: u64,
51    ) {
52        fill_buffers_interleaved(
53            buffers,
54            buffer_range,
55            start_frame as usize,
56            self.channels,
57            &self.data,
58            pcm_i16_to_f32,
59        );
60    }
61}
62
63pub struct InterleavedResourceU16 {
64    pub data: Vec<u16>,
65    pub channels: NonZeroUsize,
66}
67
68impl SampleResource for InterleavedResourceU16 {
69    fn num_channels(&self) -> NonZeroUsize {
70        self.channels
71    }
72
73    fn len_frames(&self) -> u64 {
74        (self.data.len() / self.channels.get()) as u64
75    }
76
77    fn fill_buffers(
78        &self,
79        buffers: &mut [&mut [f32]],
80        buffer_range: Range<usize>,
81        start_frame: u64,
82    ) {
83        fill_buffers_interleaved(
84            buffers,
85            buffer_range,
86            start_frame as usize,
87            self.channels,
88            &self.data,
89            pcm_u16_to_f32,
90        );
91    }
92}
93
94pub struct InterleavedResourceF32 {
95    pub data: Vec<f32>,
96    pub channels: NonZeroUsize,
97}
98
99impl SampleResource for InterleavedResourceF32 {
100    fn num_channels(&self) -> NonZeroUsize {
101        self.channels
102    }
103
104    fn len_frames(&self) -> u64 {
105        (self.data.len() / self.channels.get()) as u64
106    }
107
108    fn fill_buffers(
109        &self,
110        buffers: &mut [&mut [f32]],
111        buffer_range: Range<usize>,
112        start_frame: u64,
113    ) {
114        fill_buffers_interleaved(
115            buffers,
116            buffer_range,
117            start_frame as usize,
118            self.channels,
119            &self.data,
120            |s| s,
121        );
122    }
123}
124
125impl SampleResource for Vec<Vec<i16>> {
126    fn num_channels(&self) -> NonZeroUsize {
127        NonZeroUsize::new(self.len()).unwrap()
128    }
129
130    fn len_frames(&self) -> u64 {
131        self[0].len() as u64
132    }
133
134    fn fill_buffers(
135        &self,
136        buffers: &mut [&mut [f32]],
137        buffer_range: Range<usize>,
138        start_frame: u64,
139    ) {
140        fill_buffers_deinterleaved(
141            buffers,
142            buffer_range,
143            start_frame as usize,
144            self.as_slice(),
145            pcm_i16_to_f32,
146        );
147    }
148}
149
150impl SampleResource for Vec<Vec<u16>> {
151    fn num_channels(&self) -> NonZeroUsize {
152        NonZeroUsize::new(self.len()).unwrap()
153    }
154
155    fn len_frames(&self) -> u64 {
156        self[0].len() as u64
157    }
158
159    fn fill_buffers(
160        &self,
161        buffers: &mut [&mut [f32]],
162        buffer_range: Range<usize>,
163        start_frame: u64,
164    ) {
165        fill_buffers_deinterleaved(
166            buffers,
167            buffer_range,
168            start_frame as usize,
169            self.as_slice(),
170            pcm_u16_to_f32,
171        );
172    }
173}
174
175impl SampleResource for Vec<Vec<f32>> {
176    fn num_channels(&self) -> NonZeroUsize {
177        NonZeroUsize::new(self.len()).unwrap()
178    }
179
180    fn len_frames(&self) -> u64 {
181        self[0].len() as u64
182    }
183
184    fn fill_buffers(
185        &self,
186        buffers: &mut [&mut [f32]],
187        buffer_range: Range<usize>,
188        start_frame: u64,
189    ) {
190        fill_buffers_deinterleaved_f32(buffers, buffer_range, start_frame as usize, self);
191    }
192}
193
194#[inline]
195pub fn pcm_i16_to_f32(s: i16) -> f32 {
196    f32::from(s) * (1.0 / core::i16::MAX as f32)
197}
198
199#[inline]
200pub fn pcm_u16_to_f32(s: u16) -> f32 {
201    ((f32::from(s)) * (2.0 / core::u16::MAX as f32)) - 1.0
202}
203
204/// A helper method to fill buffers from a resource of interleaved samples.
205pub fn fill_buffers_interleaved<T: Clone + Copy>(
206    buffers: &mut [&mut [f32]],
207    buffer_range: Range<usize>,
208    start_frame: usize,
209    channels: NonZeroUsize,
210    data: &[T],
211    convert: impl Fn(T) -> f32,
212) {
213    let start_frame = start_frame as usize;
214    let channels = channels.get();
215
216    let frames = buffer_range.end - buffer_range.start;
217
218    if channels == 1 {
219        // Mono, no need to deinterleave.
220        for (buf_s, &src_s) in buffers[0][buffer_range.clone()]
221            .iter_mut()
222            .zip(&data[start_frame..start_frame + frames])
223        {
224            *buf_s = convert(src_s);
225        }
226        return;
227    }
228
229    if channels == 2 && buffers.len() >= 2 {
230        // Provide an optimized loop for stereo.
231        let (buf0, buf1) = buffers.split_first_mut().unwrap();
232        let buf0 = &mut buf0[buffer_range.clone()];
233        let buf1 = &mut buf1[0][buffer_range.clone()];
234
235        let src_slice = &data[start_frame * 2..(start_frame + frames) * 2];
236
237        for (src_chunk, (buf0_s, buf1_s)) in src_slice
238            .chunks_exact(2)
239            .zip(buf0.iter_mut().zip(buf1.iter_mut()))
240        {
241            *buf0_s = convert(src_chunk[0]);
242            *buf1_s = convert(src_chunk[1]);
243        }
244
245        return;
246    }
247
248    let src_slice = &data[start_frame * channels..(start_frame + frames) * channels];
249    for (ch_i, buf_ch) in (0..channels).zip(buffers.iter_mut()) {
250        for (src_chunk, buf_s) in src_slice
251            .chunks_exact(channels)
252            .zip(buf_ch[buffer_range.clone()].iter_mut())
253        {
254            *buf_s = convert(src_chunk[ch_i]);
255        }
256    }
257}
258
259/// A helper method to fill buffers from a resource of deinterleaved samples.
260pub fn fill_buffers_deinterleaved<T: Clone + Copy, V: AsRef<[T]>>(
261    buffers: &mut [&mut [f32]],
262    buffer_range: Range<usize>,
263    start_frame: usize,
264    data: &[V],
265    convert: impl Fn(T) -> f32,
266) {
267    let start_frame = start_frame as usize;
268    let frames = buffer_range.end - buffer_range.start;
269
270    if data.len() == 2 && buffers.len() >= 2 {
271        // Provide an optimized loop for stereo.
272        let (buf0, buf1) = buffers.split_first_mut().unwrap();
273        let buf0 = &mut buf0[buffer_range.clone()];
274        let buf1 = &mut buf1[0][buffer_range.clone()];
275        let s0 = &data[0].as_ref()[start_frame..start_frame + frames];
276        let s1 = &data[1].as_ref()[start_frame..start_frame + frames];
277
278        for i in 0..frames {
279            buf0[i] = convert(s0[i]);
280            buf1[i] = convert(s1[i]);
281        }
282
283        return;
284    }
285
286    for (buf, ch) in buffers.iter_mut().zip(data.iter()) {
287        for (buf_s, &ch_s) in buf[buffer_range.clone()]
288            .iter_mut()
289            .zip(ch.as_ref()[start_frame..start_frame + frames].iter())
290        {
291            *buf_s = convert(ch_s);
292        }
293    }
294}
295
296/// A helper method to fill buffers from a resource of deinterleaved `f32` samples.
297pub fn fill_buffers_deinterleaved_f32<V: AsRef<[f32]>>(
298    buffers: &mut [&mut [f32]],
299    buffer_range: Range<usize>,
300    start_frame: usize,
301    data: &[V],
302) {
303    let start_frame = start_frame as usize;
304
305    for (buf, ch) in buffers.iter_mut().zip(data.iter()) {
306        buf[buffer_range.clone()].copy_from_slice(
307            &ch.as_ref()[start_frame..start_frame + buffer_range.end - buffer_range.start],
308        );
309    }
310}
311
312#[cfg(feature = "symphonium")]
313/// A wrapper around [`symphonium::DecodedAudio`] which implements the
314/// [`SampleResource`] trait.
315pub struct DecodedAudio(pub symphonium::DecodedAudio);
316
317#[cfg(feature = "symphonium")]
318impl DecodedAudio {
319    pub fn duration_seconds(&self) -> f64 {
320        self.0.frames() as f64 / self.0.sample_rate() as f64
321    }
322
323    pub fn into_dyn_resource(self) -> crate::collector::ArcGc<dyn SampleResource> {
324        crate::collector::ArcGc::new_unsized(|| {
325            bevy_platform::sync::Arc::new(self) as bevy_platform::sync::Arc<dyn SampleResource>
326        })
327    }
328}
329
330#[cfg(feature = "symphonium")]
331impl Into<crate::collector::ArcGc<dyn SampleResource>> for DecodedAudio {
332    fn into(self) -> crate::collector::ArcGc<dyn SampleResource> {
333        self.into_dyn_resource()
334    }
335}
336
337#[cfg(feature = "symphonium")]
338impl SampleResource for DecodedAudio {
339    fn num_channels(&self) -> NonZeroUsize {
340        NonZeroUsize::new(self.0.channels()).unwrap()
341    }
342
343    fn len_frames(&self) -> u64 {
344        self.0.frames() as u64
345    }
346
347    fn fill_buffers(
348        &self,
349        buffers: &mut [&mut [f32]],
350        buffer_range: Range<usize>,
351        start_frame: u64,
352    ) {
353        let channels = self.0.channels().min(buffers.len());
354
355        if channels == 2 {
356            let (b1, b2) = buffers.split_first_mut().unwrap();
357
358            self.0.fill_stereo(
359                start_frame as usize,
360                &mut b1[buffer_range.clone()],
361                &mut b2[0][buffer_range.clone()],
362            );
363        } else {
364            for (ch_i, b) in buffers[0..channels].iter_mut().enumerate() {
365                self.0
366                    .fill_channel(ch_i, start_frame as usize, &mut b[buffer_range.clone()])
367                    .unwrap();
368            }
369        }
370    }
371}
372
373#[cfg(feature = "symphonium")]
374impl From<symphonium::DecodedAudio> for DecodedAudio {
375    fn from(data: symphonium::DecodedAudio) -> Self {
376        Self(data)
377    }
378}
379
380#[cfg(feature = "symphonium")]
381/// A wrapper around [`symphonium::DecodedAudioF32`] which implements the
382/// [`SampleResource`] trait.
383pub struct DecodedAudioF32(pub symphonium::DecodedAudioF32);
384
385#[cfg(feature = "symphonium")]
386impl DecodedAudioF32 {
387    pub fn duration_seconds(&self, sample_rate: u32) -> f64 {
388        self.0.frames() as f64 / sample_rate as f64
389    }
390}
391
392#[cfg(feature = "symphonium")]
393impl SampleResource for DecodedAudioF32 {
394    fn num_channels(&self) -> NonZeroUsize {
395        NonZeroUsize::new(self.0.channels()).unwrap()
396    }
397
398    fn len_frames(&self) -> u64 {
399        self.0.frames() as u64
400    }
401
402    fn fill_buffers(
403        &self,
404        buffers: &mut [&mut [f32]],
405        buffer_range: Range<usize>,
406        start_frame: u64,
407    ) {
408        fill_buffers_deinterleaved_f32(buffers, buffer_range, start_frame as usize, &self.0.data);
409    }
410}
411
412#[cfg(feature = "symphonium")]
413impl From<symphonium::DecodedAudioF32> for DecodedAudioF32 {
414    fn from(data: symphonium::DecodedAudioF32) -> Self {
415        Self(data)
416    }
417}
418
419/// A helper method to load an audio file from a path using Symphonium.
420///
421/// * `loader` - The symphonium loader.
422/// * `path`` - The path to the audio file stored on disk.
423/// * `sample_rate` - The sample rate of the audio stream.
424/// * `resample_quality` - The quality of the resampler to use.
425#[cfg(feature = "symphonium")]
426pub fn load_audio_file<P: AsRef<std::path::Path>>(
427    loader: &mut symphonium::SymphoniumLoader,
428    path: P,
429    #[cfg(feature = "resampler")] sample_rate: core::num::NonZeroU32,
430    #[cfg(feature = "resampler")] resample_quality: symphonium::ResampleQuality,
431) -> Result<DecodedAudio, symphonium::error::LoadError> {
432    loader
433        .load(
434            path,
435            #[cfg(feature = "resampler")]
436            Some(sample_rate.get()),
437            #[cfg(feature = "resampler")]
438            resample_quality,
439            None,
440        )
441        .map(|d| DecodedAudio(d))
442}
443
444/// A helper method to load an audio file from a custom source using Symphonium.
445///
446/// * `loader` - The symphonium loader.
447/// * `source` - The audio source which implements the [`MediaSource`] trait.
448/// * `hint` -  An optional hint to help the format registry guess what format reader is appropriate.
449/// * `sample_rate` - The sample rate of the audio stream.
450/// * `resample_quality` - The quality of the resampler to use.
451///
452/// [`MediaSource`]: symphonium::symphonia::core::io::MediaSource
453#[cfg(feature = "symphonium")]
454pub fn load_audio_file_from_source(
455    loader: &mut symphonium::SymphoniumLoader,
456    source: Box<dyn symphonium::symphonia::core::io::MediaSource>,
457    hint: Option<symphonium::symphonia::core::probe::Hint>,
458    #[cfg(feature = "resampler")] sample_rate: core::num::NonZeroU32,
459    #[cfg(feature = "resampler")] resample_quality: symphonium::ResampleQuality,
460) -> Result<DecodedAudio, symphonium::error::LoadError> {
461    loader
462        .load_from_source(
463            source,
464            hint,
465            #[cfg(feature = "resampler")]
466            Some(sample_rate.get()),
467            #[cfg(feature = "resampler")]
468            resample_quality,
469            None,
470        )
471        .map(|d| DecodedAudio(d))
472}
473
474#[cfg(feature = "symphonium")]
475/// A helper method to convert a [`symphonium::DecodedAudio`] resource into
476/// a [`SampleResource`].
477pub fn decoded_to_resource(
478    data: symphonium::DecodedAudio,
479) -> bevy_platform::sync::Arc<dyn SampleResource> {
480    bevy_platform::sync::Arc::new(DecodedAudio(data))
481}
482
483#[cfg(feature = "symphonium")]
484/// A helper method to convert a [`symphonium::DecodedAudioF32`] resource into
485/// a [`SampleResource`].
486pub fn decoded_f32_to_resource(
487    data: symphonium::DecodedAudioF32,
488) -> bevy_platform::sync::Arc<dyn SampleResource> {
489    bevy_platform::sync::Arc::new(DecodedAudioF32(data))
490}