Skip to main content

falcon_library/audio/
mod.rs

1//! Audio decoding: the one path playback, seeking, durations, waveforms
2//! and the tempo all read audio through.
3//!
4//! [`open`] returns an [`AudioSource`], a stream of interleaved `f32`
5//! blocks that can seek. The main backend is symphonia (pure Rust,
6//! MPL-2.0) with its MP3, AAC, ALAC, FLAC, Vorbis, PCM and ADPCM decoders
7//! in MP4/M4A, Ogg, WAV, FLAC, MP3 and ADTS files, plus the pure-Rust
8//! `opus-decoder` crate for Opus (in Ogg or M4A, which Eagle's Chromium
9//! player also plays). A file that doesn't decode returns an
10//! [`AudioError`] and callers fall back (the viewer shows the reason, the
11//! waveform is flat, no duration is stored). When an LGPL FFmpeg is
12//! installed (`falcon-ffmpeg`, loaded at run time), [`open`] tries it after
13//! symphonia, which covers the soundtracks of video files.
14
15mod adts;
16mod edit;
17mod ffmpeg;
18mod native;
19mod opus;
20
21use std::fmt;
22use std::path::Path;
23
24pub use ffmpeg::FfmpegSource;
25pub use native::SymphoniaSource;
26pub(crate) use native::open_format;
27
28/// A decoded audio stream.
29pub trait AudioSource: Send {
30    /// Channels in each block's frames.
31    fn channels(&self) -> u16;
32    /// Frames per second.
33    fn sample_rate(&self) -> u32;
34    /// The stream's length in seconds, when the file says.
35    fn duration(&self) -> Option<f64>;
36    /// The next block of samples, or `None` at the end.
37    fn next_block(&mut self) -> Option<Block<'_>>;
38    /// Moves so the next block starts at `secs` (clamped to the stream)
39    /// and returns the time it starts at.
40    fn seek(&mut self, secs: f64) -> Result<f64, AudioError>;
41    /// Where the next block starts, in seconds.
42    fn position(&self) -> f64;
43    /// Which decoder backs the stream, for messages and tests.
44    fn backend(&self) -> &'static str;
45}
46
47/// Decoded audio: interleaved samples in -1..=1, never empty.
48#[derive(Debug, Clone, Copy)]
49pub struct Block<'a> {
50    pub samples: &'a [f32],
51    pub channels: u16,
52    pub rate: u32,
53}
54
55impl Block<'_> {
56    /// The samples one frame at a time.
57    pub fn frames(&self) -> std::slice::Chunks<'_, f32> {
58        self.samples.chunks(usize::from(self.channels.max(1)))
59    }
60}
61
62/// Why a file can't be decoded.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum AudioError {
65    /// The file couldn't be read.
66    Io(String),
67    /// No backend recognises the container.
68    UnknownFormat,
69    /// The container holds no audio track.
70    NoAudioTrack,
71    /// The track's codec has no decoder here.
72    UnsupportedCodec,
73    /// The stream couldn't be moved to the asked time.
74    Seek(String),
75}
76
77impl fmt::Display for AudioError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            AudioError::Io(e) => f.write_str(e),
81            AudioError::UnknownFormat => f.write_str("unsupported audio format"),
82            AudioError::NoAudioTrack => f.write_str("no audio track"),
83            AudioError::UnsupportedCodec => f.write_str("unsupported audio codec"),
84            AudioError::Seek(e) => write!(f, "seek failed: {e}"),
85        }
86    }
87}
88
89impl std::error::Error for AudioError {}
90
91/// Opens an audio file (`ext` is a hint; the contents decide). Backends
92/// are tried in turn; the first error is kept when none can.
93pub fn open(path: &Path, ext: &str) -> Result<Box<dyn AudioSource>, AudioError> {
94    match SymphoniaSource::open(path, ext) {
95        Ok(s) => Ok(Box::new(s)),
96        Err(AudioError::Io(e)) => Err(AudioError::Io(e)),
97        Err(e) => FfmpegSource::open(path).map(|s| Box::new(s) as Box<dyn AudioSource>).map_err(|_| e),
98    }
99}
100
101/// Audio formats that decode, by extension, with the codecs each may hold.
102pub const DECODED: [(&str, &str); 6] = [
103    ("mp3", "MP3"),
104    ("wav", "PCM, IEEE float, A-law, ยต-law, IMA and MS ADPCM"),
105    ("flac", "FLAC"),
106    ("ogg", "Vorbis, Opus, FLAC"),
107    ("aac", "AAC-LC (ADTS)"),
108    ("m4a", "AAC-LC, ALAC, FLAC, Opus"),
109];
110
111#[cfg(test)]
112mod tests;