Skip to main content

phonic/source/file/
preloaded.rs

1use std::{
2    ops::Range,
3    path::Path,
4    sync::{mpsc::SyncSender, Arc},
5    time::Duration,
6};
7
8use crossbeam_queue::ArrayQueue;
9
10use super::{common::FileSourceImpl, FilePlaybackMessage, FilePlaybackOptions, FileSource};
11
12use crate::{
13    error::Error,
14    source::{
15        file::{AudioFileBuffer, PlaybackId, PlaybackStatusContext, PlaybackStatusEvent},
16        Source, SourceTime,
17    },
18    utils::{buffer::clear_buffer, fader::FaderState},
19};
20
21// -------------------------------------------------------------------------------------------------
22
23/// A buffered, clonable [`FileSource`], which decodes the entire file into a buffer before its
24/// played back.
25///
26/// Buffers of preloaded file sources are shared (wrapped in an Arc), so cloning a source is
27/// very cheap as this only copies a buffer reference and not the buffer itself. This way a file
28/// can be pre-loaded once and can then be cloned and reused as often as necessary.
29pub struct PreloadedFileSource {
30    file_buffer: Arc<AudioFileBuffer>,
31    file_source: FileSourceImpl,
32    playback_repeat: usize,
33    playback_repeat_count: usize,
34    playback_pos: usize,
35    playback_pos_eof: bool,
36    loop_range_override: Option<Range<u64>>,
37}
38
39impl PreloadedFileSource {
40    /// Create a new preloaded file source with the given audio file path.
41    pub fn from_file<P: AsRef<Path>>(
42        path: P,
43        options: FilePlaybackOptions,
44        output_sample_rate: u32,
45    ) -> Result<Self, Error> {
46        // Memorize file path for progress
47        let file_path = path.as_ref().to_string_lossy().to_string();
48        Self::from_shared_buffer(
49            Arc::new(AudioFileBuffer::from_file(path)?),
50            &file_path,
51            options,
52            output_sample_rate,
53        )
54    }
55
56    /// Create a new preloaded file source with the given raw **encoded** file stream buffer.
57    pub fn from_file_buffer(
58        file_buffer: Vec<u8>,
59        file_path: &str,
60        options: FilePlaybackOptions,
61        output_sample_rate: u32,
62    ) -> Result<Self, Error> {
63        Self::from_shared_buffer(
64            Arc::new(AudioFileBuffer::from_file_buffer(file_buffer)?),
65            file_path,
66            options,
67            output_sample_rate,
68        )
69    }
70
71    /// Create a new preloaded file source from the given shared, **decoded** file buffer.
72    pub fn from_shared_buffer(
73        file_buffer: Arc<AudioFileBuffer>,
74        file_path: &str,
75        options: FilePlaybackOptions,
76        output_sample_rate: u32,
77    ) -> Result<Self, Error> {
78        // validate options
79        options.validate()?;
80
81        // create common data
82        let file_source = FileSourceImpl::new(
83            file_path,
84            options,
85            file_buffer.sample_rate(),
86            file_buffer.channel_count(),
87            output_sample_rate,
88        )?;
89
90        let playback_repeat = options
91            .repeat
92            .unwrap_or(if file_buffer.loop_range().is_some() {
93                usize::MAX
94            } else {
95                0
96            });
97        let playback_repeat_count = playback_repeat;
98        let playback_pos = 0;
99        let playback_pos_eof = false;
100
101        let loop_range_override = options.loop_range.map(|(start, end)| {
102            let frame_count = file_buffer.frame_count() as u64;
103            start.min(frame_count.saturating_sub(1))..end.min(frame_count)
104        });
105
106        Ok(Self {
107            file_buffer,
108            file_source,
109            playback_repeat_count,
110            playback_repeat,
111            playback_pos,
112            playback_pos_eof,
113            loop_range_override,
114        })
115    }
116
117    /// Create a copy of this preloaded source with the given playback options.
118    pub fn clone(
119        &self,
120        options: FilePlaybackOptions,
121        output_sample_rate: u32,
122    ) -> Result<Self, Error> {
123        let file_buffer = Arc::clone(&self.file_buffer);
124        Self::from_shared_buffer(
125            file_buffer,
126            &self.file_source.file_path,
127            options,
128            output_sample_rate,
129        )
130    }
131
132    /// Access to the shared file buffer.
133    pub fn file_buffer(&self) -> Arc<AudioFileBuffer> {
134        Arc::clone(&self.file_buffer)
135    }
136
137    /// Set a new playback position for this source.
138    pub fn seek(&mut self, position: Duration) {
139        if !self.is_exhausted() {
140            let buffer_pos = position.as_secs_f64()
141                * self.file_buffer.sample_rate() as f64
142                * self.file_buffer.channel_count() as f64;
143            self.playback_pos = (buffer_pos as usize).clamp(0, self.file_buffer.buffer().len());
144            self.file_source.resampler.reset();
145        }
146    }
147
148    /// Returns the active loop range in sample frames: the override if set, else the file's
149    /// embedded loop range. Returns `None` when neither is set.
150    pub fn loop_range(&self) -> Option<Range<u64>> {
151        self.loop_range_override.clone().or_else(|| {
152            self.file_buffer
153                .loop_range()
154                .map(|r| r.start as u64..r.end as u64)
155        })
156    }
157
158    /// Override the file's embedded loop range with a custom one.
159    /// Pass `None` to revert to the file's embedded loop range.
160    pub fn set_loop_range(&mut self, range: Option<Range<u64>>) {
161        let frame_count = self.file_buffer.frame_count() as u64;
162        assert!(
163            range.is_none()
164                || range
165                    .as_ref()
166                    .is_some_and(|r| r.start < frame_count && r.end <= frame_count),
167            "Invalid loop range: {:?} not in range {:?}",
168            range,
169            0..frame_count
170        );
171        self.loop_range_override = range;
172    }
173
174    /// Override the file's playback option repeat settings with the given ones.
175    /// Set to 0 to disable looping, usize::MAX to repeat forever.
176    pub fn set_repeat(&mut self, repeat_count: usize) {
177        self.playback_repeat = repeat_count;
178        self.playback_repeat_count = self.playback_repeat;
179    }
180
181    /// Set the playback speed (pitch) for this source.
182    pub fn set_speed(&mut self, speed: f64, glide: Option<f32>) {
183        if !self.is_exhausted() {
184            self.file_source.samples_to_next_speed_update = 0;
185            self.file_source.target_speed = speed;
186            self.file_source.speed_glide_rate = glide.unwrap_or(0.0);
187            if self.file_source.speed_glide_rate == 0.0 {
188                self.file_source.current_speed = speed;
189                self.file_source
190                    .update_speed(self.file_buffer.sample_rate());
191            }
192        }
193    }
194
195    /// Stop the file source, starting to fade-out, when a fadeout is set, stop immediately.
196    pub fn stop(&mut self) {
197        if !self.is_exhausted() {
198            match self.file_source.fade_out_duration {
199                Some(duration) if !duration.is_zero() => {
200                    self.file_source.volume_fader.start_fade_out(duration);
201                }
202                _ => {
203                    self.file_source
204                        .send_playback_stopped_status(self.playback_pos_eof);
205                    self.file_source.playback_finished = true;
206                }
207            }
208        }
209    }
210
211    /// Reset the file to start playback from the beginning.
212    pub fn reset(&mut self) {
213        // Send stopped status
214        if !self.is_exhausted() {
215            self.kill();
216        }
217        // Reset positions and playback status
218        self.playback_pos = 0;
219        self.playback_repeat_count = self.playback_repeat;
220        self.playback_pos_eof = false;
221        self.file_source.playback_started = false;
222        self.file_source.playback_finished = false;
223
224        // Reset resampler state
225        self.file_source.resampler.reset();
226        self.file_source.resampler_input_buffer.clear_range();
227
228        // Reset volume fader
229        self.file_source.volume_fader.reset();
230    }
231
232    /// Abruptly stop the source without applying fade-outs
233    pub fn kill(&mut self) {
234        if !self.is_exhausted() {
235            self.file_source
236                .send_playback_stopped_status(self.playback_pos_eof);
237            self.file_source.playback_finished = true;
238        }
239    }
240
241    /// access to the file source impl
242    #[allow(unused)]
243    pub(crate) fn file_source_impl(&self) -> &FileSourceImpl {
244        &self.file_source
245    }
246    /// Mut access to the file source impl
247    pub(crate) fn file_source_impl_mut(&mut self) -> &mut FileSourceImpl {
248        &mut self.file_source
249    }
250
251    fn process_messages(&mut self) {
252        while let Some(msg) = self.file_source.playback_message_queue.pop() {
253            match msg {
254                FilePlaybackMessage::Seek(position) => {
255                    self.seek(position);
256                }
257                FilePlaybackMessage::SetSpeed(speed, glide) => {
258                    self.set_speed(speed, glide);
259                }
260                FilePlaybackMessage::Stop => {
261                    self.stop();
262                }
263                FilePlaybackMessage::Kill => {
264                    self.kill();
265                }
266            }
267        }
268    }
269
270    fn write_buffer(&mut self, output: &mut [f32]) -> usize {
271        let mut written = 0;
272
273        let loop_range = if self.playback_repeat > 0 {
274            let channel_count = self.file_buffer.channel_count();
275            self.loop_range()
276                .map(|r| r.start as usize * channel_count..r.end as usize * channel_count)
277                .unwrap_or(0..self.file_buffer.buffer().len())
278        } else {
279            0..self.file_buffer.buffer().len()
280        };
281
282        let resampler = &mut self.file_source.resampler;
283        let resampler_input_buffer = &mut self.file_source.resampler_input_buffer;
284
285        let required_input_len = resampler.required_input_buffer_size().unwrap_or(0);
286
287        while written < output.len() {
288            // write from resampled buffer into output and apply volume
289            let remaining_input_len = loop_range.end.saturating_sub(self.playback_pos);
290            let remaining_input_buffer = &self.file_buffer.buffer()
291                [self.playback_pos..self.playback_pos + remaining_input_len];
292            let remaining_output = &mut output[written..];
293            let (input_consumed, output_written) = {
294                // pad input with zeros if resampler has input size constrains.
295                // should only happen in the last process call at the EOF
296                if remaining_input_buffer.len() < required_input_len {
297                    resampler_input_buffer.reset_range();
298                    resampler_input_buffer.copy_from(remaining_input_buffer);
299                    clear_buffer(&mut resampler_input_buffer.get_mut()[remaining_input_len..]);
300                    let (_, output_written) = resampler
301                        .process(resampler_input_buffer.get(), remaining_output)
302                        .expect("PreloadedFile resampling failed");
303                    (remaining_input_len, output_written)
304                } else {
305                    resampler
306                        .process(remaining_input_buffer, remaining_output)
307                        .expect("PreloadedFile resampling failed")
308                }
309            };
310
311            // move buffer read pos
312            self.playback_pos += input_consumed;
313            written += output_written;
314
315            // loop or stop when reaching end of file or end of loop
316            if self.playback_pos >= loop_range.end {
317                if self.playback_repeat_count > 0 {
318                    if self.playback_repeat_count != usize::MAX {
319                        self.playback_repeat_count -= 1;
320                    }
321                    self.playback_pos = loop_range.start;
322                } else {
323                    self.playback_pos_eof = true;
324                }
325            }
326            if self.playback_pos_eof && output_written == 0 {
327                // got no more output from file or resampler
328                break;
329            }
330        }
331        written
332    }
333}
334
335impl FileSource for PreloadedFileSource {
336    fn file_name(&self) -> String {
337        self.file_source.file_path.to_string()
338    }
339
340    fn playback_id(&self) -> PlaybackId {
341        self.file_source.file_id
342    }
343
344    fn playback_options(&self) -> &FilePlaybackOptions {
345        &self.file_source.options
346    }
347
348    fn playback_message_queue(&self) -> Arc<ArrayQueue<FilePlaybackMessage>> {
349        Arc::clone(&self.file_source.playback_message_queue)
350    }
351
352    fn playback_status_sender(&self) -> Option<SyncSender<PlaybackStatusEvent>> {
353        self.file_source.playback_status_send.clone()
354    }
355    fn set_playback_status_sender(&mut self, sender: Option<SyncSender<PlaybackStatusEvent>>) {
356        self.file_source.playback_status_send = sender;
357    }
358
359    fn playback_status_context(&self) -> Option<PlaybackStatusContext> {
360        self.file_source.playback_status_context.clone()
361    }
362    fn set_playback_status_context(&mut self, context: Option<PlaybackStatusContext>) {
363        self.file_source.playback_status_context = context;
364    }
365
366    fn total_frames(&self) -> Option<u64> {
367        Some(self.file_buffer.buffer().len() as u64 / self.file_buffer.channel_count() as u64)
368    }
369
370    fn current_frame_position(&self) -> u64 {
371        self.playback_pos as u64 / self.file_source.output_channel_count as u64
372    }
373
374    fn end_of_track(&self) -> bool {
375        self.file_source.playback_finished
376    }
377}
378
379impl Source for PreloadedFileSource {
380    fn channel_count(&self) -> usize {
381        self.file_source.output_channel_count
382    }
383
384    fn sample_rate(&self) -> u32 {
385        self.file_source.output_sample_rate
386    }
387
388    fn is_exhausted(&self) -> bool {
389        self.file_source.playback_finished
390    }
391
392    fn weight(&self) -> usize {
393        1
394    }
395
396    fn write(&mut self, output: &mut [f32], time: &SourceTime) -> usize {
397        // consume playback messages
398        self.process_messages();
399
400        // quickly bail out when we've finished playing
401        if self.file_source.playback_finished {
402            return 0;
403        }
404
405        // send Position start event, if needed
406        if self.file_source.playback_started {
407            self.file_source.playback_started = false;
408            let is_start_event = true;
409            self.file_source.send_playback_position_status(
410                time,
411                is_start_event,
412                self.playback_pos as u64,
413                self.file_buffer.channel_count(),
414                self.file_buffer.sample_rate(),
415            );
416        }
417
418        let mut total_written = 0_usize;
419        if self.file_source.current_speed != self.file_source.target_speed {
420            // update pitch slide in blocks of SPEED_UPDATE_CHUNK_SIZE
421            while total_written < output.len() {
422                if self.file_source.samples_to_next_speed_update == 0 {
423                    if self.file_source.current_speed != self.file_source.target_speed {
424                        self.file_source
425                            .update_speed(self.file_buffer.sample_rate());
426                    }
427                    self.file_source.samples_to_next_speed_update =
428                        FileSourceImpl::SPEED_UPDATE_CHUNK_SIZE
429                            * self.file_source.output_channel_count;
430                }
431                let chunk_length = (output.len() - total_written)
432                    .min(self.file_source.samples_to_next_speed_update);
433                let output_chunk = &mut output[total_written..total_written + chunk_length];
434                let written = self.write_buffer(output_chunk);
435
436                self.file_source.samples_to_next_speed_update -= written;
437                total_written += written;
438
439                if written < output_chunk.len() {
440                    break; // input exhausted
441                }
442            }
443        } else {
444            // write into buffer without pitch changes
445            self.file_source.samples_to_next_speed_update = 0;
446            total_written = self.write_buffer(output);
447        }
448
449        // apply volume fading
450        self.file_source
451            .volume_fader
452            .process(&mut output[..total_written]);
453
454        // send Position change events, if needed
455        let is_start_event = false;
456        self.file_source.send_playback_position_status(
457            time,
458            is_start_event,
459            self.playback_pos as u64,
460            self.file_buffer.channel_count(),
461            self.file_buffer.sample_rate(),
462        );
463
464        // check if we've finished playing and send Stopped events
465        let fade_out_completed = self.file_source.volume_fader.state() == FaderState::Finished
466            && self.file_source.volume_fader.target_volume() == 0.0;
467        if self.playback_pos_eof || fade_out_completed {
468            // mark playback as finished
469            self.file_source
470                .send_playback_stopped_status(self.playback_pos_eof);
471            self.file_source.playback_finished = true;
472        }
473
474        total_written
475    }
476}
477
478// -------------------------------------------------------------------------------------------------
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    use crate::source::resampled::ResamplingQuality;
485
486    #[test]
487    fn resampling() {
488        let source_sample_rate = 44100;
489        let target_sample_rate = 48000;
490
491        // NB add extra tailing 0.0 sample for the cubic resampler
492        let file_buffer = Arc::new(
493            AudioFileBuffer::new(vec![0.2, 1.0, 0.5, 0.0], 1, source_sample_rate, None).unwrap(),
494        );
495
496        // Default
497        let mut preloaded = PreloadedFileSource::from_shared_buffer(
498            Arc::clone(&file_buffer),
499            "buffer",
500            FilePlaybackOptions::default().resampling_quality(ResamplingQuality::Default),
501            target_sample_rate,
502        )
503        .unwrap();
504        let mut output = vec![0.0; 1024];
505        let written = preloaded.write(&mut output, &SourceTime::default());
506        let expected_output =
507            file_buffer.buffer().len() as u32 * source_sample_rate / target_sample_rate;
508        assert!(written as u32 >= expected_output);
509        assert!(
510            (output.iter().sum::<f32>() - file_buffer.buffer().iter().sum::<f32>()).abs() < 0.1
511        );
512
513        // HighQuality
514        let file_buffer =
515            Arc::new(AudioFileBuffer::new(vec![0.2, 1.0, 0.5], 1, 48000, None).unwrap());
516
517        let mut preloaded = PreloadedFileSource::from_shared_buffer(
518            Arc::clone(&file_buffer),
519            "buffer",
520            FilePlaybackOptions::default().resampling_quality(ResamplingQuality::HighQuality),
521            target_sample_rate,
522        )
523        .unwrap();
524        let mut output = vec![0.0; 1024];
525        let written = preloaded.write(&mut output, &SourceTime::default());
526        let expected_output =
527            file_buffer.buffer().len() as u32 * source_sample_rate / target_sample_rate;
528        assert!(written as u32 >= expected_output);
529        assert!(
530            (output.iter().sum::<f32>() - file_buffer.buffer().iter().sum::<f32>()).abs() < 0.2
531        );
532        assert!(output[3..].iter().sum::<f32>() < 0.1);
533    }
534}