khora_io/asset/decoders/audio/
wav.rs1use anyhow::{anyhow, Result};
18use khora_data::assets::SoundData;
19use std::{error::Error, io::Cursor};
20
21use crate::asset::AssetDecoder;
22
23#[derive(Default)]
25pub struct WavDecoder;
26
27impl WavDecoder {
28 pub fn new() -> Self {
30 Self
31 }
32}
33
34impl AssetDecoder<SoundData> for WavDecoder {
35 fn load(&self, bytes: &[u8]) -> Result<SoundData, Box<dyn Error + Send + Sync>> {
36 let cursor = Cursor::new(bytes);
37 let mut reader = hound::WavReader::new(cursor)?;
38
39 let spec = reader.spec();
40
41 let samples: Result<Vec<f32>, _> = match spec.sample_format {
42 hound::SampleFormat::Float => reader.samples::<f32>().collect(),
43 hound::SampleFormat::Int => {
44 let max_value = (1 << (spec.bits_per_sample - 1)) as f32;
45 reader
46 .samples::<i32>()
47 .map(|sample| sample.map(|s| s as f32 / max_value))
48 .collect()
49 }
50 };
51
52 let samples = samples.map_err(|e| anyhow!("Failed to parse WAV samples: {}", e))?;
53
54 Ok(SoundData {
55 samples,
56 channels: spec.channels,
57 sample_rate: spec.sample_rate,
58 })
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 const TEST_WAV_BYTES: &[u8] = &[
68 82, 73, 70, 70, 52, 0, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1, 0,
69 68, 172, 0, 0, 136, 88, 1, 0, 2, 0, 16, 0, 100, 97, 116, 97, 8, 0, 0, 0, 0, 12, 204, 251,
70 51, 13, 205, 243,
71 ];
72
73 #[test]
74 fn wav_loader_success() {
75 let loader = WavDecoder::new();
76 let result = loader.load(TEST_WAV_BYTES);
77 assert!(result.is_ok());
78 let sound_data = result.unwrap();
79 assert_eq!(sound_data.sample_rate, 44100);
80 assert_eq!(sound_data.channels, 1);
81 assert!(!sound_data.samples.is_empty());
82 }
83
84 #[test]
85 fn wav_loader_invalid_bytes() {
86 let loader = WavDecoder::new();
87 let invalid_bytes = &[0, 1, 2, 3, 4];
88 assert!(loader.load(invalid_bytes).is_err());
89 }
90}