Starting to create a local smart turn
This commit is contained in:
51
src/pipecat/audio/turn/local_smart_turn.py
Normal file
51
src/pipecat/audio/turn/local_smart_turn.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# Copyright (c) 2024–2025, Daily
|
||||
#
|
||||
# SPDX-License-Identifier: BSD 2-Clause License
|
||||
#
|
||||
|
||||
|
||||
import numpy as np
|
||||
from loguru import logger
|
||||
|
||||
from pipecat.audio.turn.base_turn_analyzer import BaseEndOfTurnAnalyzer, EndOfTurnState
|
||||
|
||||
|
||||
class LocalSmartTurnAnalyzer(BaseEndOfTurnAnalyzer):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._audio_buffer = bytearray()
|
||||
|
||||
logger.debug("Loading Local Smart Turn model...")
|
||||
|
||||
# TODO: implement it
|
||||
|
||||
logger.debug("Loaded Local Smart Turn")
|
||||
|
||||
def analyze_audio(self, buffer: bytes) -> EndOfTurnState:
|
||||
self._audio_buffer += buffer
|
||||
|
||||
# TODO: we probably don't need this
|
||||
# Checking if we have at least 6 seconds of audio
|
||||
# if len(self._audio_buffer) < 16000 * 2 * 6:
|
||||
# return EndOfTurnState.INCOMPLETE
|
||||
|
||||
audio_int16 = np.frombuffer(self._audio_buffer, dtype=np.int16)
|
||||
|
||||
# Divide by 32768 because we have signed 16-bit data.
|
||||
audio_float32 = np.frombuffer(audio_int16, dtype=np.int16).astype(np.float32) / 32768.0
|
||||
|
||||
# TODO: implement to use the smart turn
|
||||
# for now it is always returning as complete only for testing it
|
||||
prediction = 1
|
||||
|
||||
state = EndOfTurnState.COMPLETE if prediction == 1 else EndOfTurnState.INCOMPLETE
|
||||
|
||||
if state == EndOfTurnState.COMPLETE:
|
||||
# clears the buffer completely
|
||||
self._audio_buffer = bytearray()
|
||||
else:
|
||||
# TODO: implement it
|
||||
pass
|
||||
|
||||
return state
|
||||
@@ -172,9 +172,16 @@ class BaseInputTransport(FrameProcessor):
|
||||
elif isinstance(frame, UserStoppedSpeakingFrame):
|
||||
logger.debug("User stopped speaking")
|
||||
await self.push_frame(frame)
|
||||
|
||||
# TODO check, we probably should change here as well.
|
||||
# if the end of turn is enabled, we should only stop interruption after this point
|
||||
if self.interruptions_allowed:
|
||||
await self._stop_interruption()
|
||||
await self.push_frame(StopInterruptionFrame())
|
||||
elif isinstance(frame, UserEndOfTurnFrame):
|
||||
logger.debug("User end of turn")
|
||||
await self.push_frame(frame)
|
||||
# TODO: implement to handle interruptions
|
||||
|
||||
#
|
||||
# Audio input
|
||||
@@ -220,7 +227,7 @@ class BaseInputTransport(FrameProcessor):
|
||||
):
|
||||
new_eot_state = await self._end_of_turn_analyze(audio_frame)
|
||||
if new_eot_state != end_of_turn_state:
|
||||
await self.push_frame(UserEndOfTurnFrame())
|
||||
await self._handle_user_interruption(UserEndOfTurnFrame())
|
||||
return new_eot_state
|
||||
|
||||
async def _audio_task_handler(self):
|
||||
@@ -239,9 +246,13 @@ class BaseInputTransport(FrameProcessor):
|
||||
# changes from QUIET to SPEAKING and vice versa.
|
||||
if self._params.vad_enabled:
|
||||
vad_state = await self._handle_vad(frame, vad_state)
|
||||
# TODO: need to check if we need to keep it later
|
||||
if vad_state == VADState.QUIET:
|
||||
end_of_turn_state = EndOfTurnState.INCOMPLETE
|
||||
audio_passthrough = self._params.vad_audio_passthrough
|
||||
|
||||
if self._params.end_of_turn_analyzer:
|
||||
# We only need to check for completion if the user is speaking
|
||||
if self._params.end_of_turn_analyzer and VADState.QUIET != vad_state:
|
||||
end_of_turn_state = await self._handle_end_of_turn(frame, end_of_turn_state)
|
||||
|
||||
# Push audio downstream if passthrough.
|
||||
|
||||
Reference in New Issue
Block a user