1use 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#[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 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 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 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 pub fn duration(&self) -> f32 {
64 self.frames() as f32 / self.sample_rate as f32
65 }
66
67 pub fn channels(&self) -> usize {
69 self.channels
70 }
71
72 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 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 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#[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}