Skip to main content

ferraus_core/
buffer.rs

1//! Planar `f32` audio buffers and the `MediaBuffer` enum reserved for v2 video.
2
3use serde::Serialize;
4
5/// Planar `f32` audio: one `Vec<f32>` per channel, all the same length.
6#[derive(Clone, Debug, PartialEq)]
7pub struct AudioBuffer {
8    pub sample_rate: u32,
9    pub data: Vec<Vec<f32>>,
10}
11
12impl AudioBuffer {
13    /// A silent buffer.
14    pub fn new(sample_rate: u32, channels: usize, frames: usize) -> Self {
15        AudioBuffer { sample_rate, data: vec![vec![0.0; frames]; channels.max(1)] }
16    }
17
18    /// Build from planar channels. Shorter channels are zero-padded to the longest.
19    pub fn from_planar(sample_rate: u32, mut data: Vec<Vec<f32>>) -> Self {
20        if data.is_empty() {
21            data.push(Vec::new());
22        }
23        let n = data.iter().map(|c| c.len()).max().unwrap_or(0);
24        for c in &mut data {
25            c.resize(n, 0.0);
26        }
27        AudioBuffer { sample_rate, data }
28    }
29
30    pub fn from_mono(sample_rate: u32, samples: Vec<f32>) -> Self {
31        AudioBuffer { sample_rate, data: vec![samples] }
32    }
33
34    pub fn from_interleaved(sample_rate: u32, channels: usize, samples: &[f32]) -> Self {
35        let channels = channels.max(1);
36        let frames = samples.len() / channels;
37        let mut data = vec![Vec::with_capacity(frames); channels];
38        for f in 0..frames {
39            for (c, ch) in data.iter_mut().enumerate() {
40                ch.push(samples[f * channels + c]);
41            }
42        }
43        AudioBuffer { sample_rate, data }
44    }
45
46    pub fn to_interleaved(&self) -> Vec<f32> {
47        let ch = self.channels();
48        let n = self.frames();
49        let mut out = Vec::with_capacity(ch * n);
50        for f in 0..n {
51            for c in 0..ch {
52                out.push(self.data[c][f]);
53            }
54        }
55        out
56    }
57
58    pub fn channels(&self) -> usize {
59        self.data.len()
60    }
61
62    pub fn frames(&self) -> usize {
63        self.data.first().map(|c| c.len()).unwrap_or(0)
64    }
65
66    pub fn duration_s(&self) -> f64 {
67        if self.sample_rate == 0 {
68            0.0
69        } else {
70            self.frames() as f64 / self.sample_rate as f64
71        }
72    }
73
74    pub fn is_empty(&self) -> bool {
75        self.frames() == 0
76    }
77
78    /// Average of all channels.
79    pub fn mono_mix(&self) -> Vec<f32> {
80        let n = self.frames();
81        let ch = self.channels() as f32;
82        let mut out = vec![0.0f32; n];
83        for c in &self.data {
84            for (o, s) in out.iter_mut().zip(c) {
85                *o += *s;
86            }
87        }
88        if ch > 1.0 {
89            for o in &mut out {
90                *o /= ch;
91            }
92        }
93        out
94    }
95
96    /// Frames `[start, end)`, clamped to the buffer.
97    pub fn slice(&self, start: usize, end: usize) -> AudioBuffer {
98        let n = self.frames();
99        let s = start.min(n);
100        let e = end.clamp(s, n);
101        AudioBuffer { sample_rate: self.sample_rate, data: self.data.iter().map(|c| c[s..e].to_vec()).collect() }
102    }
103
104    /// Append another buffer with the same channel count (extra channels are dropped,
105    /// missing ones are filled from the last channel).
106    pub fn append(&mut self, other: &AudioBuffer) {
107        let ch = self.channels();
108        for c in 0..ch {
109            let src = other.data.get(c).or(other.data.last());
110            if let Some(src) = src {
111                self.data[c].extend_from_slice(src);
112            }
113        }
114    }
115
116    pub fn peak(&self) -> f32 {
117        self.data.iter().flat_map(|c| c.iter()).fold(0.0f32, |m, s| m.max(s.abs()))
118    }
119
120    pub fn rms(&self) -> f32 {
121        let n = (self.frames() * self.channels()).max(1);
122        let sum: f64 = self.data.iter().flat_map(|c| c.iter()).map(|s| (*s as f64) * (*s as f64)).sum();
123        (sum / n as f64).sqrt() as f32
124    }
125
126    /// Multiply every sample by `g`.
127    pub fn scale(&mut self, g: f32) {
128        for c in &mut self.data {
129            for s in c.iter_mut() {
130                *s *= g;
131            }
132        }
133    }
134
135    /// Change the channel count: mono→N duplicates, N→mono averages, otherwise
136    /// truncates or repeats the last channel.
137    pub fn with_channels(&self, n: usize) -> AudioBuffer {
138        let n = n.max(1);
139        let cur = self.channels();
140        if n == cur {
141            return self.clone();
142        }
143        let data = if n == 1 {
144            vec![self.mono_mix()]
145        } else if cur == 1 {
146            vec![self.data[0].clone(); n]
147        } else {
148            (0..n).map(|i| self.data[i.min(cur - 1)].clone()).collect()
149        };
150        AudioBuffer { sample_rate: self.sample_rate, data }
151    }
152
153    /// Resize every channel to `frames`, padding with zeros.
154    pub fn resize(&mut self, frames: usize) {
155        for c in &mut self.data {
156            c.resize(frames, 0.0);
157        }
158    }
159
160    pub fn info(&self) -> BufferInfo {
161        BufferInfo {
162            sample_rate: self.sample_rate,
163            channels: self.channels(),
164            frames: self.frames(),
165            duration_s: self.duration_s(),
166        }
167    }
168}
169
170#[derive(Clone, Debug, Serialize)]
171pub struct BufferInfo {
172    pub sample_rate: u32,
173    pub channels: usize,
174    pub frames: usize,
175    pub duration_s: f64,
176}
177
178/// The unit of data that flows through a pipeline. v1 only has `Audio`; `Video` and
179/// `Subtitle` are additive in v2 (see the PRD's v2 video path).
180#[derive(Clone, Debug)]
181#[non_exhaustive]
182pub enum MediaBuffer {
183    Audio(AudioBuffer),
184}
185
186impl MediaBuffer {
187    pub fn as_audio(&self) -> Option<&AudioBuffer> {
188        match self {
189            MediaBuffer::Audio(a) => Some(a),
190        }
191    }
192
193    pub fn into_audio(self) -> Option<AudioBuffer> {
194        match self {
195            MediaBuffer::Audio(a) => Some(a),
196        }
197    }
198}
199
200/// Convert decibels to linear gain.
201#[inline]
202pub fn db_to_lin(db: f64) -> f64 {
203    10f64.powf(db / 20.0)
204}
205
206/// Convert linear gain to decibels (floored at -240 dB).
207#[inline]
208pub fn lin_to_db(lin: f64) -> f64 {
209    if lin <= 1e-12 {
210        -240.0
211    } else {
212        20.0 * lin.log10()
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn interleave_roundtrip() {
222        let b = AudioBuffer::from_planar(48000, vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]);
223        let i = b.to_interleaved();
224        assert_eq!(i, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
225        assert_eq!(AudioBuffer::from_interleaved(48000, 2, &i), b);
226    }
227
228    #[test]
229    fn channels_conversion() {
230        let b = AudioBuffer::from_planar(48000, vec![vec![1.0, 0.0], vec![0.0, 1.0]]);
231        assert_eq!(b.with_channels(1).data[0], vec![0.5, 0.5]);
232        assert_eq!(b.with_channels(1).with_channels(2).channels(), 2);
233    }
234
235    #[test]
236    fn db_conversions() {
237        assert!((db_to_lin(-6.0206) - 0.5).abs() < 1e-4);
238        assert!((lin_to_db(0.5) + 6.0206).abs() < 1e-3);
239    }
240}