Skip to main content

aria_kit/synthesis/
stream.rs

1//! Playing speech while synthesis is still producing it.
2//!
3//! A synthesizer appends PCM from a worker thread; playback drains it as
4//! it arrives, so audio starts before the utterance is finished. The buffer
5//! is bounded: a writer that gets far ahead blocks until playback catches up,
6//! and a writer whose reader is gone learns it on the next write.
7
8use std::collections::VecDeque;
9use std::sync::{Arc, Condvar, Mutex, MutexGuard};
10
11use super::SynthesisError;
12
13/// How much audio the buffer holds before the writer blocks. Synthesis
14/// outruns playback, so blocking is the backpressure.
15const BUFFER_SECONDS: u32 = 10;
16
17/// The two ends of one utterance: the writer synthesis appends to, and the
18/// stream playback reads from.
19pub fn stream(sample_rate: u32) -> (SpeechWriter, SpeechStream) {
20    let shared = Arc::new(Shared {
21        state: Mutex::new(StreamState::default()),
22        changed: Condvar::new(),
23        sample_rate,
24        capacity: sample_rate as usize * BUFFER_SECONDS as usize,
25    });
26    (
27        SpeechWriter {
28            shared: Arc::clone(&shared),
29        },
30        SpeechStream {
31            shared,
32            failed: false,
33        },
34    )
35}
36
37/// The writer's reader is gone or the utterance already ended; there is
38/// nobody left to hear more samples.
39#[derive(Debug, thiserror::Error)]
40#[error("the speech stream is closed")]
41pub struct StreamClosed;
42
43/// Result of a buffered read.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum TryRead {
46    /// This many samples were copied into the front of the buffer.
47    Samples(usize),
48    /// Nothing buffered yet, but the writer is still producing.
49    Starved,
50    /// No samples are left and none are coming.
51    Ended,
52}
53
54struct Shared {
55    state: Mutex<StreamState>,
56    changed: Condvar,
57    sample_rate: u32,
58    capacity: usize,
59}
60
61#[derive(Default)]
62struct StreamState {
63    pending: VecDeque<f32>,
64    reader_gone: bool,
65    ended: Option<Result<(), SynthesisError>>,
66}
67
68impl Shared {
69    fn lock(&self) -> MutexGuard<'_, StreamState> {
70        self.state
71            .lock()
72            .unwrap_or_else(|poisoned| poisoned.into_inner())
73    }
74
75    /// The first outcome stands, so the reader learns why synthesis stopped
76    /// rather than what tidied up after it.
77    fn close(&self, outcome: Result<(), SynthesisError>) {
78        let mut state = self.lock();
79        if state.ended.is_none() {
80            state.ended = Some(outcome);
81        }
82        drop(state);
83        self.changed.notify_all();
84    }
85}
86
87/// The synthesis end of an utterance. Dropping it without [`finish`] ends the
88/// speech the way a vanished synthesizer does.
89///
90/// [`finish`]: SpeechWriter::finish
91pub struct SpeechWriter {
92    shared: Arc<Shared>,
93}
94
95impl SpeechWriter {
96    /// Appends samples, waiting while the buffer is full. Returns an error
97    /// when the reader is gone or synthesis has ended.
98    pub fn write(&mut self, samples: &[f32]) -> Result<(), StreamClosed> {
99        let mut remaining = samples;
100        while !remaining.is_empty() {
101            let mut state = self.shared.lock();
102            loop {
103                if state.reader_gone || state.ended.is_some() {
104                    return Err(StreamClosed);
105                }
106                if state.pending.len() < self.shared.capacity {
107                    break;
108                }
109                state = self
110                    .shared
111                    .changed
112                    .wait(state)
113                    .unwrap_or_else(|poisoned| poisoned.into_inner());
114            }
115            let available = self.shared.capacity - state.pending.len();
116            let appended = available.min(remaining.len());
117            state.pending.extend(&remaining[..appended]);
118            remaining = &remaining[appended..];
119            drop(state);
120            self.shared.changed.notify_all();
121        }
122        Ok(())
123    }
124
125    /// Ends the utterance. The reader drains what is buffered, then sees the
126    /// end.
127    pub fn finish(self) {
128        self.shared.close(Ok(()));
129    }
130
131    /// Ends the utterance with its cause. The reader learns `error` instead
132    /// of a bare end.
133    pub fn fail(self, error: SynthesisError) {
134        self.shared.close(Err(error));
135    }
136}
137
138impl Drop for SpeechWriter {
139    fn drop(&mut self) {
140        self.shared.close(Err(SynthesisError::SourceGone));
141    }
142}
143
144/// An utterance, readable while synthesis is still writing it.
145pub struct SpeechStream {
146    shared: Arc<Shared>,
147    failed: bool,
148}
149
150impl SpeechStream {
151    pub fn sample_rate(&self) -> u32 {
152        self.shared.sample_rate
153    }
154
155    /// Copies buffered samples into `out`, waiting until some exist or the
156    /// utterance ends. `Ok(0)` is the end; a failed utterance reports its
157    /// cause once and [`SynthesisError::AlreadyFailed`] after.
158    pub fn read(&mut self, out: &mut [f32]) -> Result<usize, SynthesisError> {
159        if out.is_empty() {
160            return Err(SynthesisError::EmptyReadBuffer);
161        }
162        let mut state = self.shared.lock();
163        while state.pending.is_empty() && state.ended.is_none() {
164            state = self
165                .shared
166                .changed
167                .wait(state)
168                .unwrap_or_else(|poisoned| poisoned.into_inner());
169        }
170        let copied = Self::drain(&mut state, out);
171        if copied > 0 {
172            drop(state);
173            self.shared.changed.notify_all();
174            return Ok(copied);
175        }
176        let outcome = state
177            .ended
178            .as_mut()
179            .expect("the wait only ends with samples or an outcome");
180        match std::mem::replace(outcome, Ok(())) {
181            Ok(()) if self.failed => Err(SynthesisError::AlreadyFailed),
182            Ok(()) => Ok(0),
183            Err(error) => {
184                self.failed = true;
185                Err(error)
186            }
187        }
188    }
189
190    /// Reads buffered samples without waiting for more audio.
191    /// May wait for the shared buffer lock.
192    pub fn try_read(&mut self, out: &mut [f32]) -> TryRead {
193        let mut state = self.shared.lock();
194        let copied = Self::drain(&mut state, out);
195        if copied > 0 {
196            drop(state);
197            self.shared.changed.notify_all();
198            return TryRead::Samples(copied);
199        }
200        if state.ended.is_some() {
201            TryRead::Ended
202        } else {
203            TryRead::Starved
204        }
205    }
206
207    fn drain(state: &mut StreamState, out: &mut [f32]) -> usize {
208        let wanted = out.len().min(state.pending.len());
209        for slot in out.iter_mut().take(wanted) {
210            *slot = state
211                .pending
212                .pop_front()
213                .expect("the drain count is bounded by the buffer length");
214        }
215        wanted
216    }
217}
218
219impl Drop for SpeechStream {
220    fn drop(&mut self) {
221        let mut state = self.shared.lock();
222        state.reader_gone = true;
223        state.pending.clear();
224        drop(state);
225        self.shared.changed.notify_all();
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    const RATE: u32 = 24_000;
234
235    #[test]
236    fn a_reader_hears_what_the_writer_wrote() {
237        let (mut writer, mut speech) = stream(RATE);
238        writer.write(&[0.1, 0.2]).expect("the reader is listening");
239        writer.write(&[0.3]).expect("the reader is listening");
240        writer.finish();
241
242        let mut heard = Vec::new();
243        let mut buffer = [0.0; 2];
244        loop {
245            match speech.read(&mut buffer).expect("the utterance succeeded") {
246                0 => break,
247                copied => heard.extend_from_slice(&buffer[..copied]),
248            }
249        }
250
251        assert_eq!(heard, [0.1, 0.2, 0.3]);
252    }
253
254    #[test]
255    fn a_reader_waits_for_samples_synthesis_has_not_written_yet() {
256        let (mut writer, mut speech) = stream(RATE);
257        std::thread::spawn(move || {
258            writer.write(&[0.5]).expect("the reader is listening");
259            writer.finish();
260        });
261
262        let mut buffer = [0.0; 4];
263        let copied = speech.read(&mut buffer).expect("a sample arrives");
264
265        assert_eq!(&buffer[..copied], [0.5]);
266    }
267
268    #[test]
269    fn a_full_buffer_blocks_the_writer_until_playback_drains_it() {
270        let (mut writer, mut speech) = stream(1);
271        let chunk = vec![0.0; BUFFER_SECONDS as usize];
272        writer.write(&chunk).expect("the buffer has room");
273
274        let blocked = std::thread::spawn(move || {
275            writer.write(&[1.0]).expect("the reader drains the buffer");
276            writer.finish();
277        });
278        let mut buffer = vec![0.0; BUFFER_SECONDS as usize];
279        while !matches!(speech.read(&mut buffer), Ok(0) | Err(_)) {}
280
281        blocked.join().expect("the writer unblocks");
282    }
283
284    #[test]
285    fn one_large_write_never_overfills_the_buffer() {
286        let (mut writer, mut speech) = stream(1);
287        let samples = vec![0.5; BUFFER_SECONDS as usize * 2];
288        let finished = std::thread::spawn(move || {
289            writer
290                .write(&samples)
291                .expect("the reader drains the buffer");
292            writer.finish();
293        });
294
295        let mut received = Vec::new();
296        let mut buffer = [0.0; BUFFER_SECONDS as usize];
297        loop {
298            let copied = speech.read(&mut buffer).expect("the utterance succeeds");
299            if copied == 0 {
300                break;
301            }
302            assert!(copied <= BUFFER_SECONDS as usize);
303            received.extend_from_slice(&buffer[..copied]);
304        }
305
306        finished.join().expect("the writer finishes");
307        assert_eq!(received.len(), BUFFER_SECONDS as usize * 2);
308    }
309
310    #[test]
311    fn dropping_the_reader_stops_the_writer() {
312        let (mut writer, speech) = stream(RATE);
313        drop(speech);
314
315        writer
316            .write(&[0.1])
317            .expect_err("nobody is left to hear the samples");
318    }
319
320    #[test]
321    fn dropping_the_writer_unfinished_fails_the_utterance() {
322        let (writer, mut speech) = stream(RATE);
323        drop(writer);
324
325        let error = speech
326            .read(&mut [0.0; 4])
327            .expect_err("a vanished synthesizer is a failure");
328
329        assert!(matches!(error, SynthesisError::SourceGone));
330    }
331
332    #[test]
333    fn a_failed_utterance_stays_failed_for_every_later_read() {
334        let (writer, mut speech) = stream(RATE);
335        writer.fail(SynthesisError::Cancelled);
336        let first = speech
337            .read(&mut [0.0; 4])
338            .expect_err("the first read reports the cause");
339
340        let again = speech
341            .read(&mut [0.0; 4])
342            .expect_err("so does the next one");
343
344        assert!(matches!(first, SynthesisError::Cancelled));
345        assert!(matches!(again, SynthesisError::AlreadyFailed));
346    }
347
348    #[test]
349    fn the_first_outcome_is_the_one_the_reader_learns() {
350        let (writer, mut speech) = stream(RATE);
351        let shared = Arc::clone(&writer.shared);
352        writer.fail(SynthesisError::Cancelled);
353        shared.close(Err(SynthesisError::WorkerPanicked { backend: "test" }));
354
355        let error = speech
356            .read(&mut [0.0; 4])
357            .expect_err("the utterance failed");
358
359        assert!(matches!(error, SynthesisError::Cancelled));
360    }
361
362    #[test]
363    fn an_empty_read_buffer_is_rejected_while_samples_wait() {
364        let (mut writer, mut speech) = stream(RATE);
365        writer.write(&[0.1]).expect("the reader is listening");
366
367        assert!(matches!(
368            speech.read(&mut []),
369            Err(SynthesisError::EmptyReadBuffer)
370        ));
371    }
372
373    #[test]
374    fn try_read_never_waits() {
375        let (mut writer, mut speech) = stream(RATE);
376
377        assert_eq!(speech.try_read(&mut [0.0; 4]), TryRead::Starved);
378
379        writer.write(&[0.7]).expect("the reader is listening");
380        let mut buffer = [0.0; 4];
381        assert_eq!(speech.try_read(&mut buffer), TryRead::Samples(1));
382        assert_eq!(buffer[0], 0.7);
383
384        writer.finish();
385        assert_eq!(speech.try_read(&mut [0.0; 4]), TryRead::Ended);
386    }
387}