Skip to main content

wavekat_turn/
controller.rs

1use crate::{AudioFrame, AudioTurnDetector, TurnError, TurnPrediction, TurnState};
2
3/// Orchestration wrapper around any [`AudioTurnDetector`].
4///
5/// Tracks prediction state across calls and provides convenience methods
6/// like [`reset_if_finished`](TurnController::reset_if_finished) for
7/// correct VAD integration without manual state bookkeeping.
8///
9/// # Usage
10///
11/// ```ignore
12/// let detector = PipecatSmartTurn::new()?;
13/// let mut ctrl = TurnController::new(detector);
14///
15/// // Audio arrives continuously
16/// ctrl.push_audio(&frame);
17///
18/// // VAD speech start — soft reset (keeps buffer if turn was unfinished)
19/// ctrl.reset_if_finished();
20///
21/// // VAD speech end — predict
22/// let result = ctrl.predict()?;
23/// ```
24///
25/// See [`reset_if_finished`](TurnController::reset_if_finished) for details
26/// on when to use soft vs hard reset.
27pub struct TurnController<T: AudioTurnDetector> {
28    inner: T,
29    last_state: Option<TurnState>,
30}
31
32impl<T: AudioTurnDetector> TurnController<T> {
33    /// Create a new controller wrapping the given detector.
34    pub fn new(inner: T) -> Self {
35        Self {
36            inner,
37            last_state: None,
38        }
39    }
40
41    /// Feed audio into the detector.
42    pub fn push_audio(&mut self, frame: &AudioFrame) {
43        self.inner.push_audio(frame);
44    }
45
46    /// Run prediction on buffered audio.
47    ///
48    /// Tracks the result state internally for [`reset_if_finished`](Self::reset_if_finished).
49    pub fn predict(&mut self) -> Result<TurnPrediction, TurnError> {
50        let result = self.inner.predict()?;
51        self.last_state = Some(result.state);
52        Ok(result)
53    }
54
55    /// Hard reset — always clears the buffer.
56    ///
57    /// Use when you know a new turn is starting (e.g. after the assistant
58    /// finishes responding).
59    pub fn reset(&mut self) {
60        self.inner.reset();
61        self.last_state = None;
62    }
63
64    /// Soft reset — clears the buffer only if the last prediction was
65    /// [`Finished`](TurnState::Finished) or no prediction has been made
66    /// since the last reset.
67    ///
68    /// Returns `true` if a reset occurred, `false` if skipped.
69    ///
70    /// Call this on VAD speech-start when you don't know whether the user
71    /// is continuing the same turn or starting a new one. If the previous
72    /// prediction was [`Unfinished`](TurnState::Unfinished), the buffer is
73    /// preserved so the next [`predict`](Self::predict) runs on the full
74    /// accumulated audio.
75    pub fn reset_if_finished(&mut self) -> bool {
76        match self.last_state {
77            Some(TurnState::Unfinished) => false,
78            _ => {
79                self.reset();
80                true
81            }
82        }
83    }
84
85    /// Returns the state from the last [`predict`](Self::predict) call,
86    /// or `None` if no prediction has been made since the last reset.
87    pub fn last_state(&self) -> Option<TurnState> {
88        self.last_state
89    }
90
91    /// Returns a mutable reference to the inner detector.
92    pub fn inner_mut(&mut self) -> &mut T {
93        &mut self.inner
94    }
95
96    /// Unwrap the controller, returning the inner detector.
97    pub fn into_inner(self) -> T {
98        self.inner
99    }
100}