Skip to main content

aria_kit/speaker/turns/
feed.rs

1use std::time::Duration;
2
3use super::{SpeakerTracker, Timeline};
4use crate::audio::{CaptureError, RecordingReader, RecordingWaker, canonical_samples};
5use crate::primitives::error_chain;
6use crate::voice_activity::{SpeechDetector, SpeechMap, VoiceActivityDetector};
7
8/// Feeds one recording to its speech detector and then to its tracker, on
9/// a fork of its own, so turns are cut where the audio pauses, not where a
10/// decoder finalizes. The tracker hears only what the detector has scored.
11pub struct TurnFeed<V, K> {
12    audio: RecordingReader,
13    speech: SpeechDetector<V>,
14    /// Where the next sample handed to the detector sits.
15    spoken_to: u64,
16    tracker: Option<K>,
17}
18
19/// How far [`TurnFeed::hear_ahead`] got.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Ahead {
22    Heard,
23    /// Nothing was left to hear: wait with [`TurnFeed::wait_for_audio`].
24    Idle,
25    /// Capture ended, and the detector and any tracker heard all of it.
26    Ended,
27}
28
29impl<V: VoiceActivityDetector, K: SpeakerTracker> TurnFeed<V, K> {
30    /// Forks `recording`, so call it before anything reads from it: the fork
31    /// starts where the reader stands. `speech` and `tracker` must start at
32    /// `recording.held_from()`.
33    pub fn start(
34        recording: &mut RecordingReader,
35        speech: SpeechDetector<V>,
36        tracker: Option<K>,
37    ) -> Self {
38        let audio = recording.fork_window();
39        let from = audio.held_from();
40        debug_assert_eq!(
41            speech.map().reached(),
42            from,
43            "the detector starts at the fork"
44        );
45        debug_assert!(
46            tracker
47                .as_ref()
48                .is_none_or(|tracker| tracker.heard_to() == from),
49            "the tracker starts at the fork"
50        );
51        Self {
52            audio,
53            speech,
54            spoken_to: from,
55            tracker,
56        }
57    }
58
59    /// The first sample the feed still holds: where the recording starts,
60    /// until the feed has heard some of it.
61    pub fn held_from(&self) -> u64 {
62        self.audio.held_from()
63    }
64
65    pub fn speech(&self) -> &SpeechMap {
66        self.speech.map()
67    }
68
69    /// `None` when no tracker runs.
70    pub fn timeline(&self) -> Option<&Timeline> {
71        self.tracker.as_ref().map(SpeakerTracker::timeline)
72    }
73
74    /// Stops judging voices. The detector goes on, and the audio only the
75    /// tracker still needed is freed.
76    pub fn drop_tracker(&mut self) {
77        self.tracker = None;
78        self.release();
79    }
80
81    /// Paced by a decoder: the detector, then the tracker, hear the audio up
82    /// to stream `position`. Words a decoder has read never end past where
83    /// it read, so their turns are cut by then. Returns whether the
84    /// recording reached `position`.
85    pub fn hear_through(&mut self, position: u64) -> bool {
86        let filled = self.audio.fill_through(position);
87        let reached = filled.as_ref().is_ok_and(|reached| *reached);
88        self.speak(filled.map(drop), position);
89        self.track_through(position);
90        reached
91    }
92
93    /// Paced by itself: the detector takes all the audio that has arrived,
94    /// the tracker at most `piece` of it. A tracker more than `max_lag`
95    /// behind the detector first skips to it.
96    pub fn hear_ahead(&mut self, piece: Duration, max_lag: Option<Duration>) -> Ahead {
97        let before = self.heard_to();
98        let filled = self.audio.fill_available();
99        self.speak(filled, u64::MAX);
100        if let Some(max_lag) = max_lag {
101            self.catch_up(max_lag);
102        }
103        let through = self.tracker.as_ref().map_or(self.spoken_to, |tracker| {
104            tracker.heard_to() + canonical_samples(piece)
105        });
106        self.track_through(through);
107        // Judged by what moved, not by why: a stage that stops hearing, for
108        // whatever reason, cannot keep the feed spinning.
109        if self.heard_to() != before {
110            return Ahead::Heard;
111        }
112        if self.audio.ended_at().is_some() {
113            return Ahead::Ended;
114        }
115        Ahead::Idle
116    }
117
118    /// Waits up to `timeout` for audio not heard yet, for capture to end,
119    /// or for `stopped`. A stop that rings [`Self::waker`] ends the wait at
120    /// once; one that is only polled is seen at the timeout.
121    pub fn wait_for_audio(&self, timeout: Duration, stopped: impl Fn() -> bool) {
122        self.audio.wait_for_audio(timeout, stopped);
123    }
124
125    /// Wakes a [`Self::wait_for_audio`] in progress.
126    pub fn waker(&self) -> RecordingWaker {
127        self.audio.waker()
128    }
129
130    /// Where the detector and the tracker will hear next.
131    fn heard_to(&self) -> (u64, Option<u64>) {
132        let tracked = self.tracker.as_ref().map(SpeakerTracker::heard_to);
133        (self.spoken_to, tracked)
134    }
135
136    /// Hears the rest of the recording, waiting for capture to end, and
137    /// judges what is still open, so every held segment can be decided.
138    pub fn hear_rest(&mut self) {
139        let filled = self.audio.fill();
140        self.speak(filled, u64::MAX);
141        self.speech.finish();
142        self.track_through(u64::MAX);
143        if let Some(tracker) = &mut self.tracker {
144            tracker.finish();
145        }
146    }
147
148    /// Forgets the map, and the tracker's timeline, before `at`, though
149    /// never what the detector or the tracker still reads.
150    pub fn forget_before(&mut self, at: u64) {
151        let at = match &mut self.tracker {
152            Some(tracker) => tracker.forget_before(at),
153            None => at.min(self.spoken_to),
154        };
155        self.speech.forget_before(at);
156    }
157
158    fn speak(&mut self, filled: Result<(), CaptureError>, position: u64) {
159        // A failed capture ends the recording; what it held is still heard.
160        if let Err(error) = filled {
161            tracing::debug!(error = %error_chain(&error), "the voice feed heard capture end early");
162        }
163        let end = position.min(self.audio.held_to()).max(self.spoken_to);
164        if !self.speech.map().is_broken()
165            && let Err(error) = self.speech.hear(self.audio.span(self.spoken_to..end))
166        {
167            tracing::warn!(
168                error = %error_chain(&error),
169                "voice-activity inference failed; no more speech can be told apart"
170            );
171        }
172        self.spoken_to = end;
173    }
174
175    fn catch_up(&mut self, max_lag: Duration) {
176        if let Some(tracker) = &mut self.tracker
177            && self.spoken_to.saturating_sub(tracker.heard_to()) > canonical_samples(max_lag)
178        {
179            tracker.skip_to(self.speech.map(), self.spoken_to);
180        }
181    }
182
183    /// The tracker hears up to stream `position`, but never past the audio
184    /// the detector has had.
185    fn track_through(&mut self, position: u64) {
186        if let Some(tracker) = &mut self.tracker {
187            let start = tracker.heard_to();
188            let end = position.min(self.spoken_to).max(start);
189            tracker.hear(self.speech.map(), self.audio.span(start..end));
190        }
191        self.release();
192    }
193
194    /// Frees the audio both the detector and the tracker are done with.
195    fn release(&mut self) {
196        let heard = self
197            .tracker
198            .as_ref()
199            .map_or(self.spoken_to, SpeakerTracker::heard_to);
200        self.audio.release_before(heard.min(self.spoken_to));
201    }
202}