Skip to main content

aether_sampler/
buffer.rs

1//! Audio buffer loading and storage.
2
3use 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/// A decoded audio buffer stored as interleaved f32 samples.
17#[derive(Debug, Clone)]
18pub struct SampleBuffer {
19    /// Interleaved samples (mono or stereo).
20    pub samples: Vec<f32>,
21    /// Number of channels (1=mono, 2=stereo).
22    pub channels: u16,
23    /// Original sample rate in Hz.
24    pub sample_rate: u32,
25    /// Total number of frames (samples / channels).
26    pub frames: usize,
27    /// File path this was loaded from.
28    pub path: String,
29}
30
31impl SampleBuffer {
32    /// Load a WAV file into memory as f32 samples.
33    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    /// Get a mono sample at a given frame position (linear interpolation).
70    /// If stereo, returns the average of both channels.
71    #[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    /// Get mono value at an exact frame index.
86    #[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    /// Load a WAV from any `Read + Seek` source (e.g. an in-memory `Cursor<&[u8]>`).
99    /// This is used by the CLAP plugin to decode audio that was embedded at build time.
100    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    /// Duration in seconds.
144    pub fn duration_secs(&self) -> f32 {
145        self.frames as f32 / self.sample_rate as f32
146    }
147}