1use 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#[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
60pub 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 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
118pub(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
134pub struct Recording {
138 state: Mutex<State>,
139 changed: Condvar,
140}
141
142#[derive(Default)]
143struct State {
144 capturing: bool,
146 queues: Vec<Queue>,
149 next_queue: u64,
151 appended: usize,
153 ended: Option<Ended>,
154}
155
156struct Queue {
158 id: u64,
159 pending: Vec<f32>,
160}
161
162impl State {
163 fn queue(&mut self, id: u64) -> &mut Queue {
167 self.queues
168 .iter_mut()
169 .find(|queue| queue.id == id)
170 .expect("a live reader holds its own queue")
171 }
172}
173
174struct Ended {
175 failed: bool,
178 outcome: Result<RecordedAudio, CaptureError>,
179 #[cfg(not(target_family = "wasm"))]
180 at: Instant,
181}
182
183#[cfg(not(target_family = "wasm"))]
185fn spool_room(samples: usize) -> Duration {
186 (canonical_duration(samples as u64) + Duration::from_secs(1)).min(MAX_RECORDING_DURATION)
187}
188
189impl Recording {
190 pub fn open() -> (RecordingReader, RecordingWriter) {
192 let (reader, writer) = ends();
193 writer.begin();
194 (reader, writer)
195 }
196}
197
198pub struct RecordingWriter {
203 recording: Arc<Recording>,
204}
205
206impl RecordingWriter {
207 pub fn push(&mut self, samples: &[f32]) {
208 self.recording.extend(samples.iter().copied());
209 }
210
211 pub fn finish(self) {
213 let appended = self.recording.lock().appended as u64;
214 self.close(Ok(RecordedAudio::from_sample_count(appended)));
215 }
216
217 #[cfg(not(target_family = "wasm"))]
218 pub(super) fn recording(&self) -> Arc<Recording> {
219 Arc::clone(&self.recording)
220 }
221
222 #[cfg(not(target_family = "wasm"))]
223 pub(super) fn push_pcm16(&self, samples: &[i16]) {
224 self.recording.append(samples);
225 }
226
227 pub(super) fn begin(&self) {
231 self.recording.begin();
232 }
233
234 pub(super) fn close(&self, outcome: Result<RecordedAudio, CaptureError>) {
235 self.recording.close(outcome);
236 }
237}
238
239impl Drop for RecordingWriter {
240 fn drop(&mut self) {
241 self.recording.close(Err(CaptureError::SourceGone));
242 }
243}
244
245impl Recording {
246 fn new() -> Self {
247 Self {
248 state: Mutex::default(),
249 changed: Condvar::new(),
250 }
251 }
252
253 fn begin(&self) {
254 let mut state = self.lock();
255 state.capturing = true;
256 drop(state);
257 self.changed.notify_all();
258 }
259
260 #[cfg(not(target_family = "wasm"))]
262 pub(super) fn append(&self, samples: &[i16]) {
263 self.extend(samples.iter().copied().map(f32_from_pcm16));
264 }
265
266 fn extend(&self, samples: impl ExactSizeIterator<Item = f32>) {
267 let mut state = self.lock();
268 if state.ended.is_some() {
269 return;
270 }
271 let count = samples.len();
272 state.appended += count;
273 if let Some((first, rest)) = state.queues.split_first_mut() {
276 first.pending.extend(samples);
277 let written = first.pending.len().saturating_sub(count);
278 for queue in rest {
279 queue.pending.extend_from_slice(&first.pending[written..]);
280 }
281 }
282 drop(state);
283 self.changed.notify_all();
284 }
285
286 fn enrol(&self) -> (u64, u64) {
293 let mut state = self.lock();
294 let id = state.next_queue;
295 state.next_queue += 1;
296 state.queues.push(Queue {
297 id,
298 pending: Vec::new(),
299 });
300 (id, state.appended as u64)
301 }
302
303 fn retire(&self, queue: u64) {
305 self.lock().queues.retain(|held| held.id != queue);
306 }
307
308 fn close(&self, outcome: Result<RecordedAudio, CaptureError>) {
311 let mut state = self.lock();
312 if state.ended.is_none() {
313 state.ended = Some(Ended {
314 failed: outcome.is_err(),
315 outcome,
316 #[cfg(not(target_family = "wasm"))]
317 at: Instant::now(),
318 });
319 }
320 drop(state);
321 self.changed.notify_all();
322 }
323
324 fn lock(&self) -> MutexGuard<'_, State> {
325 self.state
326 .lock()
327 .unwrap_or_else(|poisoned| poisoned.into_inner())
328 }
329}
330
331pub struct RecordingReader {
339 recording: Arc<Recording>,
340 samples: Vec<f32>,
341 offset: u64,
343 keep_samples: bool,
346 queue: u64,
348 owns_outcome: bool,
351 outcome: Option<Ending>,
352}
353
354enum Ending {
357 Recorded(Option<RecordedAudio>),
358 Failed,
359}
360
361impl RecordingReader {
362 pub fn from_samples(samples: &[f32]) -> Self {
364 let (reader, mut writer) = Recording::open();
365 writer.push(samples);
366 writer.finish();
367 reader
368 }
369
370 #[cfg(not(target_family = "wasm"))]
373 pub fn from_samples_in(
374 spool_dir: &std::path::Path,
375 samples: &[f32],
376 ) -> Result<Self, CaptureError> {
377 let pcm16: Vec<i16> = samples.iter().copied().map(pcm16_from_f32).collect();
378 Self::from_pcm16_in(spool_dir, &pcm16)
379 }
380
381 #[cfg(not(target_family = "wasm"))]
383 pub fn from_pcm16_in(
384 spool_dir: &std::path::Path,
385 samples: &[i16],
386 ) -> Result<Self, CaptureError> {
387 let (recording, writer) = ends();
388 let mut spool = WavSpool::create(spool_dir, spool_room(samples.len()), &writer)?;
389 spool.write_samples(samples)?;
390 writer.close(spool.finish().map_err(CaptureError::from));
391 Ok(recording)
392 }
393
394 #[cfg(not(target_family = "wasm"))]
397 pub(crate) fn wait_for_capture(&mut self) -> Result<(), CaptureError> {
398 let mut state = self.recording.lock();
399 while !state.capturing && state.ended.is_none() {
400 state = self.wait_for_more(state)?;
401 }
402 let capturing = state.capturing;
403 drop(state);
404 if capturing { Ok(()) } else { self.fill() }
405 }
406
407 pub fn fill_through(&mut self, position: u64) -> Result<bool, CaptureError> {
410 while self.outcome.is_none() && self.held_to() < position {
411 self.take()?;
412 }
413 self.failure()?;
414 Ok(self.held_to() >= position)
415 }
416
417 pub fn fill(&mut self) -> Result<(), CaptureError> {
419 while self.outcome.is_none() {
420 self.take()?;
421 }
422 self.failure()
423 }
424
425 pub fn fork(&self) -> Self {
435 let (queue, from) = self.recording.enrol();
436 Self {
437 recording: Arc::clone(&self.recording),
438 samples: Vec::new(),
439 offset: from,
440 keep_samples: true,
441 queue,
442 owns_outcome: false,
443 outcome: None,
444 }
445 }
446
447 pub fn held_from(&self) -> u64 {
449 self.offset
450 }
451
452 pub fn samples(&self) -> &[f32] {
454 &self.samples
455 }
456
457 pub fn held_to(&self) -> u64 {
459 self.offset + self.samples.len() as u64
460 }
461
462 pub fn span(&self, span: Range<u64>) -> &[f32] {
465 let start = span.start.clamp(self.offset, self.held_to()) - self.offset;
466 let end = span.end.clamp(self.offset, self.held_to()) - self.offset;
467 &self.samples[start as usize..end.max(start) as usize]
468 }
469
470 pub fn release_before(&mut self, position: u64) {
473 let freed = position.clamp(self.offset, self.held_to()) - self.offset;
474 self.samples.drain(..freed as usize);
475 self.offset += freed;
476 }
477
478 pub fn reached_end(&self) -> bool {
482 self.outcome.is_some()
483 }
484
485 pub fn finished_audio(&mut self) -> Result<&RecordedAudio, CaptureError> {
492 assert!(
493 self.owns_outcome,
494 "only the reader that opened a recording has its audio"
495 );
496 self.keep_samples = false;
497 self.offset = self.held_to();
498 self.samples = Vec::new();
499 self.fill()?;
500 match &self.outcome {
501 Some(Ending::Recorded(Some(audio))) => Ok(audio),
502 _failed => Err(CaptureError::AlreadyFailed),
503 }
504 }
505
506 #[cfg(not(target_family = "wasm"))]
509 #[must_use]
510 pub fn wait_for_end(&self, timeout: Duration) -> bool {
511 let state = self.recording.lock();
512 let (state, _timed_out) = self
513 .recording
514 .changed
515 .wait_timeout_while(state, timeout, |state| state.ended.is_none())
516 .unwrap_or_else(|poisoned| poisoned.into_inner());
517 state.ended.is_some()
518 }
519
520 #[cfg(not(target_family = "wasm"))]
523 pub fn ended_at(&self) -> Option<Instant> {
524 self.recording.lock().ended.as_ref().map(|ended| ended.at)
525 }
526
527 #[cfg(not(target_family = "wasm"))]
528 fn wait_for_more<'state>(
529 &self,
530 state: MutexGuard<'state, State>,
531 ) -> Result<MutexGuard<'state, State>, CaptureError> {
532 Ok(self
533 .recording
534 .changed
535 .wait(state)
536 .unwrap_or_else(|poisoned| poisoned.into_inner()))
537 }
538
539 #[cfg(target_family = "wasm")]
542 fn wait_for_more<'state>(
543 &self,
544 _state: MutexGuard<'state, State>,
545 ) -> Result<MutexGuard<'state, State>, CaptureError> {
546 Err(CaptureError::StillCapturing)
547 }
548
549 pub fn duration(&self) -> Duration {
551 canonical_duration(self.recording.lock().appended as u64)
552 }
553
554 fn take(&mut self) -> Result<(), CaptureError> {
556 let mut state = self.recording.lock();
557 while state.queue(self.queue).pending.is_empty() && state.ended.is_none() {
560 state = self.wait_for_more(state)?;
561 }
562 let pending = &mut state.queue(self.queue).pending;
563 if self.keep_samples {
564 self.samples.append(pending);
565 } else {
566 self.offset += pending.len() as u64;
569 pending.clear();
570 }
571
572 let Some(ended) = &mut state.ended else {
573 return Ok(());
574 };
575 if !self.owns_outcome {
576 let failed = ended.failed;
579 drop(state);
580 self.outcome = Some(match failed {
581 true => Ending::Failed,
582 false => Ending::Recorded(None),
583 });
584 return match failed {
585 true => Err(CaptureError::AlreadyFailed),
586 false => Ok(()),
587 };
588 }
589 let outcome = std::mem::replace(&mut ended.outcome, Err(CaptureError::AlreadyFailed));
592 drop(state);
593 match outcome {
594 Ok(audio) => {
595 self.outcome = Some(Ending::Recorded(Some(audio)));
596 Ok(())
597 }
598 Err(error) => {
599 self.outcome = Some(Ending::Failed);
600 Err(error)
601 }
602 }
603 }
604
605 fn failure(&self) -> Result<(), CaptureError> {
606 match &self.outcome {
607 Some(Ending::Failed) => Err(CaptureError::AlreadyFailed),
608 _live_or_recorded => Ok(()),
609 }
610 }
611}
612
613impl Drop for RecordingReader {
616 fn drop(&mut self) {
617 self.recording.retire(self.queue);
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use super::super::capture::MicrophoneError;
624 use super::super::testing::{recorded, spooled};
625
626 use super::*;
627
628 const SAMPLES: [i16; 4] = [0, 16_384, -16_384, 32_767];
629
630 fn streamed(batches: Vec<Vec<i16>>) -> (tempfile::TempDir, RecordingReader) {
633 let (directory, writer, mut spool, recording) = spooled();
634 std::thread::spawn(move || {
635 for batch in batches {
636 spool.write_samples(&batch).expect("samples are writable");
637 }
638 writer.close(spool.finish().map_err(CaptureError::from));
639 });
640 (directory, recording)
641 }
642
643 fn expected() -> Vec<f32> {
644 SAMPLES.iter().copied().map(f32_from_pcm16).collect()
645 }
646
647 #[test]
648 fn a_fork_reads_the_audio_the_first_reader_already_took() {
649 let (mut recording, mut capture) = Recording::open();
650 let mut forked = recording.fork();
651 capture.push(&[0.25, 0.5]);
652 capture.finish();
653
654 recording.fill().expect("the recording ends");
655 forked.fill().expect("the fork ends too");
656
657 assert_eq!(recording.samples(), [0.25, 0.5]);
658 assert_eq!(
659 forked.samples(),
660 [0.25, 0.5],
661 "one reader taking the audio must not empty it for the other"
662 );
663 }
664
665 #[test]
666 fn a_fork_opens_where_capture_stands_rather_than_at_the_beginning() {
667 let (mut recording, mut capture) = Recording::open();
668 capture.push(&[0.1, 0.2]);
669 recording.take().expect("the first batch arrives");
670 let mut forked = recording.fork();
671 capture.push(&[0.3]);
672 capture.finish();
673
674 forked.fill().expect("the fork ends");
675
676 assert_eq!(forked.held_from(), 2, "the fork opens past the spent audio");
677 assert_eq!(forked.samples(), [0.3]);
678 assert_eq!(
679 forked.held_to(),
680 3,
681 "and still counts from the first sample ever"
682 );
683 }
684
685 #[test]
686 fn a_fork_counts_from_where_its_own_audio_starts() {
687 let (recording, mut capture) = Recording::open();
692 let writing = std::thread::spawn(move || {
693 for sample in 0..4_000 {
694 capture.push(&[sample as f32]);
695 }
696 capture.finish();
697 });
698
699 for _ in 0..400 {
700 let mut forked = recording.fork();
701 if forked.take().is_err() {
702 break;
703 }
704 if let Some(&first) = forked.samples().first() {
706 assert_eq!(
707 first as u64,
708 forked.held_from(),
709 "a fork's first sample must sit where the fork says it does"
710 );
711 }
712 }
713 writing.join().expect("capture finishes");
714 }
715
716 #[test]
717 fn releasing_on_one_reader_leaves_the_other_window_alone() {
718 let (mut recording, mut capture) = Recording::open();
719 let mut forked = recording.fork();
720 capture.push(&[0.1, 0.2, 0.3]);
721 capture.finish();
722 recording.fill().expect("the recording ends");
723 forked.fill().expect("the fork ends");
724
725 recording.release_before(2);
726
727 assert_eq!(
728 recording.samples(),
729 [0.3],
730 "the reader freed its own prefix"
731 );
732 assert_eq!(
733 forked.samples(),
734 [0.1, 0.2, 0.3],
735 "a track walking behind must still be able to read it"
736 );
737 }
738
739 #[test]
740 fn the_finished_audio_belongs_to_the_reader_that_opened_the_recording() {
741 let (_directory, mut recording) = recorded(&SAMPLES);
742 let mut forked = recording.fork();
743
744 forked.fill().expect("the fork sees a clean end");
745
746 assert!(
747 forked.reached_end(),
748 "the fork learns there is no more audio"
749 );
750 assert!(
751 recording.finished_audio().is_ok(),
752 "and leaves the WAV for whoever opened the recording"
753 );
754 }
755
756 #[test]
757 fn a_failed_capture_ends_a_fork_with_the_same_verdict() {
758 let (_directory, writer, _spool, recording) = spooled();
759 let mut forked = recording.fork();
760 writer.close(Err(CaptureError::SourceGone));
761
762 let failure = forked.fill().expect_err("a failed capture fails its fork");
763
764 assert!(matches!(failure, CaptureError::AlreadyFailed));
765 }
766
767 #[test]
768 fn a_dropped_fork_stops_costing_capture_anything() {
769 let (reader, mut capture) = Recording::open();
770 let forked = reader.fork();
771 assert_eq!(reader.recording.lock().queues.len(), 2);
772
773 drop(forked);
774 capture.push(&[0.1]);
775
776 let state = reader.recording.lock();
777 assert_eq!(
778 state.queues.len(),
779 1,
780 "capture still copies into a dead queue"
781 );
782 assert_eq!(state.queues[0].pending.len(), 1);
783 }
784
785 #[test]
786 fn two_readers_waiting_are_both_woken_by_one_write() {
787 let (mut recording, mut capture) = Recording::open();
788 let mut forked = recording.fork();
789 let waiting = std::thread::spawn(move || {
790 forked.fill_through(1).expect("the fork is woken");
791 forked.held_to()
792 });
793
794 capture.push(&[0.5]);
795 recording.fill_through(1).expect("the reader is woken");
796
797 assert_eq!(recording.held_to(), 1);
798 assert_eq!(waiting.join().expect("the fork thread finishes"), 1);
799 }
800
801 #[test]
802 fn a_reader_waits_for_samples_capture_has_not_written_yet() {
803 let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
804 let (_directory, mut recording) = streamed(batches);
805
806 recording.fill().expect("the recording ends");
807
808 assert_eq!(recording.samples(), expected());
809 }
810
811 #[test]
812 fn a_reader_can_take_a_prefix_before_the_recording_ends() {
813 let (_directory, mut recording) = recorded(&SAMPLES);
814
815 recording
816 .fill_through(2)
817 .expect("two samples are available");
818 let first = recording.samples()[..2].to_vec();
819 recording.fill().expect("the rest is available");
820
821 assert_eq!(first, expected()[..2]);
822 assert_eq!(recording.samples(), expected());
823 }
824
825 #[test]
826 fn asking_past_the_end_returns_what_was_recorded() {
827 let (_directory, mut recording) = recorded(&SAMPLES);
828
829 recording
830 .fill_through(SAMPLES.len() as u64 * 4)
831 .expect("a short recording is not a failure");
832
833 assert!(recording.reached_end());
834 assert_eq!(recording.samples(), expected());
835 }
836
837 #[test]
840 fn what_the_reader_sees_is_what_the_wav_got() {
841 let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
842 let (_directory, mut recording) = streamed(batches);
843 recording.fill().expect("the recording ends");
844 let read = recording.samples().to_vec();
845
846 let audio = recording.finished_audio().expect("the recording succeeded");
847 let written: Vec<f32> = hound::WavReader::open(audio.path().expect("a captured WAV"))
848 .expect("the WAV is readable")
849 .into_samples::<i16>()
850 .map(|sample| f32_from_pcm16(sample.expect("every sample reads back")))
851 .collect();
852
853 assert_eq!(read, written);
854 }
855
856 #[test]
857 fn a_recording_as_long_as_allowed_still_gets_a_spool() {
858 let samples = 15 * 60 * crate::audio::CANONICAL_SAMPLE_RATE as usize;
859 assert!(super::super::spool::maximum_samples(spool_room(samples)).is_ok());
860 }
861
862 #[test]
863 fn a_recording_longer_than_allowed_is_refused_and_leaves_no_file() {
864 let directory = tempfile::tempdir().expect("tempdir must be creatable");
865 let samples = 15 * 60 * crate::audio::CANONICAL_SAMPLE_RATE as usize + 1;
866
867 let error = RecordingReader::from_pcm16_in(directory.path(), &vec![0; samples])
868 .err()
869 .expect("an oversized recording is refused");
870
871 assert!(matches!(
872 error,
873 CaptureError::Spool(SpoolError::LimitExceeded { .. })
874 ));
875 let leftovers = std::fs::read_dir(directory.path()).expect("the directory lists");
876 assert_eq!(leftovers.count(), 0);
877 }
878
879 #[test]
880 fn a_fill_through_says_whether_it_reached_the_position() {
881 let mut recording = RecordingReader::from_samples(&[0.0; 4]);
882
883 assert!(recording.fill_through(4).expect("the recording succeeded"));
884 assert!(!recording.fill_through(5).expect("the recording succeeded"));
885 }
886
887 #[test]
888 fn waiting_for_the_end_times_out_while_capture_runs() {
889 let (recording, writer) = Recording::open();
890
891 assert!(!recording.wait_for_end(Duration::from_millis(10)));
892 writer.finish();
893 assert!(recording.wait_for_end(Duration::from_millis(10)));
894 }
895
896 #[test]
897 fn a_recording_from_samples_holds_them_and_has_ended() {
898 let mut recording = RecordingReader::from_samples(&[0.25, -0.5]);
899
900 recording.fill().expect("the recording already ended");
901
902 assert_eq!(recording.samples(), [0.25, -0.5]);
903 assert_eq!(
904 recording.finished_audio().expect("it succeeded").path(),
905 None
906 );
907 }
908
909 #[test]
910 fn a_pcm16_recording_keeps_every_sample_exactly() {
911 let directory = tempfile::tempdir().expect("test directory is created");
912 let mut recording = RecordingReader::from_pcm16_in(directory.path(), &SAMPLES)
913 .expect("the recording is written");
914
915 let audio = recording.finished_audio().expect("the recording succeeded");
916 let written: Vec<i16> = hound::WavReader::open(audio.path().expect("a saved WAV"))
917 .expect("the WAV is readable")
918 .into_samples::<i16>()
919 .map(|sample| sample.expect("every sample reads back"))
920 .collect();
921
922 assert_eq!(written, SAMPLES);
923 }
924
925 #[test]
926 fn the_wav_reader_keeps_none_of_the_samples_it_waits_through() {
927 let (_directory, mut recording) = recorded(&SAMPLES);
928
929 recording.finished_audio().expect("the recording succeeded");
930
931 assert!(recording.samples().is_empty());
932 }
933
934 #[test]
937 fn a_failed_recording_stays_failed_for_every_later_read() {
938 let (mut recording, writer) = ends();
939 writer.close(Err(CaptureError::Microphone(
940 MicrophoneError::NoInputDevice,
941 )));
942 recording
943 .fill()
944 .expect_err("the first read reports the cause");
945
946 let again = recording.fill_through(1).expect_err("so does the next one");
947 let finished = recording
948 .finished_audio()
949 .err()
950 .expect("and so does the WAV");
951
952 assert!(matches!(again, CaptureError::AlreadyFailed));
953 assert!(matches!(finished, CaptureError::AlreadyFailed));
954 }
955
956 #[test]
959 fn a_silent_capture_still_counts_as_started() {
960 let (mut recording, writer) = ends();
961 writer.begin();
962
963 recording
964 .wait_for_capture()
965 .expect("a live capture has started");
966
967 assert!(recording.samples().is_empty());
968 }
969
970 #[test]
971 fn a_capture_that_dies_before_starting_reports_its_cause() {
972 let (mut recording, writer) = ends();
973 writer.close(Err(CaptureError::Microphone(
974 MicrophoneError::NoInputDevice,
975 )));
976
977 let error = recording
978 .wait_for_capture()
979 .expect_err("the recording never started");
980
981 assert!(matches!(
982 error,
983 CaptureError::Microphone(MicrophoneError::NoInputDevice)
984 ));
985 }
986
987 #[test]
988 fn dropping_the_capture_end_ends_the_recording() {
989 let (mut recording, writer) = ends();
990 drop(writer);
991
992 let error = recording
993 .fill()
994 .expect_err("a reader must not wait on a producer that is gone");
995
996 assert!(matches!(error, CaptureError::SourceGone));
997 }
998
999 #[test]
1000 fn the_first_outcome_is_the_one_the_reader_learns() {
1001 let (mut recording, writer) = ends();
1002 writer.close(Err(CaptureError::Microphone(
1003 MicrophoneError::NoInputDevice,
1004 )));
1005 drop(writer);
1006
1007 let error = recording.fill().expect_err("the recording failed to start");
1008
1009 assert!(matches!(
1010 error,
1011 CaptureError::Microphone(MicrophoneError::NoInputDevice)
1012 ));
1013 }
1014}