Skip to main content

brainwires_audio/
buffer.rs

1use crate::types::AudioConfig;
2
3/// A ring buffer for accumulating audio samples with a fixed capacity.
4///
5/// Useful for buffering streaming audio before processing (e.g., accumulating
6/// enough audio for a STT inference pass).
7pub struct AudioRingBuffer {
8    data: Vec<u8>,
9    capacity: usize,
10    write_pos: usize,
11    len: usize,
12    config: AudioConfig,
13}
14
15impl AudioRingBuffer {
16    /// Create a new ring buffer with capacity for `duration_secs` of audio.
17    pub fn new(config: AudioConfig, duration_secs: f64) -> Self {
18        let capacity =
19            (config.sample_rate as f64 * duration_secs) as usize * config.bytes_per_frame();
20        Self {
21            data: vec![0u8; capacity],
22            capacity,
23            write_pos: 0,
24            len: 0,
25            config,
26        }
27    }
28
29    /// Push raw PCM bytes into the buffer, overwriting oldest data if full.
30    pub fn push(&mut self, bytes: &[u8]) {
31        for &byte in bytes {
32            self.data[self.write_pos] = byte;
33            self.write_pos = (self.write_pos + 1) % self.capacity;
34            if self.len < self.capacity {
35                self.len += 1;
36            }
37        }
38    }
39
40    /// Read all available data as a contiguous byte slice.
41    ///
42    /// Returns data in chronological order (oldest first).
43    pub fn read_all(&self) -> Vec<u8> {
44        if self.len < self.capacity {
45            // Haven't wrapped yet
46            self.data[..self.len].to_vec()
47        } else {
48            // Wrapped: read from write_pos to end, then from start to write_pos
49            let mut result = Vec::with_capacity(self.capacity);
50            result.extend_from_slice(&self.data[self.write_pos..]);
51            result.extend_from_slice(&self.data[..self.write_pos]);
52            result
53        }
54    }
55
56    /// Duration of buffered audio in seconds.
57    pub fn duration_secs(&self) -> f64 {
58        let frame_size = self.config.bytes_per_frame();
59        if frame_size == 0 {
60            return 0.0;
61        }
62        let num_frames = self.len / frame_size;
63        num_frames as f64 / self.config.sample_rate as f64
64    }
65
66    /// Number of bytes currently buffered.
67    pub fn len(&self) -> usize {
68        self.len
69    }
70
71    /// Whether the buffer is empty.
72    pub fn is_empty(&self) -> bool {
73        self.len == 0
74    }
75
76    /// Total capacity in bytes.
77    pub fn capacity(&self) -> usize {
78        self.capacity
79    }
80
81    /// Whether the buffer is at capacity.
82    pub fn is_full(&self) -> bool {
83        self.len >= self.capacity
84    }
85
86    /// Clear all buffered data.
87    pub fn clear(&mut self) {
88        self.write_pos = 0;
89        self.len = 0;
90    }
91
92    /// Get the audio config for this buffer.
93    pub fn config(&self) -> &AudioConfig {
94        &self.config
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_ring_buffer_basic() {
104        let config = AudioConfig::speech(); // 16kHz mono i16 = 2 bytes/frame
105        let mut buf = AudioRingBuffer::new(config, 0.001); // ~32 bytes
106
107        assert!(buf.is_empty());
108        assert_eq!(buf.duration_secs(), 0.0);
109
110        buf.push(&[1, 2, 3, 4]);
111        assert_eq!(buf.len(), 4);
112        assert_eq!(buf.read_all(), vec![1, 2, 3, 4]);
113    }
114
115    #[test]
116    fn test_ring_buffer_wrap() {
117        let config = AudioConfig {
118            sample_rate: 4,
119            channels: 1,
120            sample_format: crate::types::SampleFormat::I16,
121        };
122        // 4 Hz * 1 sec * 2 bytes/frame = 8 bytes capacity
123        let mut buf = AudioRingBuffer::new(config, 1.0);
124        assert_eq!(buf.capacity(), 8);
125
126        buf.push(&[1, 2, 3, 4, 5, 6, 7, 8]);
127        assert!(buf.is_full());
128
129        // Push more, should overwrite oldest
130        buf.push(&[9, 10]);
131        let data = buf.read_all();
132        assert_eq!(data, vec![3, 4, 5, 6, 7, 8, 9, 10]);
133    }
134
135    #[test]
136    fn test_ring_buffer_clear() {
137        let config = AudioConfig::speech();
138        let mut buf = AudioRingBuffer::new(config, 0.01);
139        buf.push(&[1, 2, 3, 4]);
140        buf.clear();
141        assert!(buf.is_empty());
142        assert_eq!(buf.len(), 0);
143    }
144}