1use std::path::Path;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum BufferError {
8 #[error("IO error: {0}")]
9 Io(#[from] std::io::Error),
10 #[error("WAV decode error: {0}")]
11 Wav(#[from] hound::Error),
12 #[error("Unsupported format: {0}")]
13 Format(String),
14}
15
16#[derive(Debug, Clone)]
18pub struct SampleBuffer {
19 pub samples: Vec<f32>,
21 pub channels: u16,
23 pub sample_rate: u32,
25 pub frames: usize,
27 pub path: String,
29}
30
31impl SampleBuffer {
32 pub fn load_wav(path: &Path) -> Result<Self, BufferError> {
34 let mut reader = hound::WavReader::open(path)?;
35 let spec = reader.spec();
36 let channels = spec.channels;
37 let sample_rate = spec.sample_rate;
38
39 let samples: Vec<f32> = match spec.sample_format {
40 hound::SampleFormat::Float => {
41 reader.samples::<f32>().map(|s| s.unwrap_or(0.0)).collect()
42 }
43 hound::SampleFormat::Int => {
44 let max = (1i64 << (spec.bits_per_sample - 1)) as f32;
45 match spec.bits_per_sample {
46 16 => reader.samples::<i16>()
47 .map(|s| s.unwrap_or(0) as f32 / max)
48 .collect(),
49 24 | 32 => reader.samples::<i32>()
50 .map(|s| s.unwrap_or(0) as f32 / max)
51 .collect(),
52 _ => return Err(BufferError::Format(
53 format!("Unsupported bit depth: {}", spec.bits_per_sample)
54 )),
55 }
56 }
57 };
58
59 let frames = samples.len() / channels as usize;
60 Ok(Self {
61 samples,
62 channels,
63 sample_rate,
64 frames,
65 path: path.to_string_lossy().into_owned(),
66 })
67 }
68
69 #[inline]
72 pub fn sample_at(&self, frame: f64) -> f32 {
73 let frame_floor = frame as usize;
74 let frac = (frame - frame.floor()) as f32;
75
76 if frame_floor + 1 >= self.frames {
77 return self.frame_mono(self.frames.saturating_sub(1));
78 }
79
80 let s0 = self.frame_mono(frame_floor);
81 let s1 = self.frame_mono(frame_floor + 1);
82 s0 + (s1 - s0) * frac
83 }
84
85 #[inline]
87 fn frame_mono(&self, frame: usize) -> f32 {
88 if self.channels == 1 {
89 self.samples.get(frame).copied().unwrap_or(0.0)
90 } else {
91 let i = frame * self.channels as usize;
92 let l = self.samples.get(i).copied().unwrap_or(0.0);
93 let r = self.samples.get(i + 1).copied().unwrap_or(0.0);
94 (l + r) * 0.5
95 }
96 }
97
98 pub fn load_wav_reader<R: std::io::Read + std::io::Seek>(
101 reader: R,
102 ) -> Result<Self, BufferError> {
103 let mut wav = hound::WavReader::new(reader)?;
104 let spec = wav.spec();
105 let channels = spec.channels;
106 let sample_rate = spec.sample_rate;
107
108 let samples: Vec<f32> = match spec.sample_format {
109 hound::SampleFormat::Float => {
110 wav.samples::<f32>().map(|s| s.unwrap_or(0.0)).collect()
111 }
112 hound::SampleFormat::Int => {
113 let max = (1i64 << (spec.bits_per_sample - 1)) as f32;
114 match spec.bits_per_sample {
115 16 => wav
116 .samples::<i16>()
117 .map(|s| s.unwrap_or(0) as f32 / max)
118 .collect(),
119 24 | 32 => wav
120 .samples::<i32>()
121 .map(|s| s.unwrap_or(0) as f32 / max)
122 .collect(),
123 _ => {
124 return Err(BufferError::Format(format!(
125 "Unsupported bit depth: {}",
126 spec.bits_per_sample
127 )))
128 }
129 }
130 }
131 };
132
133 let frames = samples.len() / channels as usize;
134 Ok(Self {
135 samples,
136 channels,
137 sample_rate,
138 frames,
139 path: "<embedded>".into(),
140 })
141 }
142
143 pub fn duration_secs(&self) -> f32 {
145 self.frames as f32 / self.sample_rate as f32
146 }
147}