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}