gemini_genai_rs/buffer/
mod.rs

1//! Lock-free audio buffers for the hot path.
2//!
3//! - [`SpscRing`]: wait-free single-producer single-consumer ring for audio
4//!   streaming, split into a [`SpscProducer`] and an [`SpscConsumer`].
5//! - [`AudioJitterBuffer`]: adaptive jitter buffer for smooth playback of
6//!   network audio.
7
8pub mod convert;
9pub mod jitter;
10
11pub use convert::{bytes_to_i16, i16_to_bytes, into_shared};
12pub use jitter::{AudioJitterBuffer, BufferState, JitterConfig};
13
14/// Wait-free single-producer single-consumer ring buffer for audio samples.
15///
16/// [`SpscRing::channel`] hands back the two halves, the way `mpsc::channel` does. Each half is `Send` but not
17/// `Sync`, so the "exactly one producer, exactly one consumer" rule that a
18/// ring like this depends on is enforced by the type system instead of by a
19/// comment: the producer moves to the capture thread, the consumer to the
20/// playback thread, and neither can be shared.
21///
22/// Backed by [`rtrb`] (the ring used across the Rust audio ecosystem):
23/// no allocation after construction, no locks, no `unsafe` in this crate.
24///
25/// ```
26/// use gemini_genai_rs::buffer::SpscRing;
27///
28/// let (mut tx, mut rx) = SpscRing::<i16>::channel(1024);
29/// assert_eq!(tx.write(&[1, 2, 3]), 3);
30/// let mut out = [0i16; 8];
31/// assert_eq!(rx.read(&mut out), 3);
32/// assert_eq!(&out[..3], &[1, 2, 3]);
33/// ```
34#[derive(Debug)]
35pub struct SpscRing<T>(std::marker::PhantomData<T>);
36
37impl<T: Copy> SpscRing<T> {
38    /// Create a ring holding exactly `capacity` samples and split it into its
39    /// producer and consumer halves.
40    ///
41    /// # Panics
42    ///
43    /// Panics if `capacity` is 0.
44    pub fn channel(capacity: usize) -> (SpscProducer<T>, SpscConsumer<T>) {
45        assert!(capacity > 0, "ring capacity must be > 0");
46        let (producer, consumer) = rtrb::RingBuffer::new(capacity);
47        (SpscProducer(producer), SpscConsumer(consumer))
48    }
49}
50
51/// The writing half of an [`SpscRing`].
52pub struct SpscProducer<T>(rtrb::Producer<T>);
53
54/// The reading half of an [`SpscRing`].
55pub struct SpscConsumer<T>(rtrb::Consumer<T>);
56
57impl<T: Copy> SpscProducer<T> {
58    /// Write as many samples as fit; returns how many were written.
59    ///
60    /// A short write means the consumer is behind — the caller decides
61    /// whether to retry, drop, or block.
62    pub fn write(&mut self, data: &[T]) -> usize {
63        let (written, _remaining) = self.0.push_partial_slice(data);
64        written.len()
65    }
66
67    /// Number of samples that can be written right now.
68    pub fn available(&self) -> usize {
69        self.0.slots()
70    }
71
72    /// Whether a write of even one sample would fail right now.
73    pub fn is_full(&self) -> bool {
74        self.0.is_full()
75    }
76
77    /// Total capacity in samples.
78    pub fn capacity(&self) -> usize {
79        self.0.buffer().capacity()
80    }
81
82    /// Whether the consumer half has been dropped: nothing written will ever
83    /// be read.
84    pub fn is_abandoned(&self) -> bool {
85        self.0.is_abandoned()
86    }
87}
88
89impl<T: Copy> SpscConsumer<T> {
90    /// Read up to `out.len()` samples; returns how many were read.
91    pub fn read(&mut self, out: &mut [T]) -> usize {
92        let (filled, _unfilled) = self.0.pop_partial_slice(out);
93        filled.len()
94    }
95
96    /// Number of samples waiting to be read.
97    pub fn len(&self) -> usize {
98        self.0.slots()
99    }
100
101    /// Whether nothing is waiting to be read.
102    pub fn is_empty(&self) -> bool {
103        self.0.is_empty()
104    }
105
106    /// Total capacity in samples.
107    pub fn capacity(&self) -> usize {
108        self.0.buffer().capacity()
109    }
110
111    /// Discard everything buffered without reading it — the barge-in flush.
112    pub fn clear(&mut self) {
113        let n = self.0.slots();
114        if let Ok(chunk) = self.0.read_chunk(n) {
115            chunk.commit_all();
116        }
117    }
118
119    /// Whether the producer half has been dropped: once drained, nothing more
120    /// will arrive.
121    pub fn is_abandoned(&self) -> bool {
122        self.0.is_abandoned()
123    }
124}
125
126impl<T> std::fmt::Debug for SpscProducer<T> {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        f.debug_struct("SpscProducer")
129            .field("free", &self.0.slots())
130            .field("capacity", &self.0.buffer().capacity())
131            .finish()
132    }
133}
134
135impl<T> std::fmt::Debug for SpscConsumer<T> {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_struct("SpscConsumer")
138            .field("len", &self.0.slots())
139            .field("capacity", &self.0.buffer().capacity())
140            .finish()
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn capacity_is_exact() {
150        let (tx, rx) = SpscRing::<i16>::channel(100);
151        assert_eq!(tx.capacity(), 100);
152        assert_eq!(rx.capacity(), 100);
153        assert!(rx.is_empty());
154        assert_eq!(tx.available(), 100);
155    }
156
157    #[test]
158    fn write_and_read() {
159        let (mut tx, mut rx) = SpscRing::<i16>::channel(16);
160        assert_eq!(tx.write(&[1i16, 2, 3, 4, 5]), 5);
161        assert_eq!(rx.len(), 5);
162        let mut out = [0i16; 5];
163        assert_eq!(rx.read(&mut out), 5);
164        assert_eq!(out, [1, 2, 3, 4, 5]);
165        assert!(rx.is_empty());
166    }
167
168    #[test]
169    fn wraparound() {
170        let (mut tx, mut rx) = SpscRing::<i16>::channel(8);
171        tx.write(&[1i16, 2, 3, 4, 5, 6]);
172        let mut out = [0i16; 4];
173        rx.read(&mut out);
174        assert_eq!(out, [1, 2, 3, 4]);
175        assert_eq!(tx.write(&[7i16, 8, 9, 10, 11, 12]), 6);
176        let mut out2 = [0i16; 8];
177        assert_eq!(rx.read(&mut out2), 8);
178        assert_eq!(out2, [5, 6, 7, 8, 9, 10, 11, 12]);
179    }
180
181    #[test]
182    fn overflow_returns_partial() {
183        let (mut tx, _rx) = SpscRing::<i16>::channel(4);
184        assert_eq!(tx.write(&[1i16, 2, 3, 4, 5, 6]), 4);
185        assert!(tx.is_full());
186    }
187
188    #[test]
189    fn underflow_returns_partial() {
190        let (mut tx, mut rx) = SpscRing::<i16>::channel(16);
191        tx.write(&[1i16, 2, 3]);
192        let mut out = [0i16; 10];
193        assert_eq!(rx.read(&mut out), 3);
194        assert_eq!(&out[..3], &[1, 2, 3]);
195    }
196
197    #[test]
198    fn clear_discards_data() {
199        let (mut tx, mut rx) = SpscRing::<i16>::channel(16);
200        tx.write(&[1i16, 2, 3, 4, 5]);
201        assert_eq!(rx.len(), 5);
202        rx.clear();
203        assert!(rx.is_empty());
204        assert_eq!(tx.available(), 16);
205    }
206
207    #[test]
208    fn abandonment_is_visible_to_the_other_half() {
209        let (tx, rx) = SpscRing::<i16>::channel(4);
210        assert!(!tx.is_abandoned());
211        drop(rx);
212        assert!(tx.is_abandoned());
213    }
214
215    #[test]
216    fn halves_move_to_their_threads() {
217        // Each half is `Send` (it can move to the capture / playback thread);
218        // neither is `Sync`, which is what makes "one producer, one consumer"
219        // a compile-time fact rather than a comment — rtrb's own impls.
220        fn is_send<T: Send>() {}
221        is_send::<SpscProducer<i16>>();
222        is_send::<SpscConsumer<i16>>();
223    }
224
225    #[test]
226    fn concurrent_write_read() {
227        let (mut tx, mut rx) = SpscRing::<i16>::channel(1024);
228
229        let writer = std::thread::spawn(move || {
230            let mut total = 0usize;
231            for i in 0..1000 {
232                let chunk: Vec<i16> = (0..16).map(|j| (i * 16 + j) as i16).collect();
233                loop {
234                    let w = tx.write(&chunk[total % 16..]);
235                    total += w;
236                    if total >= (i as usize + 1) * 16 {
237                        break;
238                    }
239                    std::thread::yield_now();
240                }
241            }
242        });
243
244        let reader = std::thread::spawn(move || {
245            let mut total = 0usize;
246            let mut buf = [0i16; 64];
247            while total < 16000 {
248                let r = rx.read(&mut buf);
249                total += r;
250                if r == 0 {
251                    std::thread::yield_now();
252                }
253            }
254            total
255        });
256
257        writer.join().unwrap();
258        assert_eq!(reader.join().unwrap(), 16000);
259    }
260
261    #[test]
262    #[should_panic(expected = "capacity must be > 0")]
263    fn zero_capacity_panics() {
264        SpscRing::<i16>::channel(0);
265    }
266}