use std::time::Duration;
use super::{SpeakerTracker, Timeline};
use crate::audio::{CaptureError, RecordingReader, RecordingWaker, canonical_samples};
use crate::primitives::error_chain;
use crate::voice_activity::{SpeechDetector, SpeechMap, VoiceActivityDetector};
pub struct TurnFeed<V, K> {
audio: RecordingReader,
speech: SpeechDetector<V>,
spoken_to: u64,
tracker: Option<K>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ahead {
Heard,
Idle,
Ended,
}
impl<V: VoiceActivityDetector, K: SpeakerTracker> TurnFeed<V, K> {
pub fn start(
recording: &mut RecordingReader,
speech: SpeechDetector<V>,
tracker: Option<K>,
) -> Self {
let audio = recording.fork_window();
let from = audio.held_from();
debug_assert_eq!(
speech.map().reached(),
from,
"the detector starts at the fork"
);
debug_assert!(
tracker
.as_ref()
.is_none_or(|tracker| tracker.heard_to() == from),
"the tracker starts at the fork"
);
Self {
audio,
speech,
spoken_to: from,
tracker,
}
}
pub fn held_from(&self) -> u64 {
self.audio.held_from()
}
pub fn speech(&self) -> &SpeechMap {
self.speech.map()
}
pub fn timeline(&self) -> Option<&Timeline> {
self.tracker.as_ref().map(SpeakerTracker::timeline)
}
pub fn drop_tracker(&mut self) {
self.tracker = None;
self.release();
}
pub fn hear_through(&mut self, position: u64) -> bool {
let filled = self.audio.fill_through(position);
let reached = filled.as_ref().is_ok_and(|reached| *reached);
self.speak(filled.map(drop), position);
self.track_through(position);
reached
}
pub fn hear_ahead(&mut self, piece: Duration, max_lag: Option<Duration>) -> Ahead {
let before = self.heard_to();
let filled = self.audio.fill_available();
self.speak(filled, u64::MAX);
if let Some(max_lag) = max_lag {
self.catch_up(max_lag);
}
let through = self.tracker.as_ref().map_or(self.spoken_to, |tracker| {
tracker.heard_to() + canonical_samples(piece)
});
self.track_through(through);
if self.heard_to() != before {
return Ahead::Heard;
}
if self.audio.ended_at().is_some() {
return Ahead::Ended;
}
Ahead::Idle
}
pub fn wait_for_audio(&self, timeout: Duration, stopped: impl Fn() -> bool) {
self.audio.wait_for_audio(timeout, stopped);
}
pub fn waker(&self) -> RecordingWaker {
self.audio.waker()
}
fn heard_to(&self) -> (u64, Option<u64>) {
let tracked = self.tracker.as_ref().map(SpeakerTracker::heard_to);
(self.spoken_to, tracked)
}
pub fn hear_rest(&mut self) {
let filled = self.audio.fill();
self.speak(filled, u64::MAX);
self.speech.finish();
self.track_through(u64::MAX);
if let Some(tracker) = &mut self.tracker {
tracker.finish();
}
}
pub fn forget_before(&mut self, at: u64) {
let at = match &mut self.tracker {
Some(tracker) => tracker.forget_before(at),
None => at.min(self.spoken_to),
};
self.speech.forget_before(at);
}
fn speak(&mut self, filled: Result<(), CaptureError>, position: u64) {
if let Err(error) = filled {
tracing::debug!(error = %error_chain(&error), "the voice feed heard capture end early");
}
let end = position.min(self.audio.held_to()).max(self.spoken_to);
if !self.speech.map().is_broken()
&& let Err(error) = self.speech.hear(self.audio.span(self.spoken_to..end))
{
tracing::warn!(
error = %error_chain(&error),
"voice-activity inference failed; no more speech can be told apart"
);
}
self.spoken_to = end;
}
fn catch_up(&mut self, max_lag: Duration) {
if let Some(tracker) = &mut self.tracker
&& self.spoken_to.saturating_sub(tracker.heard_to()) > canonical_samples(max_lag)
{
tracker.skip_to(self.speech.map(), self.spoken_to);
}
}
fn track_through(&mut self, position: u64) {
if let Some(tracker) = &mut self.tracker {
let start = tracker.heard_to();
let end = position.min(self.spoken_to).max(start);
tracker.hear(self.speech.map(), self.audio.span(start..end));
}
self.release();
}
fn release(&mut self) {
let heard = self
.tracker
.as_ref()
.map_or(self.spoken_to, SpeakerTracker::heard_to);
self.audio.release_before(heard.min(self.spoken_to));
}
}