ringkernel_wavesim3d/audio/
mod.rs1pub mod binaural;
10pub mod source;
11
12pub use binaural::{BinauralMicrophone, BinauralProcessor, DelayLine, VirtualHead};
13pub use source::{AudioSource, SourceManager, SourceType};
14
15use crate::simulation::physics::Position3D;
16use std::path::Path;
17
18pub fn load_wav<P: AsRef<Path>>(path: P) -> Result<(Vec<f32>, u32), WavError> {
20 let reader = hound::WavReader::open(path).map_err(|e| WavError::ReadError(e.to_string()))?;
21
22 let spec = reader.spec();
23 let sample_rate = spec.sample_rate;
24
25 let samples: Vec<f32> = match spec.sample_format {
27 hound::SampleFormat::Float => reader
28 .into_samples::<f32>()
29 .filter_map(Result::ok)
30 .collect(),
31 hound::SampleFormat::Int => {
32 let max_val = (1i32 << (spec.bits_per_sample - 1)) as f32;
33 reader
34 .into_samples::<i32>()
35 .filter_map(Result::ok)
36 .map(|s| s as f32 / max_val)
37 .collect()
38 }
39 };
40
41 let mono_samples = if spec.channels == 2 {
43 samples
44 .chunks(2)
45 .map(|chunk| (chunk[0] + chunk.get(1).copied().unwrap_or(0.0)) / 2.0)
46 .collect()
47 } else {
48 samples
49 };
50
51 Ok((mono_samples, sample_rate))
52}
53
54pub fn save_wav<P: AsRef<Path>>(
56 path: P,
57 samples: &[f32],
58 sample_rate: u32,
59 channels: u16,
60) -> Result<(), WavError> {
61 let spec = hound::WavSpec {
62 channels,
63 sample_rate,
64 bits_per_sample: 32,
65 sample_format: hound::SampleFormat::Float,
66 };
67
68 let mut writer =
69 hound::WavWriter::create(path, spec).map_err(|e| WavError::WriteError(e.to_string()))?;
70
71 for &sample in samples {
72 writer
73 .write_sample(sample)
74 .map_err(|e| WavError::WriteError(e.to_string()))?;
75 }
76
77 writer
78 .finalize()
79 .map_err(|e| WavError::WriteError(e.to_string()))?;
80
81 Ok(())
82}
83
84pub fn save_stereo_wav<P: AsRef<Path>>(
86 path: P,
87 left: &[f32],
88 right: &[f32],
89 sample_rate: u32,
90) -> Result<(), WavError> {
91 let mut interleaved = Vec::with_capacity(left.len() * 2);
92 for (l, r) in left.iter().zip(right.iter()) {
93 interleaved.push(*l);
94 interleaved.push(*r);
95 }
96 save_wav(path, &interleaved, sample_rate, 2)
97}
98
99#[derive(Debug, Clone)]
101pub enum WavError {
102 ReadError(String),
103 WriteError(String),
104}
105
106impl std::fmt::Display for WavError {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 match self {
109 WavError::ReadError(msg) => write!(f, "WAV read error: {}", msg),
110 WavError::WriteError(msg) => write!(f, "WAV write error: {}", msg),
111 }
112 }
113}
114
115impl std::error::Error for WavError {}
116
117#[derive(Debug, Clone)]
119pub struct AudioConfig {
120 pub sample_rate: u32,
122 pub buffer_size: usize,
124 pub enable_output: bool,
126 pub enable_recording: bool,
128}
129
130impl Default for AudioConfig {
131 fn default() -> Self {
132 Self {
133 sample_rate: 44100,
134 buffer_size: 1024,
135 enable_output: true,
136 enable_recording: false,
137 }
138 }
139}
140
141pub struct AudioSystem {
143 pub config: AudioConfig,
145 pub sources: SourceManager,
147 pub microphone: Option<BinauralMicrophone>,
149 recording_left: Vec<f32>,
151 recording_right: Vec<f32>,
153 is_recording: bool,
155}
156
157impl AudioSystem {
158 pub fn new(config: AudioConfig) -> Self {
160 Self {
161 config,
162 sources: SourceManager::new(),
163 microphone: None,
164 recording_left: Vec::new(),
165 recording_right: Vec::new(),
166 is_recording: false,
167 }
168 }
169
170 pub fn init_microphone(&mut self, head: VirtualHead, simulation_dt: f32) {
172 self.microphone = Some(BinauralMicrophone::new(
173 head,
174 self.config.sample_rate,
175 simulation_dt,
176 ));
177 }
178
179 pub fn add_source(&mut self, source: AudioSource) -> u32 {
181 self.sources.add(source)
182 }
183
184 pub fn add_impulse(&mut self, position: Position3D, amplitude: f32) -> u32 {
186 self.sources
187 .add(AudioSource::impulse(0, position, amplitude))
188 }
189
190 pub fn add_tone(&mut self, position: Position3D, frequency: f32, amplitude: f32) -> u32 {
192 self.sources
193 .add(AudioSource::tone(0, position, frequency, amplitude))
194 }
195
196 pub fn start_recording(&mut self) {
198 self.recording_left.clear();
199 self.recording_right.clear();
200 self.is_recording = true;
201 }
202
203 pub fn stop_recording(&mut self) -> (Vec<f32>, Vec<f32>) {
205 self.is_recording = false;
206 (
207 std::mem::take(&mut self.recording_left),
208 std::mem::take(&mut self.recording_right),
209 )
210 }
211
212 pub fn process_recording(&mut self) {
214 if self.is_recording {
215 if let Some(mic) = &mut self.microphone {
216 let (left, right) = mic.get_samples(self.config.buffer_size);
217 self.recording_left.extend(left);
218 self.recording_right.extend(right);
219 }
220 }
221 }
222
223 pub fn sample_rate(&self) -> u32 {
225 self.config.sample_rate
226 }
227
228 pub fn reset(&mut self) {
230 self.sources.reset_all();
231 if let Some(mic) = &mut self.microphone {
232 mic.clear();
233 }
234 self.recording_left.clear();
235 self.recording_right.clear();
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_audio_system_creation() {
245 let config = AudioConfig::default();
246 let system = AudioSystem::new(config);
247
248 assert!(system.sources.is_empty());
249 assert!(system.microphone.is_none());
250 }
251
252 #[test]
253 fn test_add_sources() {
254 let mut system = AudioSystem::new(AudioConfig::default());
255
256 let id1 = system.add_impulse(Position3D::origin(), 1.0);
257 let id2 = system.add_tone(Position3D::new(1.0, 0.0, 0.0), 440.0, 0.5);
258
259 assert_ne!(id1, id2);
260 assert_eq!(system.sources.len(), 2);
261 }
262
263 #[test]
264 fn test_recording() {
265 let mut system = AudioSystem::new(AudioConfig::default());
266
267 system.start_recording();
268 assert!(system.is_recording);
269
270 let (left, right) = system.stop_recording();
271 assert!(!system.is_recording);
272 assert!(left.is_empty());
273 assert!(right.is_empty());
274 }
275}