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 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 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 failed: bool,
188 outcome: Result<RecordedAudio, CaptureError>,
189 #[cfg(not(target_family = "wasm"))]
190 at: Instant,
191}
192
193#[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 pub fn open() -> (RecordingReader, RecordingWriter) {
202 let (reader, writer) = ends();
203 writer.begin();
204 (reader, writer)
205 }
206}
207
208pub struct RecordingWriter {
213 recording: Arc<Recording>,
214}
215
216impl RecordingWriter {
217 pub fn push(&mut self, samples: &[f32]) {
218 self.recording.extend(samples.iter().copied());
219 }
220
221 pub fn finish(self) {
223 let appended = self.recording.lock().appended as u64;
224 self.close(Ok(RecordedAudio::from_sample_count(appended)));
225 }
226
227 #[cfg(not(target_family = "wasm"))]
228 pub(super) fn recording(&self) -> Arc<Recording> {
229 Arc::clone(&self.recording)
230 }
231
232 #[cfg(not(target_family = "wasm"))]
233 pub(super) fn push_pcm16(&self, samples: &[i16]) {
234 self.recording.append(samples);
235 }
236
237 pub(super) fn begin(&self) {
241 self.recording.begin();
242 }
243
244 pub(super) fn close(&self, outcome: Result<RecordedAudio, CaptureError>) {
245 self.recording.close(outcome);
246 }
247}
248
249impl Drop for RecordingWriter {
250 fn drop(&mut self) {
251 self.recording.close(Err(CaptureError::SourceGone));
252 }
253}
254
255impl Recording {
256 fn new() -> Self {
257 Self {
258 state: Mutex::default(),
259 changed: Condvar::new(),
260 }
261 }
262
263 fn begin(&self) {
264 let mut state = self.lock();
265 state.capturing = true;
266 drop(state);
267 self.changed.notify_all();
268 }
269
270 #[cfg(not(target_family = "wasm"))]
272 pub(super) fn append(&self, samples: &[i16]) {
273 self.extend(samples.iter().copied().map(f32_from_pcm16));
274 }
275
276 fn extend(&self, samples: impl ExactSizeIterator<Item = f32>) {
277 let mut state = self.lock();
278 if state.ended.is_some() {
279 return;
280 }
281 let count = samples.len();
282 state.appended += count;
283 if let Some((first, rest)) = state.queues.split_first_mut() {
286 first.pending.extend(samples);
287 let written = first.pending.len().saturating_sub(count);
288 for queue in rest {
289 queue.pending.extend_from_slice(&first.pending[written..]);
290 }
291 }
292 drop(state);
293 self.changed.notify_all();
294 }
295
296 fn enrol(&self) -> (u64, u64) {
303 let mut state = self.lock();
304 let id = state.enrol();
305 (id, state.appended as u64)
306 }
307
308 fn retire(&self, queue: u64) {
310 self.lock().queues.retain(|held| held.id != queue);
311 }
312
313 fn close(&self, outcome: Result<RecordedAudio, CaptureError>) {
316 let mut state = self.lock();
317 if state.ended.is_none() {
318 state.ended = Some(Ended {
319 failed: outcome.is_err(),
320 outcome,
321 #[cfg(not(target_family = "wasm"))]
322 at: Instant::now(),
323 });
324 }
325 drop(state);
326 self.changed.notify_all();
327 }
328
329 fn lock(&self) -> MutexGuard<'_, State> {
330 self.state
331 .lock()
332 .unwrap_or_else(|poisoned| poisoned.into_inner())
333 }
334}
335
336pub struct RecordingReader {
344 recording: Arc<Recording>,
345 samples: Vec<f32>,
346 offset: u64,
348 keep_samples: bool,
351 queue: u64,
353 owns_outcome: bool,
356 outcome: Option<Ending>,
357}
358
359enum Ending {
362 Recorded(Option<RecordedAudio>),
363 Failed,
364}
365
366impl RecordingReader {
367 pub fn from_samples(samples: &[f32]) -> Self {
370 let (reader, mut writer) = Recording::open();
371 writer.push(samples);
372 writer.finish();
373 reader
374 }
375
376 #[cfg(not(target_family = "wasm"))]
379 pub fn from_samples_in(
380 spool_dir: &std::path::Path,
381 samples: &[f32],
382 ) -> Result<Self, CaptureError> {
383 let pcm16: Vec<i16> = samples.iter().copied().map(pcm16_from_f32).collect();
384 Self::from_pcm16_in(spool_dir, &pcm16)
385 }
386
387 #[cfg(not(target_family = "wasm"))]
390 pub fn from_pcm16_in(
391 spool_dir: &std::path::Path,
392 samples: &[i16],
393 ) -> Result<Self, CaptureError> {
394 let (recording, writer) = ends();
395 let mut spool = WavSpool::create(spool_dir, spool_room(samples.len()), &writer)?;
396 spool.write_samples(samples)?;
397 writer.close(spool.finish().map_err(CaptureError::from));
398 Ok(recording)
399 }
400
401 #[cfg(not(target_family = "wasm"))]
404 pub(crate) fn wait_for_capture(&mut self) -> Result<(), CaptureError> {
405 let mut state = self.recording.lock();
406 while !state.capturing && state.ended.is_none() {
407 state = self.wait_for_more(state)?;
408 }
409 let capturing = state.capturing;
410 drop(state);
411 if capturing { Ok(()) } else { self.fill() }
412 }
413
414 pub fn fill_through(&mut self, position: u64) -> Result<bool, CaptureError> {
417 while self.outcome.is_none() && self.held_to() < position {
418 self.take()?;
419 }
420 self.failure()?;
421 Ok(self.held_to() >= position)
422 }
423
424 pub fn fill(&mut self) -> Result<(), CaptureError> {
426 while self.outcome.is_none() {
427 self.take()?;
428 }
429 self.failure()
430 }
431
432 pub fn fork(&self) -> Self {
442 let (queue, from) = self.recording.enrol();
443 Self {
444 recording: Arc::clone(&self.recording),
445 samples: Vec::new(),
446 offset: from,
447 keep_samples: true,
448 queue,
449 owns_outcome: false,
450 outcome: None,
451 }
452 }
453
454 pub fn fork_window(&mut self) -> Self {
457 let mut state = self.recording.lock();
459 let pending = std::mem::take(&mut state.queue(self.queue).pending);
460 let queue = state.enrol();
461 drop(state);
462 if self.keep_samples {
463 self.samples.extend(pending);
464 } else {
465 self.offset += pending.len() as u64;
466 }
467 Self {
468 recording: Arc::clone(&self.recording),
469 samples: self.samples.clone(),
470 offset: self.offset,
471 keep_samples: true,
472 queue,
473 owns_outcome: false,
474 outcome: None,
475 }
476 }
477
478 pub fn held_from(&self) -> u64 {
480 self.offset
481 }
482
483 pub fn samples(&self) -> &[f32] {
485 &self.samples
486 }
487
488 pub fn held_to(&self) -> u64 {
490 self.offset + self.samples.len() as u64
491 }
492
493 pub fn span(&self, span: Range<u64>) -> &[f32] {
496 let start = span.start.clamp(self.offset, self.held_to()) - self.offset;
497 let end = span.end.clamp(self.offset, self.held_to()) - self.offset;
498 &self.samples[start as usize..end.max(start) as usize]
499 }
500
501 pub fn release_before(&mut self, position: u64) {
504 let freed = position.clamp(self.offset, self.held_to()) - self.offset;
505 self.samples.drain(..freed as usize);
506 self.offset += freed;
507 }
508
509 pub fn reached_end(&self) -> bool {
513 self.outcome.is_some()
514 }
515
516 pub fn finished_audio(&mut self) -> Result<&RecordedAudio, CaptureError> {
523 assert!(
524 self.owns_outcome,
525 "only the reader that opened a recording has its audio"
526 );
527 self.keep_samples = false;
528 self.offset = self.held_to();
529 self.samples = Vec::new();
530 self.fill()?;
531 match &self.outcome {
532 Some(Ending::Recorded(Some(audio))) => Ok(audio),
533 _failed => Err(CaptureError::AlreadyFailed),
534 }
535 }
536
537 #[cfg(not(target_family = "wasm"))]
540 #[must_use]
541 pub fn wait_for_end(&self, timeout: Duration) -> bool {
542 let state = self.recording.lock();
543 let (state, _timed_out) = self
544 .recording
545 .changed
546 .wait_timeout_while(state, timeout, |state| state.ended.is_none())
547 .unwrap_or_else(|poisoned| poisoned.into_inner());
548 state.ended.is_some()
549 }
550
551 #[cfg(not(target_family = "wasm"))]
554 pub fn ended_at(&self) -> Option<Instant> {
555 self.recording.lock().ended.as_ref().map(|ended| ended.at)
556 }
557
558 #[cfg(not(target_family = "wasm"))]
559 fn wait_for_more<'state>(
560 &self,
561 state: MutexGuard<'state, State>,
562 ) -> Result<MutexGuard<'state, State>, CaptureError> {
563 Ok(self
564 .recording
565 .changed
566 .wait(state)
567 .unwrap_or_else(|poisoned| poisoned.into_inner()))
568 }
569
570 #[cfg(target_family = "wasm")]
573 fn wait_for_more<'state>(
574 &self,
575 _state: MutexGuard<'state, State>,
576 ) -> Result<MutexGuard<'state, State>, CaptureError> {
577 Err(CaptureError::StillCapturing)
578 }
579
580 pub fn written(&self) -> u64 {
583 self.recording.lock().appended as u64
584 }
585
586 pub fn duration(&self) -> Duration {
588 canonical_duration(self.written())
589 }
590
591 fn take(&mut self) -> Result<(), CaptureError> {
593 let mut state = self.recording.lock();
594 while state.queue(self.queue).pending.is_empty() && state.ended.is_none() {
597 state = self.wait_for_more(state)?;
598 }
599 let pending = &mut state.queue(self.queue).pending;
600 if self.keep_samples {
601 self.samples.append(pending);
602 } else {
603 self.offset += pending.len() as u64;
606 pending.clear();
607 }
608
609 let Some(ended) = &mut state.ended else {
610 return Ok(());
611 };
612 if !self.owns_outcome {
613 let failed = ended.failed;
616 drop(state);
617 self.outcome = Some(match failed {
618 true => Ending::Failed,
619 false => Ending::Recorded(None),
620 });
621 return match failed {
622 true => Err(CaptureError::AlreadyFailed),
623 false => Ok(()),
624 };
625 }
626 let outcome = std::mem::replace(&mut ended.outcome, Err(CaptureError::AlreadyFailed));
629 drop(state);
630 match outcome {
631 Ok(audio) => {
632 self.outcome = Some(Ending::Recorded(Some(audio)));
633 Ok(())
634 }
635 Err(error) => {
636 self.outcome = Some(Ending::Failed);
637 Err(error)
638 }
639 }
640 }
641
642 fn failure(&self) -> Result<(), CaptureError> {
643 match &self.outcome {
644 Some(Ending::Failed) => Err(CaptureError::AlreadyFailed),
645 _live_or_recorded => Ok(()),
646 }
647 }
648}
649
650impl Drop for RecordingReader {
653 fn drop(&mut self) {
654 self.recording.retire(self.queue);
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::super::capture::MicrophoneError;
661 use super::super::testing::{recorded, spooled};
662
663 use super::*;
664
665 const SAMPLES: [i16; 4] = [0, 16_384, -16_384, 32_767];
666
667 fn streamed(batches: Vec<Vec<i16>>) -> (tempfile::TempDir, RecordingReader) {
670 let (directory, writer, mut spool, recording) = spooled();
671 std::thread::spawn(move || {
672 for batch in batches {
673 spool.write_samples(&batch).expect("samples are writable");
674 }
675 writer.close(spool.finish().map_err(CaptureError::from));
676 });
677 (directory, recording)
678 }
679
680 fn expected() -> Vec<f32> {
681 SAMPLES.iter().copied().map(f32_from_pcm16).collect()
682 }
683
684 #[test]
685 fn a_fork_reads_the_audio_the_first_reader_already_took() {
686 let (mut recording, mut capture) = Recording::open();
687 let mut forked = recording.fork();
688 capture.push(&[0.25, 0.5]);
689 capture.finish();
690
691 recording.fill().expect("the recording ends");
692 forked.fill().expect("the fork ends too");
693
694 assert_eq!(recording.samples(), [0.25, 0.5]);
695 assert_eq!(
696 forked.samples(),
697 [0.25, 0.5],
698 "one reader taking the audio must not empty it for the other"
699 );
700 }
701
702 #[test]
703 fn a_fork_opens_where_capture_stands_rather_than_at_the_beginning() {
704 let (mut recording, mut capture) = Recording::open();
705 capture.push(&[0.1, 0.2]);
706 recording.take().expect("the first batch arrives");
707 let mut forked = recording.fork();
708 capture.push(&[0.3]);
709 capture.finish();
710
711 forked.fill().expect("the fork ends");
712
713 assert_eq!(forked.held_from(), 2, "the fork opens past the spent audio");
714 assert_eq!(forked.samples(), [0.3]);
715 assert_eq!(
716 forked.held_to(),
717 3,
718 "and still counts from the first sample ever"
719 );
720 }
721
722 #[test]
723 fn a_window_fork_keeps_audio_written_before_it() {
724 let (mut recording, mut capture) = Recording::open();
725 capture.push(&[0.1, 0.2]);
726 let mut forked = recording.fork_window();
727 capture.push(&[0.3]);
728 capture.finish();
729
730 forked.fill().expect("the fork ends");
731 recording.fill().expect("the recording ends");
732
733 assert_eq!(forked.held_from(), 0);
734 assert_eq!(forked.samples(), [0.1, 0.2, 0.3]);
735 assert_eq!(recording.samples(), [0.1, 0.2, 0.3]);
736 }
737
738 #[test]
739 fn a_window_fork_opens_where_the_reader_still_holds() {
740 let (mut recording, mut capture) = Recording::open();
741 capture.push(&[0.1, 0.2]);
742 recording.take().expect("the first batch arrives");
743 recording.release_before(1);
744 let mut forked = recording.fork_window();
745 capture.push(&[0.3]);
746 capture.finish();
747
748 forked.fill().expect("the fork ends");
749
750 assert_eq!(forked.held_from(), 1);
751 assert_eq!(forked.samples(), [0.2, 0.3]);
752 }
753
754 #[test]
755 fn a_fork_counts_from_where_its_own_audio_starts() {
756 let (recording, mut capture) = Recording::open();
761 let writing = std::thread::spawn(move || {
762 for sample in 0..4_000 {
763 capture.push(&[sample as f32]);
764 }
765 capture.finish();
766 });
767
768 for _ in 0..400 {
769 let mut forked = recording.fork();
770 if forked.take().is_err() {
771 break;
772 }
773 if let Some(&first) = forked.samples().first() {
775 assert_eq!(
776 first as u64,
777 forked.held_from(),
778 "a fork's first sample must sit where the fork says it does"
779 );
780 }
781 }
782 writing.join().expect("capture finishes");
783 }
784
785 #[test]
786 fn releasing_on_one_reader_leaves_the_other_window_alone() {
787 let (mut recording, mut capture) = Recording::open();
788 let mut forked = recording.fork();
789 capture.push(&[0.1, 0.2, 0.3]);
790 capture.finish();
791 recording.fill().expect("the recording ends");
792 forked.fill().expect("the fork ends");
793
794 recording.release_before(2);
795
796 assert_eq!(
797 recording.samples(),
798 [0.3],
799 "the reader freed its own prefix"
800 );
801 assert_eq!(
802 forked.samples(),
803 [0.1, 0.2, 0.3],
804 "a track walking behind must still be able to read it"
805 );
806 }
807
808 #[test]
809 fn the_finished_audio_belongs_to_the_reader_that_opened_the_recording() {
810 let (_directory, mut recording) = recorded(&SAMPLES);
811 let mut forked = recording.fork();
812
813 forked.fill().expect("the fork sees a clean end");
814
815 assert!(
816 forked.reached_end(),
817 "the fork learns there is no more audio"
818 );
819 assert!(
820 recording.finished_audio().is_ok(),
821 "and leaves the WAV for whoever opened the recording"
822 );
823 }
824
825 #[test]
826 fn a_failed_capture_ends_a_fork_with_the_same_verdict() {
827 let (_directory, writer, _spool, recording) = spooled();
828 let mut forked = recording.fork();
829 writer.close(Err(CaptureError::SourceGone));
830
831 let failure = forked.fill().expect_err("a failed capture fails its fork");
832
833 assert!(matches!(failure, CaptureError::AlreadyFailed));
834 }
835
836 #[test]
837 fn a_dropped_fork_stops_costing_capture_anything() {
838 let (reader, mut capture) = Recording::open();
839 let forked = reader.fork();
840 assert_eq!(reader.recording.lock().queues.len(), 2);
841
842 drop(forked);
843 capture.push(&[0.1]);
844
845 let state = reader.recording.lock();
846 assert_eq!(
847 state.queues.len(),
848 1,
849 "capture still copies into a dead queue"
850 );
851 assert_eq!(state.queues[0].pending.len(), 1);
852 }
853
854 #[test]
855 fn two_readers_waiting_are_both_woken_by_one_write() {
856 let (mut recording, mut capture) = Recording::open();
857 let mut forked = recording.fork();
858 let waiting = std::thread::spawn(move || {
859 forked.fill_through(1).expect("the fork is woken");
860 forked.held_to()
861 });
862
863 capture.push(&[0.5]);
864 recording.fill_through(1).expect("the reader is woken");
865
866 assert_eq!(recording.held_to(), 1);
867 assert_eq!(waiting.join().expect("the fork thread finishes"), 1);
868 }
869
870 #[test]
871 fn a_reader_waits_for_samples_capture_has_not_written_yet() {
872 let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
873 let (_directory, mut recording) = streamed(batches);
874
875 recording.fill().expect("the recording ends");
876
877 assert_eq!(recording.samples(), expected());
878 }
879
880 #[test]
881 fn a_reader_can_take_a_prefix_before_the_recording_ends() {
882 let (_directory, mut recording) = recorded(&SAMPLES);
883
884 recording
885 .fill_through(2)
886 .expect("two samples are available");
887 let first = recording.samples()[..2].to_vec();
888 recording.fill().expect("the rest is available");
889
890 assert_eq!(first, expected()[..2]);
891 assert_eq!(recording.samples(), expected());
892 }
893
894 #[test]
895 fn asking_past_the_end_returns_what_was_recorded() {
896 let (_directory, mut recording) = recorded(&SAMPLES);
897
898 recording
899 .fill_through(SAMPLES.len() as u64 * 4)
900 .expect("a short recording is not a failure");
901
902 assert!(recording.reached_end());
903 assert_eq!(recording.samples(), expected());
904 }
905
906 #[test]
909 fn what_the_reader_sees_is_what_the_wav_got() {
910 let batches = SAMPLES.iter().map(|&sample| vec![sample]).collect();
911 let (_directory, mut recording) = streamed(batches);
912 recording.fill().expect("the recording ends");
913 let read = recording.samples().to_vec();
914
915 let audio = recording.finished_audio().expect("the recording succeeded");
916 let written: Vec<f32> = hound::WavReader::open(audio.path().expect("a captured WAV"))
917 .expect("the WAV is readable")
918 .into_samples::<i16>()
919 .map(|sample| f32_from_pcm16(sample.expect("every sample reads back")))
920 .collect();
921
922 assert_eq!(read, written);
923 }
924
925 #[test]
926 fn a_recording_as_long_as_allowed_still_gets_a_spool() {
927 let samples = crate::audio::MAX_RECORDING_SAMPLES as usize;
928 assert!(super::super::spool::maximum_samples(spool_room(samples)).is_ok());
929 }
930
931 #[test]
932 fn a_fill_through_says_whether_it_reached_the_position() {
933 let mut recording = RecordingReader::from_samples(&[0.0; 4]);
934
935 assert!(recording.fill_through(4).expect("the recording succeeded"));
936 assert!(!recording.fill_through(5).expect("the recording succeeded"));
937 }
938
939 #[test]
940 fn waiting_for_the_end_times_out_while_capture_runs() {
941 let (recording, writer) = Recording::open();
942
943 assert!(!recording.wait_for_end(Duration::from_millis(10)));
944 writer.finish();
945 assert!(recording.wait_for_end(Duration::from_millis(10)));
946 }
947
948 #[test]
949 fn a_recording_from_samples_holds_them_and_has_ended() {
950 let mut recording = RecordingReader::from_samples(&[0.25, -0.5]);
951
952 recording.fill().expect("the recording already ended");
953
954 assert_eq!(recording.samples(), [0.25, -0.5]);
955 assert_eq!(
956 recording.finished_audio().expect("it succeeded").path(),
957 None
958 );
959 }
960
961 #[test]
962 fn a_pcm16_recording_keeps_every_sample_exactly() {
963 let directory = tempfile::tempdir().expect("test directory is created");
964 let mut recording = RecordingReader::from_pcm16_in(directory.path(), &SAMPLES)
965 .expect("the recording is written");
966
967 let audio = recording.finished_audio().expect("the recording succeeded");
968 let written: Vec<i16> = hound::WavReader::open(audio.path().expect("a saved WAV"))
969 .expect("the WAV is readable")
970 .into_samples::<i16>()
971 .map(|sample| sample.expect("every sample reads back"))
972 .collect();
973
974 assert_eq!(written, SAMPLES);
975 }
976
977 #[test]
978 fn the_wav_reader_keeps_none_of_the_samples_it_waits_through() {
979 let (_directory, mut recording) = recorded(&SAMPLES);
980
981 recording.finished_audio().expect("the recording succeeded");
982
983 assert!(recording.samples().is_empty());
984 }
985
986 #[test]
989 fn a_failed_recording_stays_failed_for_every_later_read() {
990 let (mut recording, writer) = ends();
991 writer.close(Err(CaptureError::Microphone(
992 MicrophoneError::NoInputDevice,
993 )));
994 recording
995 .fill()
996 .expect_err("the first read reports the cause");
997
998 let again = recording.fill_through(1).expect_err("so does the next one");
999 let finished = recording
1000 .finished_audio()
1001 .err()
1002 .expect("and so does the WAV");
1003
1004 assert!(matches!(again, CaptureError::AlreadyFailed));
1005 assert!(matches!(finished, CaptureError::AlreadyFailed));
1006 }
1007
1008 #[test]
1011 fn a_silent_capture_still_counts_as_started() {
1012 let (mut recording, writer) = ends();
1013 writer.begin();
1014
1015 recording
1016 .wait_for_capture()
1017 .expect("a live capture has started");
1018
1019 assert!(recording.samples().is_empty());
1020 }
1021
1022 #[test]
1023 fn a_capture_that_dies_before_starting_reports_its_cause() {
1024 let (mut recording, writer) = ends();
1025 writer.close(Err(CaptureError::Microphone(
1026 MicrophoneError::NoInputDevice,
1027 )));
1028
1029 let error = recording
1030 .wait_for_capture()
1031 .expect_err("the recording never started");
1032
1033 assert!(matches!(
1034 error,
1035 CaptureError::Microphone(MicrophoneError::NoInputDevice)
1036 ));
1037 }
1038
1039 #[test]
1040 fn dropping_the_capture_end_ends_the_recording() {
1041 let (mut recording, writer) = ends();
1042 drop(writer);
1043
1044 let error = recording
1045 .fill()
1046 .expect_err("a reader must not wait on a producer that is gone");
1047
1048 assert!(matches!(error, CaptureError::SourceGone));
1049 }
1050
1051 #[test]
1052 fn the_first_outcome_is_the_one_the_reader_learns() {
1053 let (mut recording, writer) = ends();
1054 writer.close(Err(CaptureError::Microphone(
1055 MicrophoneError::NoInputDevice,
1056 )));
1057 drop(writer);
1058
1059 let error = recording.fill().expect_err("the recording failed to start");
1060
1061 assert!(matches!(
1062 error,
1063 CaptureError::Microphone(MicrophoneError::NoInputDevice)
1064 ));
1065 }
1066}