Upload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
"""Barge-in: notice that the user started talking *while the pet is talking*
|
||||
so playback can be cut short mid-sentence.
|
||||
|
||||
Deliberately dumber than the utterance VAD in mic.py. The mic hears the pet's
|
||||
own voice coming back out of the speakers, so a single loud frame proves
|
||||
nothing — this requires several consecutive frames well above the normal
|
||||
speech threshold (BARGE_IN_RMS_THRESHOLD defaults to 4x VAD_RMS_THRESHOLD).
|
||||
Takes the same injectable stream shape as mic.record_utterance, so tests feed
|
||||
it fake frames instead of real audio hardware.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .. import config
|
||||
from .mic import AudioStream, rms
|
||||
|
||||
|
||||
class BargeInDetector:
|
||||
"""Poll-driven: call check() repeatedly while audio plays. Each call
|
||||
consumes exactly one mic frame (80ms at the default frame length), which
|
||||
is also what paces the playback loop's polling."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
stream: AudioStream,
|
||||
threshold: int = None,
|
||||
required_frames: int = None,
|
||||
frame_len: int = config.FRAME_LEN,
|
||||
):
|
||||
self._stream = stream
|
||||
self._threshold = config.BARGE_IN_RMS_THRESHOLD if threshold is None else threshold
|
||||
self._required = max(1, config.BARGE_IN_FRAMES if required_frames is None else required_frames)
|
||||
self._frame_len = frame_len
|
||||
self._loud_frames = 0
|
||||
|
||||
@property
|
||||
def loud_frames(self) -> int:
|
||||
return self._loud_frames
|
||||
|
||||
def reset(self) -> None:
|
||||
self._loud_frames = 0
|
||||
|
||||
def check(self) -> bool:
|
||||
"""True once the user has been loud for long enough to count as an
|
||||
interruption. Never raises: a mic hiccup mid-playback should not kill
|
||||
the reply, it should just mean "no barge-in this frame"."""
|
||||
try:
|
||||
chunk, _ = self._stream.read(self._frame_len)
|
||||
except Exception:
|
||||
return False
|
||||
frame = np.asarray(chunk)
|
||||
if frame.ndim > 1:
|
||||
frame = frame[:, 0]
|
||||
if frame.size == 0:
|
||||
return False
|
||||
if rms(frame) >= self._threshold:
|
||||
self._loud_frames += 1
|
||||
else:
|
||||
self._loud_frames = 0 # a single thump/cough shouldn't count
|
||||
return self._loud_frames >= self._required
|
||||
Reference in New Issue
Block a user