Skip to main content

aria_kit/audio/
recording.rs

1//! Reading a recording while capture is still writing it.
2//!
3//! Capture appends samples from its own thread; whoever transcribes takes them
4//! as they arrive. A backend that needs the whole recording asks for it and
5//! waits. Streaming transcription asks for one chunk at a time and starts
6//! before capture ends.
7
8use std::ops::Range;
9use std::sync::{Arc, Condvar, Mutex, MutexGuard};
10use std::time::Duration;
11#[cfg(not(target_family = "wasm"))]
12use std::time::Instant;
13
14#[cfg(not(target_family = "wasm"))]
15use super::MAX_RECORDING_DURATION;
16use super::canonical_duration;
17#[cfg(not(target_family = "wasm"))]
18use super::spool::WavSpool;
19#[cfg(not(target_family = "wasm"))]
20use super::{MicrophoneError, SpoolError, WavError, WavReplayError};
21#[cfg(not(target_family = "wasm"))]
22use super::{f32_from_pcm16, pcm16_from_f32};
23
24// The source-specific error types stay with their backends.
25#[derive(Debug, thiserror::Error)]
26pub enum CaptureError {
27    #[cfg(not(target_family = "wasm"))]
28    #[error(transparent)]
29    Microphone(#[from] MicrophoneError),
30
31    #[cfg(not(target_family = "wasm"))]
32    #[error(transparent)]
33    WavReplay(#[from] WavReplayError),
34
35    #[cfg(not(target_family = "wasm"))]
36    #[error(transparent)]
37    Wav(#[from] WavError),
38
39    #[cfg(not(target_family = "wasm"))]
40    #[error(transparent)]
41    Spool(#[from] SpoolError),
42
43    #[error("no speech was detected within {0:?}")]
44    NoSpeech(Duration),
45
46    #[error("recording exceeded its safety deadline of {0:?}")]
47    DeadlineExceeded(Duration),
48
49    #[error("the capture source stopped before the recording ended")]
50    SourceGone,
51
52    #[error("the recording already failed and its cause was reported")]
53    AlreadyFailed,
54
55    #[cfg(target_family = "wasm")]
56    #[error("the recording is still capturing and this target cannot wait for it")]
57    StillCapturing,
58}
59
60/// The result of a finished recording: its length and, on a target with a
61/// filesystem, its WAV. A WAV this crate wrote is deleted when its owner
62/// drops. A caller's own file is only borrowed. A recording ended by
63/// [`RecordingWriter::finish`] has none.
64pub struct RecordedAudio {
65    #[cfg(not(target_family = "wasm"))]
66    storage: Storage,
67    samples: u64,
68}
69
70#[cfg(not(target_family = "wasm"))]
71enum Storage {
72    Owned(tempfile::TempPath),
73    Borrowed(std::path::PathBuf),
74    Unsaved,
75}
76
77impl RecordedAudio {
78    pub fn duration(&self) -> Duration {
79        canonical_duration(self.samples)
80    }
81
82    fn from_sample_count(samples: u64) -> Self {
83        Self {
84            #[cfg(not(target_family = "wasm"))]
85            storage: Storage::Unsaved,
86            samples,
87        }
88    }
89}
90
91#[cfg(not(target_family = "wasm"))]
92impl RecordedAudio {
93    pub(super) fn owned(path: tempfile::TempPath, samples: u64) -> Self {
94        Self {
95            storage: Storage::Owned(path),
96            samples,
97        }
98    }
99
100    pub(super) fn borrowed(path: std::path::PathBuf, samples: u64) -> Self {
101        Self {
102            storage: Storage::Borrowed(path),
103            samples,
104        }
105    }
106
107    /// `None` for a recording ended by [`RecordingWriter::finish`], which
108    /// never touched the filesystem.
109    pub fn path(&self) -> Option<&std::path::Path> {
110        match &self.storage {
111            Storage::Owned(path) => Some(path.as_ref()),
112            Storage::Borrowed(path) => Some(path),
113            Storage::Unsaved => None,
114        }
115    }
116}
117
118/// The two ends of one recording, before capture begins.
119pub(super) fn ends() -> (RecordingReader, RecordingWriter) {
120    let recording = Arc::new(Recording::new());
121    let (queue, _from) = recording.enrol();
122    let reader = RecordingReader {
123        recording: Arc::clone(&recording),
124        samples: Vec::new(),
125        offset: 0,
126        keep_samples: true,
127        queue,
128        owns_outcome: true,
129        outcome: None,
130    };
131    (reader, RecordingWriter { recording })
132}
133
134/// What one capture's writer and readers share: each reader's queue of
135/// unread samples, the count written, and how capture ended. It keeps no
136/// history: a sample lives only in the queues of readers yet to take it.
137pub struct Recording {
138    state: Mutex<State>,
139    changed: Condvar,
140}
141
142#[derive(Default)]
143struct State {
144    /// Capture is live: audio is flowing, whether or not any is kept yet.
145    capturing: bool,
146    /// One queue per live reader. Capture writes into every one of them, so a
147    /// reader falling behind holds its own memory and nobody else's.
148    queues: Vec<Queue>,
149    /// Counts up, so an id stays unique after other queues retire.
150    next_queue: u64,
151    /// Samples written so far. Stream positions count from the first.
152    appended: usize,
153    ended: Option<Ended>,
154}
155
156/// What capture has written that one reader has not taken yet.
157struct Queue {
158    id: u64,
159    pending: Vec<f32>,
160}
161
162impl State {
163    fn enrol(&mut self) -> u64 {
164        let id = self.next_queue;
165        self.next_queue += 1;
166        self.queues.push(Queue {
167            id,
168            pending: Vec::new(),
169        });
170        id
171    }
172
173    /// One reader's queue. A live `RecordingReader` always has one — its
174    /// `Drop` is what retires it — so a missing queue is a bug rather than an
175    /// ending.
176    fn queue(&mut self, id: u64) -> &mut Queue {
177        self.queues
178            .iter_mut()
179            .find(|queue| queue.id == id)
180            .expect("a live reader holds its own queue")
181    }
182}
183
184struct Ended {
185    /// Whether capture failed. Forks read this: the owning reader moves
186    /// `outcome` out, leaving `AlreadyFailed` in its place either way.
187    failed: bool,
188    outcome: Result<RecordedAudio, CaptureError>,
189    #[cfg(not(target_family = "wasm"))]
190    at: Instant,
191}
192
193/// Room for `samples` plus a second, within the longest spool allowed.
194#[cfg(not(target_family = "wasm"))]
195fn spool_room(samples: usize) -> Duration {
196    (canonical_duration(samples as u64) + Duration::from_secs(1)).min(MAX_RECORDING_DURATION)
197}
198
199impl Recording {
200    /// A new recording's two ends. Capture is live at once.
201    pub fn open() -> (RecordingReader, RecordingWriter) {
202        let (reader, writer) = ends();
203        writer.begin();
204        (reader, writer)
205    }
206
207    #[cfg(not(target_family = "wasm"))]
208    pub fn open_spooled(
209        directory: &std::path::Path,
210        maximum_duration: Duration,
211    ) -> Result<(RecordingReader, SpooledRecordingWriter), CaptureError> {
212        let (reader, writer) = Self::open();
213        let spool = WavSpool::create(directory, maximum_duration, &writer)?;
214        Ok((reader, SpooledRecordingWriter { writer, spool }))
215    }
216}
217
218#[cfg(not(target_family = "wasm"))]
219pub struct SpooledRecordingWriter {
220    writer: RecordingWriter,
221    spool: WavSpool,
222}
223
224#[cfg(not(target_family = "wasm"))]
225impl SpooledRecordingWriter {
226    pub fn push(&mut self, samples: &[i16]) -> Result<(), SpoolError> {
227        self.spool.write_samples(samples)
228    }
229
230    pub fn finish(self) {
231        self.writer
232            .close(self.spool.finish().map_err(CaptureError::from));
233    }
234}
235
236/// The write end of a [`Recording`].
237///
238/// Dropping this without [`RecordingWriter::finish`] ends the recording the
239/// way a vanished capture thread does.
240pub struct RecordingWriter {
241    recording: Arc<Recording>,
242}
243
244impl RecordingWriter {
245    pub fn push(&mut self, samples: &[f32]) {
246        self.recording.extend(samples.iter().copied());
247    }
248
249    /// Ends the recording. A reader sees every pushed sample.
250    pub fn finish(self) {
251        let appended = self.recording.lock().appended as u64;
252        self.close(Ok(RecordedAudio::from_sample_count(appended)));
253    }
254
255    #[cfg(not(target_family = "wasm"))]
256    pub(super) fn recording(&self) -> Arc<Recording> {
257        Arc::clone(&self.recording)
258    }
259
260    #[cfg(not(target_family = "wasm"))]
261    pub(super) fn push_pcm16(&self, samples: &[i16]) {
262        self.recording.append(samples);
263    }
264
265    /// Capture is live from here on. The first sample can come later — a
266    /// microphone holds back leading silence — so this mark is when the
267    /// recording started.
268    pub(super) fn begin(&self) {
269        self.recording.begin();
270    }
271
272    pub(super) fn close(&self, outcome: Result<RecordedAudio, CaptureError>) {
273        self.recording.close(outcome);
274    }
275}
276
277impl Drop for RecordingWriter {
278    fn drop(&mut self) {
279        self.recording.close(Err(CaptureError::SourceGone));
280    }
281}
282
283impl Recording {
284    fn new() -> Self {
285        Self {
286            state: Mutex::default(),
287            changed: Condvar::new(),
288        }
289    }
290
291    fn begin(&self) {
292        let mut state = self.lock();
293        state.capturing = true;
294        drop(state);
295        self.changed.notify_all();
296    }
297
298    /// Hands the readers 16-bit PCM samples as floats.
299    #[cfg(not(target_family = "wasm"))]
300    pub(super) fn append(&self, samples: &[i16]) {
301        self.extend(samples.iter().copied().map(f32_from_pcm16));
302    }
303
304    fn extend(&self, samples: impl ExactSizeIterator<Item = f32>) {
305        let mut state = self.lock();
306        if state.ended.is_some() {
307            return;
308        }
309        let count = samples.len();
310        state.appended += count;
311        // Written once into the first queue, then copied across from there,
312        // so the iterator is walked once however many readers there are.
313        if let Some((first, rest)) = state.queues.split_first_mut() {
314            first.pending.extend(samples);
315            let written = first.pending.len().saturating_sub(count);
316            for queue in rest {
317                queue.pending.extend_from_slice(&first.pending[written..]);
318            }
319        }
320        drop(state);
321        self.changed.notify_all();
322    }
323
324    /// Opens a queue for one reader, and says where it opened: the recording
325    /// keeps no history, so a reader enrolled late begins where capture stands.
326    ///
327    /// Both under one lock. Read separately, a write landing between them
328    /// would go into the new queue and be counted in the origin too, which
329    /// shifts every position that reader ever reports.
330    fn enrol(&self) -> (u64, u64) {
331        let mut state = self.lock();
332        let id = state.enrol();
333        (id, state.appended as u64)
334    }
335
336    /// Closes a reader's queue, so capture stops copying into it.
337    fn retire(&self, queue: u64) {
338        self.lock().queues.retain(|held| held.id != queue);
339    }
340
341    /// The first outcome stands, so a reader learns why capture stopped rather
342    /// than what tidied up after it.
343    fn close(&self, outcome: Result<RecordedAudio, CaptureError>) {
344        let mut state = self.lock();
345        if state.ended.is_none() {
346            state.ended = Some(Ended {
347                failed: outcome.is_err(),
348                outcome,
349                #[cfg(not(target_family = "wasm"))]
350                at: Instant::now(),
351            });
352        }
353        drop(state);
354        self.changed.notify_all();
355    }
356
357    fn lock(&self) -> MutexGuard<'_, State> {
358        self.state
359            .lock()
360            .unwrap_or_else(|poisoned| poisoned.into_inner())
361    }
362}
363
364/// A window onto a recording, readable while capture is still writing it.
365/// One reader; [`RecordingReader::fork`] adds another.
366///
367/// The window holds what this reader has collected and not yet released.
368/// Audio is addressed by stream position — counted from the first sample
369/// capture ever appended — so a position keeps meaning what it meant after a
370/// release moves the window forward.
371/// Wakes a recording's waiting readers, so a reader waiting for audio
372/// notices whatever else it waits for.
373#[cfg(all(feature = "silero", not(target_family = "wasm")))]
374#[derive(Clone)]
375pub struct RecordingWaker(Arc<Recording>);
376
377#[cfg(all(feature = "silero", not(target_family = "wasm")))]
378impl RecordingWaker {
379    pub fn wake(&self) {
380        // Taken so a reader between checking and sleeping cannot miss it.
381        drop(self.0.lock());
382        self.0.changed.notify_all();
383    }
384}
385
386pub struct RecordingReader {
387    recording: Arc<Recording>,
388    samples: Vec<f32>,
389    /// Stream position of `samples[0]`.
390    offset: u64,
391    /// Cleared by a reader that only wants the WAV, so a recording nobody
392    /// reads sample by sample is not held in memory twice.
393    keep_samples: bool,
394    /// Which queue in the recording this reader drains.
395    queue: u64,
396    /// Whether the finished WAV and the failure cause are this reader's to
397    /// take. Exactly one reader per recording owns them.
398    owns_outcome: bool,
399    outcome: Option<Ending>,
400}
401
402/// How the recording ended, for one reader. A fork learns the end and
403/// whether capture failed, but gets neither the audio nor the cause.
404enum Ending {
405    Recorded(Option<RecordedAudio>),
406    Failed,
407}
408
409impl RecordingReader {
410    /// A finished recording of canonical 16 kHz mono `samples`, held in
411    /// memory.
412    pub fn from_samples(samples: &[f32]) -> Self {
413        let (reader, mut writer) = Recording::open();
414        writer.push(samples);
415        writer.finish();
416        reader
417    }
418
419    /// [`RecordingReader::from_samples`], saved as a private WAV in
420    /// `spool_dir`.
421    #[cfg(not(target_family = "wasm"))]
422    pub fn from_samples_in(
423        spool_dir: &std::path::Path,
424        samples: &[f32],
425    ) -> Result<Self, CaptureError> {
426        let pcm16: Vec<i16> = samples.iter().copied().map(pcm16_from_f32).collect();
427        Self::from_pcm16_in(spool_dir, &pcm16)
428    }
429
430    /// A finished recording of canonical samples already in 16-bit PCM, saved
431    /// as a private WAV in `spool_dir`.
432    #[cfg(not(target_family = "wasm"))]
433    pub fn from_pcm16_in(
434        spool_dir: &std::path::Path,
435        samples: &[i16],
436    ) -> Result<Self, CaptureError> {
437        let (recording, writer) = ends();
438        let mut spool = WavSpool::create(spool_dir, spool_room(samples.len()), &writer)?;
439        spool.write_samples(samples)?;
440        writer.close(spool.finish().map_err(CaptureError::from));
441        Ok(recording)
442    }
443
444    /// Waits until capture is live or the recording has ended, and reports
445    /// how a recording that ended before capturing failed.
446    #[cfg(not(target_family = "wasm"))]
447    pub(crate) fn wait_for_capture(&mut self) -> Result<(), CaptureError> {
448        let mut state = self.recording.lock();
449        while !state.capturing && state.ended.is_none() {
450            state = self.wait_for_more(state)?;
451        }
452        let capturing = state.capturing;
453        drop(state);
454        if capturing { Ok(()) } else { self.fill() }
455    }
456
457    /// Waits until the window reaches `position`, or until capture ends short
458    /// of it. Returns whether it reached `position`.
459    pub fn fill_through(&mut self, position: u64) -> Result<bool, CaptureError> {
460        while self.outcome.is_none() && self.held_to() < position {
461            self.take()?;
462        }
463        self.failure()?;
464        Ok(self.held_to() >= position)
465    }
466
467    #[cfg(not(target_family = "wasm"))]
468    /// Takes whatever audio has arrived, without waiting for more.
469    pub fn fill_available(&mut self) -> Result<(), CaptureError> {
470        if self.outcome.is_some() {
471            return self.failure();
472        }
473        let mut state = self.recording.lock();
474        let ready = !state.queue(self.queue).pending.is_empty() || state.ended.is_some();
475        drop(state);
476        if ready {
477            self.take()?;
478        }
479        Ok(())
480    }
481
482    /// Waits for capture to end.
483    pub fn fill(&mut self) -> Result<(), CaptureError> {
484        while self.outcome.is_none() {
485            self.take()?;
486        }
487        self.failure()
488    }
489
490    /// A second reader of the same recording, with its own window and its own
491    /// release floor: what one reader frees, the other still holds.
492    ///
493    /// The recording keeps no history, so a fork opens where capture stands
494    /// rather than at the beginning — ask [`RecordingReader::held_from`] where
495    /// that was.
496    ///
497    /// Every sample written from here on is copied into its queue until it
498    /// drops, so a fork nobody reads grows without bound.
499    pub fn fork(&self) -> Self {
500        let (queue, from) = self.recording.enrol();
501        Self {
502            recording: Arc::clone(&self.recording),
503            samples: Vec::new(),
504            offset: from,
505            keep_samples: true,
506            queue,
507            owns_outcome: false,
508            outcome: None,
509        }
510    }
511
512    /// A fork that starts from this reader's window, not where capture
513    /// stands. It copies what this reader holds once it takes its queue.
514    pub fn fork_window(&mut self) -> Self {
515        // One lock for both steps, so no write lands between them.
516        let mut state = self.recording.lock();
517        let pending = std::mem::take(&mut state.queue(self.queue).pending);
518        let queue = state.enrol();
519        drop(state);
520        if self.keep_samples {
521            self.samples.extend(pending);
522        } else {
523            self.offset += pending.len() as u64;
524        }
525        Self {
526            recording: Arc::clone(&self.recording),
527            samples: self.samples.clone(),
528            offset: self.offset,
529            keep_samples: true,
530            queue,
531            owns_outcome: false,
532            outcome: None,
533        }
534    }
535
536    /// Stream position of the first sample this reader still holds.
537    pub fn held_from(&self) -> u64 {
538        self.offset
539    }
540
541    /// Samples this reader has collected and not yet released.
542    pub fn samples(&self) -> &[f32] {
543        &self.samples
544    }
545
546    /// Stream position just past the last sample held.
547    pub fn held_to(&self) -> u64 {
548        self.offset + self.samples.len() as u64
549    }
550
551    /// The audio in `span`, clamped to the window. A span that ran off either
552    /// edge yields what survives of it, which may be nothing.
553    pub fn span(&self, span: Range<u64>) -> &[f32] {
554        let start = span.start.clamp(self.offset, self.held_to()) - self.offset;
555        let end = span.end.clamp(self.offset, self.held_to()) - self.offset;
556        &self.samples[start as usize..end.max(start) as usize]
557    }
558
559    /// Frees this reader's audio before `position`; other readers keep their
560    /// own copies.
561    pub fn release_before(&mut self, position: u64) {
562        let freed = position.clamp(self.offset, self.held_to()) - self.offset;
563        self.samples.drain(..freed as usize);
564        self.offset += freed;
565    }
566
567    /// Whether this reader has seen capture end. It learns that only when it
568    /// reads past the audio it holds, so it can lag.
569    /// [`RecordingReader::ended_at`] does not.
570    pub fn reached_end(&self) -> bool {
571        self.outcome.is_some()
572    }
573
574    /// The finished WAV. Waits for capture to end, and keeps none of the
575    /// samples it waits through.
576    ///
577    /// # Panics
578    ///
579    /// On a fork: only the reader that opened the recording owns its audio.
580    pub fn finished_audio(&mut self) -> Result<&RecordedAudio, CaptureError> {
581        assert!(
582            self.owns_outcome,
583            "only the reader that opened a recording has its audio"
584        );
585        self.keep_samples = false;
586        self.offset = self.held_to();
587        self.samples = Vec::new();
588        self.fill()?;
589        match &self.outcome {
590            Some(Ending::Recorded(Some(audio))) => Ok(audio),
591            _failed => Err(CaptureError::AlreadyFailed),
592        }
593    }
594
595    /// Waits up to `timeout` for capture to end, reading nothing. Returns
596    /// whether it ended.
597    #[cfg(not(target_family = "wasm"))]
598    #[must_use]
599    pub fn wait_for_end(&self, timeout: Duration) -> bool {
600        let state = self.recording.lock();
601        let (state, _timed_out) = self
602            .recording
603            .changed
604            .wait_timeout_while(state, timeout, |state| state.ended.is_none())
605            .unwrap_or_else(|poisoned| poisoned.into_inner());
606        state.ended.is_some()
607    }
608
609    /// Waits up to `timeout` for audio this reader has not taken, for
610    /// capture to end, or for `stopped`, reading nothing. A stop that rings
611    /// the [`RecordingWaker`] ends the wait at once; one that is only
612    /// polled is seen at the timeout.
613    #[cfg(all(feature = "silero", not(target_family = "wasm")))]
614    pub(crate) fn wait_for_audio(&self, timeout: Duration, stopped: impl Fn() -> bool) {
615        let queue = self.queue;
616        let state = self.recording.lock();
617        let _ = self
618            .recording
619            .changed
620            .wait_timeout_while(state, timeout, |state| {
621                state.queue(queue).pending.is_empty() && state.ended.is_none() && !stopped()
622            })
623            .unwrap_or_else(|poisoned| poisoned.into_inner());
624    }
625
626    /// Wakes this recording's waiting readers without writing to it.
627    #[cfg(all(feature = "silero", not(target_family = "wasm")))]
628    pub fn waker(&self) -> RecordingWaker {
629        RecordingWaker(Arc::clone(&self.recording))
630    }
631
632    /// When capture ended. Unlike [`RecordingReader::reached_end`], it does not wait
633    /// for this reader to catch up.
634    #[cfg(not(target_family = "wasm"))]
635    pub fn ended_at(&self) -> Option<Instant> {
636        self.recording.lock().ended.as_ref().map(|ended| ended.at)
637    }
638
639    #[cfg(not(target_family = "wasm"))]
640    fn wait_for_more<'state>(
641        &self,
642        state: MutexGuard<'state, State>,
643    ) -> Result<MutexGuard<'state, State>, CaptureError> {
644        Ok(self
645            .recording
646            .changed
647            .wait(state)
648            .unwrap_or_else(|poisoned| poisoned.into_inner()))
649    }
650
651    /// Whoever feeds this recording runs on the reader's own thread, so
652    /// waiting would wait forever.
653    #[cfg(target_family = "wasm")]
654    fn wait_for_more<'state>(
655        &self,
656        _state: MutexGuard<'state, State>,
657    ) -> Result<MutexGuard<'state, State>, CaptureError> {
658        Err(CaptureError::StillCapturing)
659    }
660
661    /// Stream position just past the last sample capture has written, whether
662    /// or not this reader has taken it.
663    pub fn written(&self) -> u64 {
664        self.recording.lock().appended as u64
665    }
666
667    /// How much audio capture has written, whether or not it has been read.
668    pub fn duration(&self) -> Duration {
669        canonical_duration(self.written())
670    }
671
672    /// Waits for capture to append or end.
673    fn take(&mut self) -> Result<(), CaptureError> {
674        let mut state = self.recording.lock();
675        // Another reader's queue filling wakes this one too, so the wait
676        // re-checks its own rather than trusting the notification.
677        while state.queue(self.queue).pending.is_empty() && state.ended.is_none() {
678            state = self.wait_for_more(state)?;
679        }
680        let pending = &mut state.queue(self.queue).pending;
681        if self.keep_samples {
682            self.samples.append(pending);
683        } else {
684            // Discarded audio still moves the window, so a position keeps
685            // pointing where it did.
686            self.offset += pending.len() as u64;
687            pending.clear();
688        }
689
690        let Some(ended) = &mut state.ended else {
691            return Ok(());
692        };
693        if !self.owns_outcome {
694            // The WAV and the cause belong to whoever opened the recording.
695            // A fork learns only that there is no more audio coming.
696            let failed = ended.failed;
697            drop(state);
698            self.outcome = Some(match failed {
699                true => Ending::Failed,
700                false => Ending::Recorded(None),
701            });
702            return match failed {
703                true => Err(CaptureError::AlreadyFailed),
704                false => Ok(()),
705            };
706        }
707        // The outcome moves to this reader, its only owner. Nothing reads the
708        // placeholder left behind.
709        let outcome = std::mem::replace(&mut ended.outcome, Err(CaptureError::AlreadyFailed));
710        drop(state);
711        match outcome {
712            Ok(audio) => {
713                self.outcome = Some(Ending::Recorded(Some(audio)));
714                Ok(())
715            }
716            Err(error) => {
717                self.outcome = Some(Ending::Failed);
718                Err(error)
719            }
720        }
721    }
722
723    fn failure(&self) -> Result<(), CaptureError> {
724        match &self.outcome {
725            Some(Ending::Failed) => Err(CaptureError::AlreadyFailed),
726            _live_or_recorded => Ok(()),
727        }
728    }
729}
730
731/// Retires this reader's queue, so capture stops copying audio nobody will
732/// read into it.
733impl Drop for RecordingReader {
734    fn drop(&mut self) {
735        self.recording.retire(self.queue);
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::super::capture::MicrophoneError;
742    use super::super::testing::{recorded, spooled};
743
744    use super::*;
745
746    const SAMPLES: [i16; 4] = [0, 16_384, -16_384, 32_767];
747
748    #[test]
749    fn available_reads_return_at_once_then_observe_audio_and_completion() {
750        let (mut recording, mut writer) = Recording::open();
751        recording.fill_available().unwrap();
752        assert_eq!(recording.held_to(), 0);
753        assert!(!recording.reached_end());
754
755        writer.push(&[0.25, 0.5]);
756        recording.fill_available().unwrap();
757        assert_eq!(recording.samples(), [0.25, 0.5]);
758        assert!(!recording.reached_end());
759
760        writer.finish();
761        recording.fill_available().unwrap();
762        assert!(recording.reached_end());
763    }
764
765    #[test]
766    fn available_reads_report_a_dropped_writer() {
767        let (mut recording, writer) = Recording::open();
768        drop(writer);
769        assert!(matches!(
770            recording.fill_available(),
771            Err(CaptureError::SourceGone)
772        ));
773        assert!(matches!(
774            recording.fill_available(),
775            Err(CaptureError::AlreadyFailed)
776        ));
777    }
778
779    #[test]
780    fn live_spools_keep_stream_and_file_equal_and_own_cleanup() {
781        let directory = tempfile::tempdir().unwrap();
782        let (mut recording, mut writer) =
783            Recording::open_spooled(directory.path(), Duration::from_secs(1)).unwrap();
784        writer.push(&SAMPLES).unwrap();
785        recording.fill_available().unwrap();
786        assert_eq!(recording.samples(), expected());
787        assert!(!recording.reached_end());
788
789        writer.finish();
790        let path = recording
791            .finished_audio()
792            .unwrap()
793            .path()
794            .unwrap()
795            .to_owned();
796        let samples = hound::WavReader::open(&path)
797            .unwrap()
798            .into_samples::<i16>()
799            .collect::<Result<Vec<_>, _>>()
800            .unwrap();
801        assert_eq!(samples, SAMPLES);
802        assert!(path.exists());
803        drop(recording);
804        assert!(!path.exists());
805    }
806
807    #[test]
808    fn live_spools_enforce_the_limit_and_fail_when_abandoned() {
809        let directory = tempfile::tempdir().unwrap();
810        let (mut recording, mut writer) =
811            Recording::open_spooled(directory.path(), Duration::from_millis(1)).unwrap();
812        assert!(matches!(
813            writer.push(&[0; 17]),
814            Err(SpoolError::LimitExceeded { .. })
815        ));
816        drop(writer);
817        assert!(matches!(recording.fill(), Err(CaptureError::SourceGone)));
818        assert_eq!(std::fs::read_dir(directory.path()).unwrap().count(), 0);
819    }
820
821    /// One recording fed a batch at a time from another thread, as capture feeds
822    /// one, so the reader blocks between batches.
823    fn streamed(batches: Vec<Vec<i16>>) -> (tempfile::TempDir, RecordingReader) {
824        let (directory, writer, mut spool, recording) = spooled();
825        std::thread::spawn(move || {
826            for batch in batches {
827                spool.write_samples(&batch).expect("samples are writable");
828            }
829            writer.close(spool.finish().map_err(CaptureError::from));
830        });
831        (directory, recording)
832    }
833
834    fn expected() -> Vec<f32> {
835        SAMPLES.iter().copied().map(f32_from_pcm16).collect()
836    }
837
838    #[test]
839    fn a_fork_reads_the_audio_the_first_reader_already_took() {
840        let (mut recording, mut capture) = Recording::open();
841        let mut forked = recording.fork();
842        capture.push(&[0.25, 0.5]);
843        capture.finish();
844
845        recording.fill().expect("the recording ends");
846        forked.fill().expect("the fork ends too");
847
848        assert_eq!(recording.samples(), [0.25, 0.5]);
849        assert_eq!(
850            forked.samples(),
851            [0.25, 0.5],
852            "one reader taking the audio must not empty it for the other"
853        );
854    }
855
856    #[test]
857    fn a_fork_opens_where_capture_stands_rather_than_at_the_beginning() {
858        let (mut recording, mut capture) = Recording::open();
859        capture.push(&[0.1, 0.2]);
860        recording.take().expect("the first batch arrives");
861        let mut forked = recording.fork();
862        capture.push(&[0.3]);
863        capture.finish();
864
865        forked.fill().expect("the fork ends");
866
867        assert_eq!(forked.held_from(), 2, "the fork opens past the spent audio");
868        assert_eq!(forked.samples(), [0.3]);
869        assert_eq!(
870            forked.held_to(),
871            3,
872            "and still counts from the first sample ever"
873        );
874    }
875
876    #[test]
877    fn a_window_fork_keeps_audio_written_before_it() {
878        let (mut recording, mut capture) = Recording::open();
879        capture.push(&[0.1, 0.2]);
880        let mut forked = recording.fork_window();
881        capture.push(&[0.3]);
882        capture.finish();
883
884        forked.fill().expect("the fork ends");
885        recording.fill().expect("the recording ends");
886
887        assert_eq!(forked.held_from(), 0);
888        assert_eq!(forked.samples(), [0.1, 0.2, 0.3]);
889        assert_eq!(recording.samples(), [0.1, 0.2, 0.3]);
890    }
891
892    #[test]
893    fn a_window_fork_opens_where_the_reader_still_holds() {
894        let (mut recording, mut capture) = Recording::open();
895        capture.push(&[0.1, 0.2]);
896        recording.take().expect("the first batch arrives");
897        recording.release_before(1);
898        let mut forked = recording.fork_window();
899        capture.push(&[0.3]);
900        capture.finish();
901
902        forked.fill().expect("the fork ends");
903
904        assert_eq!(forked.held_from(), 1);
905        assert_eq!(forked.samples(), [0.2, 0.3]);
906    }
907
908    #[test]
909    fn a_fork_counts_from_where_its_own_audio_starts() {
910        // Enrolling the queue and reading the origin have to happen under one
911        // lock. Split them and a write landing between lands in the queue and
912        // in the origin both, shifting every position the fork reports — so
913        // this forks against a writer that is already running.
914        let (recording, mut capture) = Recording::open();
915        let writing = std::thread::spawn(move || {
916            for sample in 0..4_000 {
917                capture.push(&[sample as f32]);
918            }
919            capture.finish();
920        });
921
922        for _ in 0..400 {
923            let mut forked = recording.fork();
924            if forked.take().is_err() {
925                break;
926            }
927            // Each sample carries its own stream position as its value.
928            if let Some(&first) = forked.samples().first() {
929                assert_eq!(
930                    first as u64,
931                    forked.held_from(),
932                    "a fork's first sample must sit where the fork says it does"
933                );
934            }
935        }
936        writing.join().expect("capture finishes");
937    }
938
939    #[test]
940    fn releasing_on_one_reader_leaves_the_other_window_alone() {
941        let (mut recording, mut capture) = Recording::open();
942        let mut forked = recording.fork();
943        capture.push(&[0.1, 0.2, 0.3]);
944        capture.finish();
945        recording.fill().expect("the recording ends");
946        forked.fill().expect("the fork ends");
947
948        recording.release_before(2);
949
950        assert_eq!(
951            recording.samples(),
952            [0.3],
953            "the reader freed its own prefix"
954        );
955        assert_eq!(
956            forked.samples(),
957            [0.1, 0.2, 0.3],
958            "a track walking behind must still be able to read it"
959        );
960    }
961
962    #[test]
963    fn the_finished_audio_belongs_to_the_reader_that_opened_the_recording() {
964        let (_directory, mut recording) = recorded(&SAMPLES);
965        let mut forked = recording.fork();
966
967        forked.fill().expect("the fork sees a clean end");
968
969        assert!(
970            forked.reached_end(),
971            "the fork learns there is no more audio"
972        );
973        assert!(
974            recording.finished_audio().is_ok(),
975            "and leaves the WAV for whoever opened the recording"
976        );
977    }
978
979    #[test]
980    fn a_failed_capture_ends_a_fork_with_the_same_verdict() {
981        let (_directory, writer, _spool, recording) = spooled();
982        let mut forked = recording.fork();
983        writer.close(Err(CaptureError::SourceGone));
984
985        let failure = forked.fill().expect_err("a failed capture fails its fork");
986
987        assert!(matches!(failure, CaptureError::AlreadyFailed));
988    }
989
990    #[test]
991    fn a_dropped_fork_stops_costing_capture_anything() {
992        let (reader, mut capture) = Recording::open();
993        let forked = reader.fork();
994        assert_eq!(reader.recording.lock().queues.len(), 2);
995
996        drop(forked);
997        capture.push(&[0.1]);
998
999        let state = reader.recording.lock();
1000        assert_eq!(
1001            state.queues.len(),
1002            1,
1003            "capture still copies into a dead queue"
1004        );
1005        assert_eq!(state.queues[0].pending.len(), 1);
1006    }
1007
1008    #[test]
1009    fn two_readers_waiting_are_both_woken_by_one_write() {
1010        let (mut recording, mut capture) = Recording::open();
1011        let mut forked = recording.fork();
1012        let waiting = std::thread::spawn(move || {
1013            forked.fill_through(1).expect("the fork is woken");
1014            forked.held_to()
1015        });
1016
1017        capture.push(&[0.5]);
1018        recording.fill_through(1).expect("the reader is woken");
1019
1020        assert_eq!(recording.held_to(), 1);
1021        assert_eq!(waiting.join().expect("the fork thread finishes"), 1);
1022    }
1023
1024    #[test]
1025    fn a_reader_waits_for_samples_capture_has_not_written_yet() {
1026        let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
1027        let (_directory, mut recording) = streamed(batches);
1028
1029        recording.fill().expect("the recording ends");
1030
1031        assert_eq!(recording.samples(), expected());
1032    }
1033
1034    #[test]
1035    fn a_reader_can_take_a_prefix_before_the_recording_ends() {
1036        let (_directory, mut recording) = recorded(&SAMPLES);
1037
1038        recording
1039            .fill_through(2)
1040            .expect("two samples are available");
1041        let first = recording.samples()[..2].to_vec();
1042        recording.fill().expect("the rest is available");
1043
1044        assert_eq!(first, expected()[..2]);
1045        assert_eq!(recording.samples(), expected());
1046    }
1047
1048    #[test]
1049    fn asking_past_the_end_returns_what_was_recorded() {
1050        let (_directory, mut recording) = recorded(&SAMPLES);
1051
1052        recording
1053            .fill_through(SAMPLES.len() as u64 * 4)
1054            .expect("a short recording is not a failure");
1055
1056        assert!(recording.reached_end());
1057        assert_eq!(recording.samples(), expected());
1058    }
1059
1060    /// The invariant everything else rests on: the reader and the WAV are fed
1061    /// by the same write, so they can never disagree.
1062    #[test]
1063    fn what_the_reader_sees_is_what_the_wav_got() {
1064        let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
1065        let (_directory, mut recording) = streamed(batches);
1066        recording.fill().expect("the recording ends");
1067        let read = recording.samples().to_vec();
1068
1069        let audio = recording.finished_audio().expect("the recording succeeded");
1070        let written: Vec<f32> = hound::WavReader::open(audio.path().expect("a captured WAV"))
1071            .expect("the WAV is readable")
1072            .into_samples::<i16>()
1073            .map(|sample| f32_from_pcm16(sample.expect("every sample reads back")))
1074            .collect();
1075
1076        assert_eq!(read, written);
1077    }
1078
1079    #[test]
1080    fn a_recording_as_long_as_allowed_still_gets_a_spool() {
1081        let samples = crate::audio::MAX_RECORDING_SAMPLES as usize;
1082        assert!(super::super::spool::maximum_samples(spool_room(samples)).is_ok());
1083    }
1084
1085    #[test]
1086    fn a_fill_through_says_whether_it_reached_the_position() {
1087        let mut recording = RecordingReader::from_samples(&[0.0; 4]);
1088
1089        assert!(recording.fill_through(4).expect("the recording succeeded"));
1090        assert!(!recording.fill_through(5).expect("the recording succeeded"));
1091    }
1092
1093    #[test]
1094    fn waiting_for_the_end_times_out_while_capture_runs() {
1095        let (recording, writer) = Recording::open();
1096
1097        assert!(!recording.wait_for_end(Duration::from_millis(10)));
1098        writer.finish();
1099        assert!(recording.wait_for_end(Duration::from_millis(10)));
1100    }
1101
1102    #[test]
1103    fn a_recording_from_samples_holds_them_and_has_ended() {
1104        let mut recording = RecordingReader::from_samples(&[0.25, -0.5]);
1105
1106        recording.fill().expect("the recording already ended");
1107
1108        assert_eq!(recording.samples(), [0.25, -0.5]);
1109        assert_eq!(
1110            recording.finished_audio().expect("it succeeded").path(),
1111            None
1112        );
1113    }
1114
1115    #[test]
1116    fn a_pcm16_recording_keeps_every_sample_exactly() {
1117        let directory = tempfile::tempdir().expect("test directory is created");
1118        let mut recording = RecordingReader::from_pcm16_in(directory.path(), &SAMPLES)
1119            .expect("the recording is written");
1120
1121        let audio = recording.finished_audio().expect("the recording succeeded");
1122        let written: Vec<i16> = hound::WavReader::open(audio.path().expect("a saved WAV"))
1123            .expect("the WAV is readable")
1124            .into_samples::<i16>()
1125            .map(|sample| sample.expect("every sample reads back"))
1126            .collect();
1127
1128        assert_eq!(written, SAMPLES);
1129    }
1130
1131    #[test]
1132    fn the_wav_reader_keeps_none_of_the_samples_it_waits_through() {
1133        let (_directory, mut recording) = recorded(&SAMPLES);
1134
1135        recording.finished_audio().expect("the recording succeeded");
1136
1137        assert!(recording.samples().is_empty());
1138    }
1139
1140    /// Reading on past a failure must not look like an empty recording that
1141    /// simply had nothing in it.
1142    #[test]
1143    fn a_failed_recording_stays_failed_for_every_later_read() {
1144        let (mut recording, writer) = ends();
1145        writer.close(Err(CaptureError::Microphone(
1146            MicrophoneError::NoInputDevice,
1147        )));
1148        recording
1149            .fill()
1150            .expect_err("the first read reports the cause");
1151
1152        let again = recording.fill_through(1).expect_err("so does the next one");
1153        let finished = recording
1154            .finished_audio()
1155            .err()
1156            .expect("and so does the WAV");
1157
1158        assert!(matches!(again, CaptureError::AlreadyFailed));
1159        assert!(matches!(finished, CaptureError::AlreadyFailed));
1160    }
1161
1162    /// Silence appends no samples, so capture being live must not depend on
1163    /// any sample arriving.
1164    #[test]
1165    fn a_silent_capture_still_counts_as_started() {
1166        let (mut recording, writer) = ends();
1167        writer.begin();
1168
1169        recording
1170            .wait_for_capture()
1171            .expect("a live capture has started");
1172
1173        assert!(recording.samples().is_empty());
1174    }
1175
1176    #[test]
1177    fn a_capture_that_dies_before_starting_reports_its_cause() {
1178        let (mut recording, writer) = ends();
1179        writer.close(Err(CaptureError::Microphone(
1180            MicrophoneError::NoInputDevice,
1181        )));
1182
1183        let error = recording
1184            .wait_for_capture()
1185            .expect_err("the recording never started");
1186
1187        assert!(matches!(
1188            error,
1189            CaptureError::Microphone(MicrophoneError::NoInputDevice)
1190        ));
1191    }
1192
1193    #[test]
1194    fn dropping_the_capture_end_ends_the_recording() {
1195        let (mut recording, writer) = ends();
1196        drop(writer);
1197
1198        let error = recording
1199            .fill()
1200            .expect_err("a reader must not wait on a producer that is gone");
1201
1202        assert!(matches!(error, CaptureError::SourceGone));
1203    }
1204
1205    #[test]
1206    fn the_first_outcome_is_the_one_the_reader_learns() {
1207        let (mut recording, writer) = ends();
1208        writer.close(Err(CaptureError::Microphone(
1209            MicrophoneError::NoInputDevice,
1210        )));
1211        drop(writer);
1212
1213        let error = recording.fill().expect_err("the recording failed to start");
1214
1215        assert!(matches!(
1216            error,
1217            CaptureError::Microphone(MicrophoneError::NoInputDevice)
1218        ));
1219    }
1220}