Implementing Voice Activity Detection Interrupt for Turn-Taking in Speech-to-Speech
The Hugging Face speech-to-speech library enables real-time turn-taking through the VADHandler class, which detects new user speech during system responses and triggers an interrupt event via the interrupt_on_new_speech parameter, configurable through min_speech_continuation_ms to balance responsiveness against false triggers.
The huggingface/speech-to-speech repository provides a modular pipeline for streaming speech-to-speech interactions. Implementing voice activity detection interrupt for turn-taking allows the system to naturally handle conversational overlaps, stopping text-to-speech generation when the user begins speaking again.
Core Architecture
The turn-taking system relies on three primary components that process audio streams and coordinate state transitions:
| Component | Responsibility | Source Location |
|---|---|---|
VADIterator |
Consumes raw audio chunks, runs the VAD model, and yields speech or silence events while tracking utterance state. | src/speech_to_speech/VAD/vad_iterator.py |
VADHandler |
Orchestrates the iterator, buffers audio, and emits VADAudio messages; implements the _maybe_interrupt logic for aborting ongoing utterances. |
src/speech_to_speech/VAD/vad_handler.py |
SmartTurn |
Analyzes VAD output alongside LLM responses to predict optimal turn-taking moments and can pre-empt the speaker when confidence is high. | src/speech_to_speech/VAD/smart_turn.py |
The pipeline communicates through lightweight message objects. The VADAudio class, defined in src/speech_to_speech/pipeline/messages.py, carries audio buffers with mode indicators ("progressive" for ongoing speech or "final" for completed segments) and unique turn identifiers.
How the Interrupt Mechanism Works
The interrupt logic inside VADHandler manages the transition between listening and speaking states through a five-stage process:
-
Audio Ingestion – Raw PCM chunks enter the
VADHandler.processmethod from the input stream. -
VAD Iteration – The handler delegates to
VADIterator, which applies a VAD model (such as Silero VAD or a Whisper-based detector) to classify audio as speech or silence. -
State Detection – When the iterator signals speech after a period of silence, the handler checks if a previous utterance remains open (not marked
"final"). -
Interrupt Decision – If
interrupt_on_new_speechis enabled and the iterator reports new speech while the previousVADAudiois still progressive, the private_maybe_interruptmethod finalizes the previous segment (setting mode to"final"), clears the buffer, and initializes a freshVADAudiowith mode"progressive". -
Downstream Propagation – The finalized segment propagates to STT, LLM, and TTS handlers, allowing the system to abort the current generation and process the new user input.
The min_speech_continuation_ms parameter prevents spurious interrupts by requiring a minimum pause duration before an utterance can be considered complete, protecting against brief pauses in continuous speech.
Configuration Parameters
Turn-taking behavior is controlled through VADHandlerArguments located in src/speech_to_speech/arguments_classes/vad_arguments.py. Key parameters include:
interrupt_on_new_speech– Boolean flag that enables the interrupt mechanism when set toTrue.min_speech_continuation_ms– Integer specifying the minimum silence duration (in milliseconds) required to finalize an utterance before a new interrupt can occur.vad_model– String identifier for the VAD backend (e.g.,"silero_vad").
These parameters are passed to VADHandler during pipeline construction or direct instantiation.
Implementation Examples
Basic Pipeline Integration
Configure the VAD handler through the pipeline constructor to enable interrupts in a complete speech-to-speech system:
from speech_to_speech import SpeechToSpeechPipeline
from speech_to_speech.arguments_classes import VADHandlerArguments
vad_args = VADHandlerArguments(
vad_model="silero_vad",
min_speech_continuation_ms=200,
interrupt_on_new_speech=True,
)
pipeline = SpeechToSpeechPipeline(
vad_handler_kwargs=vad_args,
# Additional handlers: stt_handler, llm_handler, tts_handler...
)
def audio_callback(indata, frames, time, status):
pipeline.process_audio(indata.astype("float32"))
# Pipeline automatically emits finalized VADAudio on interrupt or natural pauses
Direct VADHandler Usage
Interact with the handler directly to observe interrupt behavior on simulated audio streams:
from speech_to_speech.VAD.vad_handler import VADHandler
from speech_to_speech.pipeline.messages import VADAudio
import numpy as np
handler = VADHandler(
vad_model="silero_vad",
min_speech_continuation_ms=300,
interrupt_on_new_speech=True,
)
# Simulated audio: silence, speech, short pause, new speech (triggers interrupt)
audio_chunks = [
np.zeros(16000, dtype=np.float32),
np.random.randn(8000).astype(np.float32) * 0.02,
np.zeros(1600, dtype=np.float32),
np.random.randn(8000).astype(np.float32) * 0.02,
]
for chunk in audio_chunks:
msgs = list(handler.process(VADAudio(audio=chunk, mode="progressive")))
for msg in msgs:
print(f"Mode: {msg.mode}, Turn ID: {msg.turn_id}")
This outputs a "final" message for the first speech segment followed by a "progressive" message for the interrupting second segment.
Tuning Interrupt Sensitivity
Adjust the continuation threshold to match conversational dynamics:
vad_args = VADHandlerArguments(
min_speech_continuation_ms=500, # Require 500ms silence before finalizing
interrupt_on_new_speech=True,
)
handler = VADHandler(**vad_args.model_dump())
Higher values prevent premature turn-taking in languages with frequent mid-sentence pauses, while lower values increase responsiveness for rapid back-and-forth dialogue.
Key Source Files
The interrupt implementation spans the following repository locations:
src/speech_to_speech/VAD/vad_iterator.py– Low-level iterator managing VAD model inference and speech/silence state transitions.src/speech_to_speech/VAD/vad_handler.py– High-level handler containing_maybe_interruptlogic andVADAudiomessage generation.src/speech_to_speech/VAD/smart_turn.py– Optional analyzer for LLM-aware turn prediction.src/speech_to_speech/pipeline/messages.py– Definition ofVADAudiodataclass withmodeandturn_idfields.src/speech_to_speech/arguments_classes/vad_arguments.py– Configuration dataclass for VAD parameters.
Summary
- The
VADHandlerclass insrc/speech_to_speech/VAD/vad_handler.pyorchestrates turn-taking through the_maybe_interruptmethod, which finalizes ongoing utterances when new speech is detected. - Enable interrupts by setting
interrupt_on_new_speech=TrueinVADHandlerArguments. - Control sensitivity with
min_speech_continuation_msto balance between responsive interruption and false triggers during natural pauses. - The system uses
VADAudiomessages with"progressive"and"final"modes to signal state changes through the pipeline. SmartTurnprovides additional intelligence for preemptive turn-taking based on LLM confidence scores.
Frequently Asked Questions
What triggers a voice activity detection interrupt in the pipeline?
A VAD interrupt triggers when the VADIterator detects new speech activity while the VADHandler is still processing an open utterance (mode "progressive") and the interrupt_on_new_speech parameter is enabled. The handler's _maybe_interrupt method finalizes the current segment and begins buffering the new input.
How do I prevent the system from interrupting during natural speech pauses?
Increase the min_speech_continuation_ms value in VADHandlerArguments. This parameter defines the minimum silence duration required before an utterance can be finalized, preventing brief pauses from triggering false turn-taking events.
What is the difference between VADIterator and VADHandler?
VADIterator, located in src/speech_to_speech/VAD/vad_iterator.py, performs the low-level audio classification and state tracking. VADHandler, in src/speech_to_speech/VAD/vad_handler.py, wraps the iterator with buffering logic, interrupt management, and message generation for the broader pipeline.
Can SmartTurn operate without the VAD interrupt mechanism?
Yes, SmartTurn in src/speech_to_speech/VAD/smart_turn.py can function as an independent turn-taking strategy by analyzing LLM response confidence and VAD history. However, combining it with interrupt_on_new_speech provides the most responsive user experience, allowing both user-driven and system-driven turn transitions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →