1use std::io::{self, Write};
2use std::path::Path;
3
4pub fn decode_audio_to_f32_interleaved_sync(path: &Path) -> io::Result<(Vec<f32>, usize, u32)> {
10 let ext = file_extension(path)?;
11 match ext.as_str() {
12 "wav" => decode_wav(path),
13 "flac" => decode_flac(path),
14 "opus" => decode_opus(path),
15 _ => Err(io::Error::other(format!(
16 "Unsupported audio extension '{ext}' for '{}'",
17 path.display()
18 ))),
19 }
20}
21
22pub fn decode_audio_to_f32_interleaved_preferring_wav(
28 path: &Path,
29) -> io::Result<(Vec<f32>, usize, u32)> {
30 if file_extension(path)?.eq_ignore_ascii_case("wav")
31 && let Ok(ok) = decode_wav(path)
32 {
33 return Ok(ok);
34 }
35 decode_audio_to_f32_interleaved_sync(path)
36}
37
38fn file_extension(path: &Path) -> io::Result<String> {
39 path.extension()
40 .and_then(|e| e.to_str())
41 .map(|e| e.to_lowercase())
42 .ok_or_else(|| io::Error::other(format!("Missing file extension for '{}'", path.display())))
43}
44
45fn decode_wav(path: &Path) -> io::Result<(Vec<f32>, usize, u32)> {
50 let mut reader = hound::WavReader::open(path)
51 .map_err(|e| io::Error::other(format!("Failed to open WAV '{}': {e}", path.display())))?;
52 let spec = reader.spec();
53 let channels = spec.channels as usize;
54 let sample_rate = spec.sample_rate;
55 let bits_per_sample = spec.bits_per_sample;
56
57 let mut samples = Vec::new();
58 match spec.sample_format {
59 hound::SampleFormat::Float => {
60 for s in reader.samples::<f32>() {
61 let v = s.map_err(|e| {
62 io::Error::other(format!("WAV decode error for '{}': {e}", path.display()))
63 })?;
64 samples.push(v.clamp(-1.0, 1.0));
65 }
66 }
67 hound::SampleFormat::Int => {
68 for s in reader.samples::<i32>() {
69 let v = s.map_err(|e| {
70 io::Error::other(format!("WAV decode error for '{}': {e}", path.display()))
71 })?;
72 samples.push(int_sample_to_f32(v, bits_per_sample));
73 }
74 }
75 }
76
77 if samples.is_empty() {
78 return Err(io::Error::other(format!(
79 "WAV '{}' contains no samples",
80 path.display()
81 )));
82 }
83 Ok((samples, channels, sample_rate))
84}
85
86pub fn write_wav_f32(
87 path: &Path,
88 samples: &[f32],
89 channels: usize,
90 sample_rate: u32,
91) -> io::Result<()> {
92 let bytes_per_sample = 4usize;
93 let block_align = (channels.max(1) * bytes_per_sample) as u16;
94 let byte_rate = sample_rate * u32::from(block_align);
95 let data_size = samples
96 .len()
97 .checked_mul(bytes_per_sample)
98 .ok_or_else(|| io::Error::other("WAV data too large"))? as u32;
99 let riff_size = 36u32
100 .checked_add(data_size)
101 .ok_or_else(|| io::Error::other("WAV file too large"))?;
102
103 let mut file = std::fs::File::create(path)?;
104 file.write_all(b"RIFF")?;
105 file.write_all(&riff_size.to_le_bytes())?;
106 file.write_all(b"WAVE")?;
107 file.write_all(b"fmt ")?;
108 file.write_all(&16u32.to_le_bytes())?;
109 file.write_all(&3u16.to_le_bytes())?;
110 file.write_all(&(channels.max(1) as u16).to_le_bytes())?;
111 file.write_all(&sample_rate.to_le_bytes())?;
112 file.write_all(&byte_rate.to_le_bytes())?;
113 file.write_all(&block_align.to_le_bytes())?;
114 file.write_all(&32u16.to_le_bytes())?;
115 file.write_all(b"data")?;
116 file.write_all(&data_size.to_le_bytes())?;
117 for &sample in samples {
118 file.write_all(&sample.clamp(-1.0, 1.0).to_le_bytes())?;
119 }
120 Ok(())
121}
122
123fn decode_flac(path: &Path) -> io::Result<(Vec<f32>, usize, u32)> {
128 let bytes = std::fs::read(path)?;
129 let decoded = libflac_rs::decode(&bytes)
130 .ok_or_else(|| io::Error::other(format!("Failed to decode FLAC '{}'", path.display())))?;
131
132 let channels = decoded.channels as usize;
133 let sample_rate = decoded.sample_rate;
134 let bits_per_sample = decoded.bits_per_sample as u16;
135
136 let mut samples = Vec::with_capacity(decoded.interleaved.len());
137 for v in decoded.interleaved {
138 samples.push(int_sample_to_f32(v, bits_per_sample));
139 }
140
141 if samples.is_empty() {
142 return Err(io::Error::other(format!(
143 "FLAC '{}' contains no samples",
144 path.display()
145 )));
146 }
147 Ok((samples, channels, sample_rate))
148}
149
150pub fn write_flac(
151 path: &Path,
152 samples: &[f32],
153 channels: usize,
154 sample_rate: u32,
155 bits_per_sample: u16,
156) -> io::Result<()> {
157 if channels == 0 {
158 return Err(io::Error::other("FLAC write: channels must be > 0"));
159 }
160 if !samples.len().is_multiple_of(channels) {
161 return Err(io::Error::other(
162 "FLAC write: sample slice length is not a multiple of channels",
163 ));
164 }
165 if !matches!(bits_per_sample, 16 | 24 | 32) {
166 return Err(io::Error::other(format!(
167 "FLAC write: unsupported bits_per_sample {bits_per_sample} (use 16, 24, or 32)"
168 )));
169 }
170
171 let pcm: Vec<i32> = samples
172 .iter()
173 .map(|&s| f32_sample_to_int(s, bits_per_sample))
174 .collect();
175
176 let config =
177 libflac_rs::EncoderConfig::new(channels as u32, bits_per_sample as u32, sample_rate);
178 let encoder = libflac_rs::Encoder::new(config);
179 let encoded = encoder.encode(&pcm);
180
181 std::fs::write(path, encoded)?;
182 Ok(())
183}
184
185fn decode_opus(path: &Path) -> io::Result<(Vec<f32>, usize, u32)> {
190 use ogg::PacketReader;
191
192 let file = std::fs::File::open(path)?;
193 let mut reader = PacketReader::new(file);
194
195 let head_packet = reader
197 .read_packet()
198 .map_err(|e| io::Error::other(format!("Ogg read error for '{}': {e}", path.display())))?
199 .ok_or_else(|| {
200 io::Error::other(format!(
201 "Opus file '{}' contains no packets",
202 path.display()
203 ))
204 })?;
205
206 if !head_packet.data.starts_with(b"OpusHead") {
207 return Err(io::Error::other(format!(
208 "Opus file '{}' missing OpusHead header",
209 path.display()
210 )));
211 }
212 let channels = parse_opus_head_channels(&head_packet.data)?;
213
214 let _tags_packet = reader
216 .read_packet()
217 .map_err(|e| io::Error::other(format!("Ogg read error for '{}': {e}", path.display())))?;
218
219 let sample_rate = 48_000;
221 let mut decoder = opus_rs::OpusDecoder::new(sample_rate, channels).map_err(|e| {
222 io::Error::other(format!(
223 "Opus decoder init failed for '{}': {e}",
224 path.display()
225 ))
226 })?;
227
228 let mut output = Vec::new();
229 loop {
230 let packet = match reader.read_packet() {
231 Ok(Some(p)) => p,
232 Ok(None) => break,
233 Err(e) => {
234 return Err(io::Error::other(format!(
235 "Ogg read error for '{}': {e}",
236 path.display()
237 )));
238 }
239 };
240
241 let frame_size = opus_packet_frame_size(&packet.data, sample_rate).ok_or_else(|| {
242 io::Error::other(format!(
243 "Invalid Opus packet framing in '{}'",
244 path.display()
245 ))
246 })?;
247
248 let mut frame = vec![0.0f32; frame_size * channels];
249 decoder
250 .decode(&packet.data, frame_size, &mut frame)
251 .map_err(|e| {
252 io::Error::other(format!("Opus decode error for '{}': {e}", path.display()))
253 })?;
254 output.extend_from_slice(&frame);
255 }
256
257 if output.is_empty() {
258 return Err(io::Error::other(format!(
259 "Opus file '{}' contains no audio",
260 path.display()
261 )));
262 }
263 Ok((output, channels, sample_rate as u32))
264}
265
266pub fn write_opus(
267 path: &Path,
268 samples: &[f32],
269 channels: usize,
270 sample_rate: u32,
271 bitrate_bps: i32,
272) -> io::Result<()> {
273 use ogg::{PacketWriteEndInfo, PacketWriter};
274
275 if channels == 0 || channels > 2 {
276 return Err(io::Error::other(
277 "Opus write: only mono and stereo are supported",
278 ));
279 }
280 if !samples.len().is_multiple_of(channels) {
281 return Err(io::Error::other(
282 "Opus write: sample slice length is not a multiple of channels",
283 ));
284 }
285 if !matches!(sample_rate, 8_000 | 12_000 | 16_000 | 24_000 | 48_000) {
286 return Err(io::Error::other(format!(
287 "Opus write: unsupported sample rate {sample_rate} (use 8000, 12000, 16000, 24000, or 48000)"
288 )));
289 }
290
291 let total_samples = samples.len() / channels;
292 let frame_size = (sample_rate as usize / 50).max(1);
295
296 let mut encoder =
297 opus_rs::OpusEncoder::new(sample_rate as i32, channels, opus_rs::Application::Audio)
298 .map_err(|e| io::Error::other(format!("Opus encoder init failed: {e}")))?;
299 encoder.bitrate_bps = bitrate_bps;
300
301 let file = std::fs::File::create(path)?;
302 let mut writer = PacketWriter::new(file);
303 let serial = ogg_serial_from_path(path);
304
305 writer.write_packet(
307 opus_head(channels as u8, sample_rate),
308 serial,
309 PacketWriteEndInfo::EndPage,
310 0,
311 )?;
312
313 writer.write_packet(opus_tags(), serial, PacketWriteEndInfo::EndPage, 0)?;
315
316 let mut packet_buf = vec![0u8; 4000];
317 let mut encoded_granule: u64 = 0;
318
319 for frame_idx in 0..frame_count(total_samples, frame_size) {
320 let start = frame_idx * frame_size;
321 let end = ((start + frame_size).min(total_samples)).max(start);
322 let actual_len = end - start;
323
324 let mut frame_input = vec![0.0f32; frame_size * channels];
326 frame_input[..actual_len * channels]
327 .copy_from_slice(&samples[start * channels..end * channels]);
328
329 let encoded_len = encoder
330 .encode(&frame_input, frame_size, &mut packet_buf)
331 .map_err(|e| io::Error::other(format!("Opus encode failed: {e}")))?;
332
333 encoded_granule = encoded_granule.saturating_add(frame_size as u64);
334 let is_last = frame_idx == frame_count(total_samples, frame_size) - 1;
337 let granule = if is_last {
338 total_samples.min(encoded_granule as usize) as u64
339 } else {
340 encoded_granule
341 };
342
343 let end_info = if is_last {
344 PacketWriteEndInfo::EndStream
345 } else {
346 PacketWriteEndInfo::NormalPacket
347 };
348 writer.write_packet(
349 packet_buf[..encoded_len].to_vec(),
350 serial,
351 end_info,
352 granule,
353 )?;
354 }
355
356 Ok(())
357}
358
359fn parse_opus_head_channels(head: &[u8]) -> io::Result<usize> {
360 if head.len() < 10 || head[8] != 1 {
361 return Err(io::Error::other(
362 "OpusHead header is too short or has unsupported version",
363 ));
364 }
365 let channels = head[9] as usize;
366 if channels == 0 {
367 return Err(io::Error::other("OpusHead reports zero channels"));
368 }
369 Ok(channels)
370}
371
372fn opus_head(channels: u8, sample_rate: u32) -> Vec<u8> {
373 let mut head = Vec::with_capacity(19);
374 head.extend_from_slice(b"OpusHead");
375 head.push(1); head.push(channels);
377 head.extend_from_slice(&0u16.to_le_bytes()); head.extend_from_slice(&sample_rate.to_le_bytes()); head.extend_from_slice(&0i16.to_le_bytes()); head.push(0); head
382}
383
384fn opus_tags() -> Vec<u8> {
385 let vendor = b"maolan-engine";
386 let mut tags = Vec::with_capacity(8 + 4 + vendor.len() + 4);
387 tags.extend_from_slice(b"OpusTags");
388 tags.extend_from_slice(&(vendor.len() as u32).to_le_bytes());
389 tags.extend_from_slice(vendor);
390 tags.extend_from_slice(&0u32.to_le_bytes()); tags
392}
393
394fn ogg_serial_from_path(path: &Path) -> u32 {
395 use std::collections::hash_map::DefaultHasher;
396 use std::hash::{Hash, Hasher};
397 let mut hasher = DefaultHasher::new();
398 path.as_os_str().hash(&mut hasher);
399 hasher.finish() as u32
400}
401
402fn frame_count(total_samples: usize, frame_size: usize) -> usize {
405 if total_samples == 0 {
406 return 0;
407 }
408 total_samples.div_ceil(frame_size)
409}
410
411fn opus_packet_frame_size(packet: &[u8], sample_rate: i32) -> Option<usize> {
416 if packet.is_empty() {
417 return None;
418 }
419 let toc = packet[0];
420
421 let sub_frame_duration_ms = if toc & 0x80 != 0 {
422 match (toc >> 3) & 0x03 {
424 0 => 2,
425 1 => 5,
426 2 => 10,
427 _ => 20,
428 }
429 } else if toc & 0x60 == 0x60 {
430 if ((toc >> 3) & 0x01) == 0 { 10 } else { 20 }
432 } else {
433 match (toc >> 3) & 0x03 {
435 0 => 10,
436 1 => 20,
437 2 => 40,
438 _ => 60,
439 }
440 };
441
442 let sub_frame_size = (sample_rate as i64 * sub_frame_duration_ms as i64 / 1000).max(1) as usize;
443 let code = toc & 0x03;
444 let frame_count = match code {
445 0 => 1,
446 1 | 2 => 2,
447 3 => {
448 if packet.len() < 2 {
449 return None;
450 }
451 let n = (packet[1] & 0x3F) as usize;
452 if n == 0 {
453 return None;
454 }
455 n
456 }
457 _ => return None,
458 };
459
460 Some(sub_frame_size * frame_count)
461}
462
463fn int_sample_to_f32(v: i32, bits_per_sample: u16) -> f32 {
468 let divisor = match bits_per_sample {
469 8 => 128.0,
470 16 => 32_768.0,
471 24 => 8_388_608.0,
472 32 => 2_147_483_648.0,
473 _ => 2.0f32.powi(bits_per_sample as i32 - 1),
474 };
475 (v as f32 / divisor).clamp(-1.0, 1.0)
476}
477
478fn f32_sample_to_int(v: f32, bits_per_sample: u16) -> i32 {
479 let max_positive = match bits_per_sample {
480 16 => 32_767i32,
481 24 => 8_388_607i32,
482 32 => 2_147_483_647i32,
483 _ => ((1u64 << (bits_per_sample - 1)) - 1) as i32,
484 };
485 (v.clamp(-1.0, 1.0) * max_positive as f32).round() as i32
486}