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 written(&self) -> u64 {
552 self.recording.lock().appended as u64
553 }
554
555 pub fn duration(&self) -> Duration {
557 canonical_duration(self.written())
558 }
559
560 fn take(&mut self) -> Result<(), CaptureError> {
562 let mut state = self.recording.lock();
563 while state.queue(self.queue).pending.is_empty() && state.ended.is_none() {
566 state = self.wait_for_more(state)?;
567 }
568 let pending = &mut state.queue(self.queue).pending;
569 if self.keep_samples {
570 self.samples.append(pending);
571 } else {
572 self.offset += pending.len() as u64;
575 pending.clear();
576 }
577
578 let Some(ended) = &mut state.ended else {
579 return Ok(());
580 };
581 if !self.owns_outcome {
582 let failed = ended.failed;
585 drop(state);
586 self.outcome = Some(match failed {
587 true => Ending::Failed,
588 false => Ending::Recorded(None),
589 });
590 return match failed {
591 true => Err(CaptureError::AlreadyFailed),
592 false => Ok(()),
593 };
594 }
595 let outcome = std::mem::replace(&mut ended.outcome, Err(CaptureError::AlreadyFailed));
598 drop(state);
599 match outcome {
600 Ok(audio) => {
601 self.outcome = Some(Ending::Recorded(Some(audio)));
602 Ok(())
603 }
604 Err(error) => {
605 self.outcome = Some(Ending::Failed);
606 Err(error)
607 }
608 }
609 }
610
611 fn failure(&self) -> Result<(), CaptureError> {
612 match &self.outcome {
613 Some(Ending::Failed) => Err(CaptureError::AlreadyFailed),
614 _live_or_recorded => Ok(()),
615 }
616 }
617}
618
619impl Drop for RecordingReader {
622 fn drop(&mut self) {
623 self.recording.retire(self.queue);
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use super::super::capture::MicrophoneError;
630 use super::super::testing::{recorded, spooled};
631
632 use super::*;
633
634 const SAMPLES: [i16; 4] = [0, 16_384, -16_384, 32_767];
635
636 fn streamed(batches: Vec<Vec<i16>>) -> (tempfile::TempDir, RecordingReader) {
639 let (directory, writer, mut spool, recording) = spooled();
640 std::thread::spawn(move || {
641 for batch in batches {
642 spool.write_samples(&batch).expect("samples are writable");
643 }
644 writer.close(spool.finish().map_err(CaptureError::from));
645 });
646 (directory, recording)
647 }
648
649 fn expected() -> Vec<f32> {
650 SAMPLES.iter().copied().map(f32_from_pcm16).collect()
651 }
652
653 #[test]
654 fn a_fork_reads_the_audio_the_first_reader_already_took() {
655 let (mut recording, mut capture) = Recording::open();
656 let mut forked = recording.fork();
657 capture.push(&[0.25, 0.5]);
658 capture.finish();
659
660 recording.fill().expect("the recording ends");
661 forked.fill().expect("the fork ends too");
662
663 assert_eq!(recording.samples(), [0.25, 0.5]);
664 assert_eq!(
665 forked.samples(),
666 [0.25, 0.5],
667 "one reader taking the audio must not empty it for the other"
668 );
669 }
670
671 #[test]
672 fn a_fork_opens_where_capture_stands_rather_than_at_the_beginning() {
673 let (mut recording, mut capture) = Recording::open();
674 capture.push(&[0.1, 0.2]);
675 recording.take().expect("the first batch arrives");
676 let mut forked = recording.fork();
677 capture.push(&[0.3]);
678 capture.finish();
679
680 forked.fill().expect("the fork ends");
681
682 assert_eq!(forked.held_from(), 2, "the fork opens past the spent audio");
683 assert_eq!(forked.samples(), [0.3]);
684 assert_eq!(
685 forked.held_to(),
686 3,
687 "and still counts from the first sample ever"
688 );
689 }
690
691 #[test]
692 fn a_fork_counts_from_where_its_own_audio_starts() {
693 let (recording, mut capture) = Recording::open();
698 let writing = std::thread::spawn(move || {
699 for sample in 0..4_000 {
700 capture.push(&[sample as f32]);
701 }
702 capture.finish();
703 });
704
705 for _ in 0..400 {
706 let mut forked = recording.fork();
707 if forked.take().is_err() {
708 break;
709 }
710 if let Some(&first) = forked.samples().first() {
712 assert_eq!(
713 first as u64,
714 forked.held_from(),
715 "a fork's first sample must sit where the fork says it does"
716 );
717 }
718 }
719 writing.join().expect("capture finishes");
720 }
721
722 #[test]
723 fn releasing_on_one_reader_leaves_the_other_window_alone() {
724 let (mut recording, mut capture) = Recording::open();
725 let mut forked = recording.fork();
726 capture.push(&[0.1, 0.2, 0.3]);
727 capture.finish();
728 recording.fill().expect("the recording ends");
729 forked.fill().expect("the fork ends");
730
731 recording.release_before(2);
732
733 assert_eq!(
734 recording.samples(),
735 [0.3],
736 "the reader freed its own prefix"
737 );
738 assert_eq!(
739 forked.samples(),
740 [0.1, 0.2, 0.3],
741 "a track walking behind must still be able to read it"
742 );
743 }
744
745 #[test]
746 fn the_finished_audio_belongs_to_the_reader_that_opened_the_recording() {
747 let (_directory, mut recording) = recorded(&SAMPLES);
748 let mut forked = recording.fork();
749
750 forked.fill().expect("the fork sees a clean end");
751
752 assert!(
753 forked.reached_end(),
754 "the fork learns there is no more audio"
755 );
756 assert!(
757 recording.finished_audio().is_ok(),
758 "and leaves the WAV for whoever opened the recording"
759 );
760 }
761
762 #[test]
763 fn a_failed_capture_ends_a_fork_with_the_same_verdict() {
764 let (_directory, writer, _spool, recording) = spooled();
765 let mut forked = recording.fork();
766 writer.close(Err(CaptureError::SourceGone));
767
768 let failure = forked.fill().expect_err("a failed capture fails its fork");
769
770 assert!(matches!(failure, CaptureError::AlreadyFailed));
771 }
772
773 #[test]
774 fn a_dropped_fork_stops_costing_capture_anything() {
775 let (reader, mut capture) = Recording::open();
776 let forked = reader.fork();
777 assert_eq!(reader.recording.lock().queues.len(), 2);
778
779 drop(forked);
780 capture.push(&[0.1]);
781
782 let state = reader.recording.lock();
783 assert_eq!(
784 state.queues.len(),
785 1,
786 "capture still copies into a dead queue"
787 );
788 assert_eq!(state.queues[0].pending.len(), 1);
789 }
790
791 #[test]
792 fn two_readers_waiting_are_both_woken_by_one_write() {
793 let (mut recording, mut capture) = Recording::open();
794 let mut forked = recording.fork();
795 let waiting = std::thread::spawn(move || {
796 forked.fill_through(1).expect("the fork is woken");
797 forked.held_to()
798 });
799
800 capture.push(&[0.5]);
801 recording.fill_through(1).expect("the reader is woken");
802
803 assert_eq!(recording.held_to(), 1);
804 assert_eq!(waiting.join().expect("the fork thread finishes"), 1);
805 }
806
807 #[test]
808 fn a_reader_waits_for_samples_capture_has_not_written_yet() {
809 let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
810 let (_directory, mut recording) = streamed(batches);
811
812 recording.fill().expect("the recording ends");
813
814 assert_eq!(recording.samples(), expected());
815 }
816
817 #[test]
818 fn a_reader_can_take_a_prefix_before_the_recording_ends() {
819 let (_directory, mut recording) = recorded(&SAMPLES);
820
821 recording
822 .fill_through(2)
823 .expect("two samples are available");
824 let first = recording.samples()[..2].to_vec();
825 recording.fill().expect("the rest is available");
826
827 assert_eq!(first, expected()[..2]);
828 assert_eq!(recording.samples(), expected());
829 }
830
831 #[test]
832 fn asking_past_the_end_returns_what_was_recorded() {
833 let (_directory, mut recording) = recorded(&SAMPLES);
834
835 recording
836 .fill_through(SAMPLES.len() as u64 * 4)
837 .expect("a short recording is not a failure");
838
839 assert!(recording.reached_end());
840 assert_eq!(recording.samples(), expected());
841 }
842
843 #[test]
846 fn what_the_reader_sees_is_what_the_wav_got() {
847 let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
848 let (_directory, mut recording) = streamed(batches);
849 recording.fill().expect("the recording ends");
850 let read = recording.samples().to_vec();
851
852 let audio = recording.finished_audio().expect("the recording succeeded");
853 let written: Vec<f32> = hound::WavReader::open(audio.path().expect("a captured WAV"))
854 .expect("the WAV is readable")
855 .into_samples::<i16>()
856 .map(|sample| f32_from_pcm16(sample.expect("every sample reads back")))
857 .collect();
858
859 assert_eq!(read, written);
860 }
861
862 #[test]
863 fn a_recording_as_long_as_allowed_still_gets_a_spool() {
864 let samples = 15 * 60 * crate::audio::CANONICAL_SAMPLE_RATE as usize;
865 assert!(super::super::spool::maximum_samples(spool_room(samples)).is_ok());
866 }
867
868 #[test]
869 fn a_recording_longer_than_allowed_is_refused_and_leaves_no_file() {
870 let directory = tempfile::tempdir().expect("tempdir must be creatable");
871 let samples = 15 * 60 * crate::audio::CANONICAL_SAMPLE_RATE as usize + 1;
872
873 let error = RecordingReader::from_pcm16_in(directory.path(), &vec![0; samples])
874 .err()
875 .expect("an oversized recording is refused");
876
877 assert!(matches!(
878 error,
879 CaptureError::Spool(SpoolError::LimitExceeded { .. })
880 ));
881 let leftovers = std::fs::read_dir(directory.path()).expect("the directory lists");
882 assert_eq!(leftovers.count(), 0);
883 }
884
885 #[test]
886 fn a_fill_through_says_whether_it_reached_the_position() {
887 let mut recording = RecordingReader::from_samples(&[0.0; 4]);
888
889 assert!(recording.fill_through(4).expect("the recording succeeded"));
890 assert!(!recording.fill_through(5).expect("the recording succeeded"));
891 }
892
893 #[test]
894 fn waiting_for_the_end_times_out_while_capture_runs() {
895 let (recording, writer) = Recording::open();
896
897 assert!(!recording.wait_for_end(Duration::from_millis(10)));
898 writer.finish();
899 assert!(recording.wait_for_end(Duration::from_millis(10)));
900 }
901
902 #[test]
903 fn a_recording_from_samples_holds_them_and_has_ended() {
904 let mut recording = RecordingReader::from_samples(&[0.25, -0.5]);
905
906 recording.fill().expect("the recording already ended");
907
908 assert_eq!(recording.samples(), [0.25, -0.5]);
909 assert_eq!(
910 recording.finished_audio().expect("it succeeded").path(),
911 None
912 );
913 }
914
915 #[test]
916 fn a_pcm16_recording_keeps_every_sample_exactly() {
917 let directory = tempfile::tempdir().expect("test directory is created");
918 let mut recording = RecordingReader::from_pcm16_in(directory.path(), &SAMPLES)
919 .expect("the recording is written");
920
921 let audio = recording.finished_audio().expect("the recording succeeded");
922 let written: Vec<i16> = hound::WavReader::open(audio.path().expect("a saved WAV"))
923 .expect("the WAV is readable")
924 .into_samples::<i16>()
925 .map(|sample| sample.expect("every sample reads back"))
926 .collect();
927
928 assert_eq!(written, SAMPLES);
929 }
930
931 #[test]
932 fn the_wav_reader_keeps_none_of_the_samples_it_waits_through() {
933 let (_directory, mut recording) = recorded(&SAMPLES);
934
935 recording.finished_audio().expect("the recording succeeded");
936
937 assert!(recording.samples().is_empty());
938 }
939
940 #[test]
943 fn a_failed_recording_stays_failed_for_every_later_read() {
944 let (mut recording, writer) = ends();
945 writer.close(Err(CaptureError::Microphone(
946 MicrophoneError::NoInputDevice,
947 )));
948 recording
949 .fill()
950 .expect_err("the first read reports the cause");
951
952 let again = recording.fill_through(1).expect_err("so does the next one");
953 let finished = recording
954 .finished_audio()
955 .err()
956 .expect("and so does the WAV");
957
958 assert!(matches!(again, CaptureError::AlreadyFailed));
959 assert!(matches!(finished, CaptureError::AlreadyFailed));
960 }
961
962 #[test]
965 fn a_silent_capture_still_counts_as_started() {
966 let (mut recording, writer) = ends();
967 writer.begin();
968
969 recording
970 .wait_for_capture()
971 .expect("a live capture has started");
972
973 assert!(recording.samples().is_empty());
974 }
975
976 #[test]
977 fn a_capture_that_dies_before_starting_reports_its_cause() {
978 let (mut recording, writer) = ends();
979 writer.close(Err(CaptureError::Microphone(
980 MicrophoneError::NoInputDevice,
981 )));
982
983 let error = recording
984 .wait_for_capture()
985 .expect_err("the recording never started");
986
987 assert!(matches!(
988 error,
989 CaptureError::Microphone(MicrophoneError::NoInputDevice)
990 ));
991 }
992
993 #[test]
994 fn dropping_the_capture_end_ends_the_recording() {
995 let (mut recording, writer) = ends();
996 drop(writer);
997
998 let error = recording
999 .fill()
1000 .expect_err("a reader must not wait on a producer that is gone");
1001
1002 assert!(matches!(error, CaptureError::SourceGone));
1003 }
1004
1005 #[test]
1006 fn the_first_outcome_is_the_one_the_reader_learns() {
1007 let (mut recording, writer) = ends();
1008 writer.close(Err(CaptureError::Microphone(
1009 MicrophoneError::NoInputDevice,
1010 )));
1011 drop(writer);
1012
1013 let error = recording.fill().expect_err("the recording failed to start");
1014
1015 assert!(matches!(
1016 error,
1017 CaptureError::Microphone(MicrophoneError::NoInputDevice)
1018 ));
1019 }
1020}