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 #[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
236pub 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 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 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 #[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 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 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 fn retire(&self, queue: u64) {
338 self.lock().queues.retain(|held| held.id != queue);
339 }
340
341 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#[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 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 offset: u64,
391 keep_samples: bool,
394 queue: u64,
396 owns_outcome: bool,
399 outcome: Option<Ending>,
400}
401
402enum Ending {
405 Recorded(Option<RecordedAudio>),
406 Failed,
407}
408
409impl RecordingReader {
410 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 #[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 #[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 #[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 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 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 pub fn fill(&mut self) -> Result<(), CaptureError> {
484 while self.outcome.is_none() {
485 self.take()?;
486 }
487 self.failure()
488 }
489
490 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 pub fn fork_window(&mut self) -> Self {
515 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 pub fn held_from(&self) -> u64 {
538 self.offset
539 }
540
541 pub fn samples(&self) -> &[f32] {
543 &self.samples
544 }
545
546 pub fn held_to(&self) -> u64 {
548 self.offset + self.samples.len() as u64
549 }
550
551 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 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 pub fn reached_end(&self) -> bool {
571 self.outcome.is_some()
572 }
573
574 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 #[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 #[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 #[cfg(all(feature = "silero", not(target_family = "wasm")))]
628 pub fn waker(&self) -> RecordingWaker {
629 RecordingWaker(Arc::clone(&self.recording))
630 }
631
632 #[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 #[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 pub fn written(&self) -> u64 {
664 self.recording.lock().appended as u64
665 }
666
667 pub fn duration(&self) -> Duration {
669 canonical_duration(self.written())
670 }
671
672 fn take(&mut self) -> Result<(), CaptureError> {
674 let mut state = self.recording.lock();
675 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 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 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 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
731impl 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 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 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 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 #[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 #[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 #[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}