Skip to main content

audio_code/audio/
data.rs

1//! Decoded sound data and file loading.
2
3use std::fs::File;
4use std::path::Path;
5use std::sync::OnceLock;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8use symphonia::core::audio::sample::Sample;
9use symphonia::core::codecs::audio::AudioDecoderOptions;
10use symphonia::core::errors::Error as SymphoniaError;
11use symphonia::core::formats::probe::Hint;
12use symphonia::core::formats::{FormatOptions, TrackType};
13use symphonia::core::io::MediaSourceStream;
14use symphonia::core::meta::MetadataOptions;
15
16use crate::error::{Error, Result};
17
18/// Decoded PCM audio kept in memory: interleaved `f32` samples.
19///
20/// You normally never touch this directly; the engine loads files for you.
21/// It is public so you can generate sounds in code and register them with
22/// [`Audio::register`](crate::audio::Audio::register).
23#[derive(Debug, Clone)]
24pub struct SoundData {
25    pub(crate) samples: Vec<f32>,
26    pub(crate) channels: usize,
27    pub(crate) sample_rate: u32,
28}
29
30impl SoundData {
31    /// Creates sound data from interleaved samples in the range `-1.0..=1.0`.
32    ///
33    /// # Panics
34    /// If `channels` is 0 or `sample_rate` is 0.
35    pub fn from_samples(samples: Vec<f32>, channels: usize, sample_rate: u32) -> Self {
36        assert!(channels > 0 && sample_rate > 0, "channels and sample rate must be positive");
37        Self { samples, channels, sample_rate }
38    }
39
40    /// Generates a sine tone with short fades, handy for prototypes and beeps.
41    /// `frequency` is the pitch in hertz (440 = A, 880 = one octave higher), `seconds` the
42    /// length and `volume` the linear loudness from 0.0 to 1.0.
43    pub fn tone(frequency: f32, seconds: f32, volume: f32) -> Self {
44        Self::generate(seconds, |t| (t * frequency * std::f32::consts::TAU).sin() * volume)
45    }
46
47    /// Generates mono sound from a function of time in seconds.
48    /// Adds 5 ms fades at both ends to avoid clicks.
49    pub fn generate(seconds: f32, mut f: impl FnMut(f32) -> f32) -> Self {
50        const RATE: u32 = 44_100;
51        let frames = (seconds.max(0.0) * RATE as f32) as usize;
52        let fade = (RATE as usize / 200).min(frames / 2).max(1);
53        let samples = (0..frames)
54            .map(|i| {
55                let env = (i.min(frames - 1 - i) as f32 / fade as f32).min(1.0);
56                f(i as f32 / RATE as f32) * env
57            })
58            .collect();
59        Self { samples, channels: 1, sample_rate: RATE }
60    }
61
62    /// Length in seconds.
63    pub fn duration(&self) -> f32 {
64        self.frames() as f32 / self.sample_rate as f32
65    }
66
67    /// Number of channels (1 = mono, 2 = stereo).
68    pub fn channels(&self) -> usize {
69        self.channels
70    }
71
72    /// Sample rate in Hz.
73    pub fn sample_rate(&self) -> u32 {
74        self.sample_rate
75    }
76
77    pub(crate) fn frames(&self) -> usize {
78        self.samples.len() / self.channels
79    }
80
81    /// Loads and decodes a sound file (wav, ogg, mp3, flac, aiff, m4a/aac, ...).
82    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
83        let path = path.as_ref();
84        let file = File::open(path)?;
85        let mss = MediaSourceStream::new(Box::new(file), Default::default());
86        let mut hint = Hint::new();
87        if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
88            hint.with_extension(ext);
89        }
90
91        let decode_err = |e: SymphoniaError| Error::Decode(format!("{}: {e}", path.display()));
92        let mut format = symphonia::default::get_probe()
93            .probe(&hint, mss, FormatOptions::default(), MetadataOptions::default())
94            .map_err(decode_err)?;
95        let track = format
96            .default_track(TrackType::Audio)
97            .ok_or_else(|| Error::Decode(format!("{}: no audio track", path.display())))?;
98        let track_id = track.id;
99        let params = track
100            .codec_params
101            .as_ref()
102            .and_then(|p| p.audio())
103            .ok_or_else(|| Error::Decode(format!("{}: unsupported codec", path.display())))?;
104        let mut decoder = symphonia::default::get_codecs()
105            .make_audio_decoder(params, &AudioDecoderOptions::default())
106            .map_err(decode_err)?;
107
108        let mut samples = Vec::new();
109        let mut chunk = Vec::new();
110        let mut channels = 0;
111        let mut sample_rate = 0;
112        loop {
113            let packet = match format.next_packet() {
114                Ok(Some(packet)) => packet,
115                Ok(None) => break,
116                // Some files end with a truncated packet; keep what we have.
117                Err(SymphoniaError::IoError(_)) => break,
118                Err(e) => return Err(decode_err(e)),
119            };
120            if packet.track_id != track_id {
121                continue;
122            }
123            match decoder.decode(&packet) {
124                Ok(buf) => {
125                    channels = buf.spec().channels().count();
126                    sample_rate = buf.spec().rate();
127                    chunk.resize(buf.samples_interleaved(), f32::MID);
128                    buf.copy_to_slice_interleaved(&mut chunk);
129                    samples.extend_from_slice(&chunk);
130                }
131                Err(SymphoniaError::DecodeError(_)) => continue,
132                Err(e) => return Err(decode_err(e)),
133            }
134        }
135        if channels == 0 || sample_rate == 0 {
136            return Err(Error::Decode(format!("{}: file contains no audio", path.display())));
137        }
138        Ok(Self { samples, channels, sample_rate })
139    }
140}
141
142/// A slot that receives sound data, possibly later (music loads in the background).
143#[derive(Debug, Default)]
144pub(crate) struct SoundSlot {
145    data: OnceLock<SoundData>,
146    failed: AtomicBool,
147}
148
149impl SoundSlot {
150    pub(crate) fn ready(data: SoundData) -> Self {
151        let slot = Self::default();
152        let _ = slot.data.set(data);
153        slot
154    }
155
156    pub(crate) fn fill(&self, data: SoundData) {
157        let _ = self.data.set(data);
158    }
159
160    pub(crate) fn fail(&self) {
161        self.failed.store(true, Ordering::Release);
162    }
163
164    pub(crate) fn get(&self) -> Option<&SoundData> {
165        self.data.get()
166    }
167
168    pub(crate) fn has_failed(&self) -> bool {
169        self.failed.load(Ordering::Acquire)
170    }
171}