Documentation
¶
Overview ¶
Package audio provides voice activity detection (VAD), turn detection, and audio session management for real-time voice AI applications.
The package follows industry-standard patterns for voice AI:
- VAD (Voice Activity Detection): Detects when someone is speaking vs. silent
- Turn Detection: Determines when a speaker has finished their turn
- Interruption Handling: Manages user interrupting bot output
Architecture ¶
Audio processing follows a two-stage approach:
- VADAnalyzer detects voice activity in real-time
- TurnDetector uses VAD output plus additional signals to detect turn boundaries
Usage Example ¶
vad := audio.NewSimpleVAD(audio.DefaultVADParams())
detector := audio.NewSilenceDetector(500 * time.Millisecond)
for chunk := range audioStream {
vad.Analyze(ctx, chunk)
if detector.DetectTurnEnd(ctx, vad) {
// User finished speaking
}
}
Package audio provides audio processing utilities.
Index ¶
- Constants
- func Resample24kTo16k(input []byte) ([]byte, error)
- func ResamplePCM16(input []byte, fromRate, toRate int) ([]byte, error)
- type AccumulatingTurnDetector
- type AdaptiveVAD
- type Chunk
- type Format
- type InterruptionCallback
- type InterruptionHandler
- func (h *InterruptionHandler) Interrupt()
- func (h *InterruptionHandler) Interrupted() <-chan struct{}
- func (h *InterruptionHandler) IsBotSpeaking() bool
- func (h *InterruptionHandler) NotifySentenceBoundary()
- func (h *InterruptionHandler) OnInterrupt(callback InterruptionCallback)
- func (h *InterruptionHandler) ProcessAudio(ctx context.Context, audio []byte) (bool, error)
- func (h *InterruptionHandler) ProcessVADState(ctx context.Context, state VADState) (bool, error)
- func (h *InterruptionHandler) Reset()
- func (h *InterruptionHandler) SetBotSpeaking(speaking bool)
- func (h *InterruptionHandler) WasInterrupted() bool
- type InterruptionStrategy
- type JitterBuffer
- type MediaFrame
- type MediaKind
- type MemSink
- type MemSource
- type Session
- type SilenceDetector
- func (d *SilenceDetector) GetAccumulatedAudio() []byte
- func (d *SilenceDetector) IsUserSpeaking() bool
- func (d *SilenceDetector) Name() string
- func (d *SilenceDetector) OnTurnComplete(callback TurnCallback)
- func (d *SilenceDetector) ProcessAudio(ctx context.Context, audio []byte) (bool, error)
- func (d *SilenceDetector) ProcessVADState(ctx context.Context, state VADState) (bool, error)
- func (d *SilenceDetector) Reset()
- func (d *SilenceDetector) SetTranscript(transcript string)
- type SilenceDetectorOption
- type SimpleVAD
- type Sink
- type Source
- type TurnCallback
- type TurnDetector
- type VADAnalyzer
- type VADEvent
- type VADParams
- type VADState
- type ValidationError
Constants ¶
const ( SampleRate24kHz = 24000 // Common TTS output rate SampleRate16kHz = 16000 // Common STT/ASR input rate // DuplexRate is the native device rate for the single duplex stream. // Resample only at the STT (48→16 kHz) and TTS (24→48 kHz) seams. DuplexRate = 48000 )
Standard audio sample rates for common use cases.
const ( DefaultVADConfidence = 0.5 DefaultVADStartSecs = 0.2 DefaultVADStopSecs = 0.8 DefaultVADMinVolume = 0.01 DefaultVADSampleRate = 16000 )
Default VAD parameter values.
const DefaultMaxAudioBufferSize = 10 * 1024 * 1024
DefaultMaxAudioBufferSize is the maximum size of the audio buffer in bytes. At 16kHz/16-bit mono (32KB/s), 10MB holds approximately 5 minutes of audio.
Variables ¶
This section is empty.
Functions ¶
func Resample24kTo16k ¶
Resample24kTo16k is a convenience function for the common case of resampling from 24kHz (TTS output) to 16kHz (Gemini input).
Types ¶
type AccumulatingTurnDetector ¶
type AccumulatingTurnDetector interface {
TurnDetector
// OnTurnComplete registers a callback for when a complete turn is detected.
OnTurnComplete(callback TurnCallback)
// GetAccumulatedAudio returns audio accumulated so far (may be incomplete turn).
GetAccumulatedAudio() []byte
// SetTranscript sets the transcript for the current turn (from external STT).
SetTranscript(transcript string)
}
AccumulatingTurnDetector is a TurnDetector that accumulates audio during a turn.
type AdaptiveVAD ¶ added in v1.5.3
type AdaptiveVAD struct {
// contains filtered or unexported fields
}
AdaptiveVAD is a voice activity detector that adapts its speech threshold to the ambient noise level. It is well-suited for "quiet mic" environments where the speaker's voice is only slightly louder than the background — a condition where SimpleVAD's fixed threshold often fails to trigger.
Algorithm:
- Compute the RMS of each PCM16 chunk.
- Smooth with an exponential moving average (α = 0.3).
- Derive speechThreshold = max(noiseFloor × 3.0, 0.01).
- Adapt the noise floor upward only when smoothedRMS < speechThreshold (i.e. we are probably in silence, not speech).
- Map smoothedRMS → probability in [0, 1] using a linear scale between noiseFloor and 2 × (speechThreshold − noiseFloor).
AdaptiveVAD embeds *vadStateMachine, which promotes State(), OnStateChange(), and base Reset() to satisfy the VADAnalyzer interface.
func NewAdaptiveVAD ¶ added in v1.5.3
func NewAdaptiveVAD(params VADParams) (*AdaptiveVAD, error)
NewAdaptiveVAD creates an AdaptiveVAD analyzer with the given parameters.
func (*AdaptiveVAD) Analyze ¶ added in v1.5.3
Analyze processes audio and returns voice probability based on adaptive RMS analysis.
func (*AdaptiveVAD) Name ¶ added in v1.5.3
func (v *AdaptiveVAD) Name() string
Name returns the analyzer identifier.
func (AdaptiveVAD) OnStateChange ¶ added in v1.5.3
func (m AdaptiveVAD) OnStateChange() <-chan VADEvent
OnStateChange returns a channel that receives VADEvent values on each state transition. The channel is buffered; events are dropped when it is full.
func (*AdaptiveVAD) Reset ¶ added in v1.5.3
func (v *AdaptiveVAD) Reset()
Reset clears accumulated state for a new conversation, resetting the smoothed RMS and noise floor back to their initial values.
type Chunk ¶ added in v1.4.7
type Chunk struct {
// Data is the raw audio bytes.
Data []byte
// Index is the chunk sequence number (0-indexed).
Index int
// Final indicates this is the last chunk.
Final bool
// Error is set if an error occurred while producing the chunk.
Error error
}
Chunk represents a chunk of audio data flowing through the runtime — produced by TTS providers, consumed by playback sinks, realtime LLM inputs, and pipeline stages. Carries the bytes plus stream-position metadata.
type Format ¶ added in v1.5.4
type Format struct {
// SampleRate is the number of audio samples per second (e.g. 16000, 24000, 48000).
SampleRate int
// Channels is 1 for mono, 2 for stereo.
Channels int
}
Format describes the encoding of the media payload. Audio fields are present now; video fields (Width, Height, Codec) will be added later.
type InterruptionCallback ¶
type InterruptionCallback func()
InterruptionCallback is called when user interrupts the bot.
type InterruptionHandler ¶
type InterruptionHandler struct {
// contains filtered or unexported fields
}
InterruptionHandler manages user interruption logic during bot output.
func NewInterruptionHandler ¶
func NewInterruptionHandler(strategy InterruptionStrategy, vad VADAnalyzer) *InterruptionHandler
NewInterruptionHandler creates an InterruptionHandler with the given strategy and VAD.
func (*InterruptionHandler) Interrupt ¶ added in v1.5.4
func (h *InterruptionHandler) Interrupt()
Interrupt externally signals an interruption. Use this on the realtime/ASM path where barge-in is detected by the provider's server-side VAD rather than our local VAD — there is no ProcessVADState call to drive handleInterruption, so the provider stage signals the interruption directly. Idempotent within a turn; cleared by Reset.
func (*InterruptionHandler) Interrupted ¶ added in v1.5.4
func (h *InterruptionHandler) Interrupted() <-chan struct{}
Interrupted returns a channel closed when an interruption fires for the current turn. Reset re-arms it, so callers should re-fetch after a Reset.
func (*InterruptionHandler) IsBotSpeaking ¶
func (h *InterruptionHandler) IsBotSpeaking() bool
IsBotSpeaking returns true if the bot is currently outputting audio.
func (*InterruptionHandler) NotifySentenceBoundary ¶
func (h *InterruptionHandler) NotifySentenceBoundary()
NotifySentenceBoundary notifies the handler of a sentence boundary. For deferred interruption strategy, this may trigger the pending interruption.
func (*InterruptionHandler) OnInterrupt ¶
func (h *InterruptionHandler) OnInterrupt(callback InterruptionCallback)
OnInterrupt registers a callback for when interruption occurs.
func (*InterruptionHandler) ProcessAudio ¶
ProcessAudio processes audio and detects user interruption. Returns true if an interruption was detected and should be acted upon.
func (*InterruptionHandler) ProcessVADState ¶
ProcessVADState processes a VAD state update for interruption detection. Returns true if an interruption was detected and should be acted upon.
func (*InterruptionHandler) Reset ¶
func (h *InterruptionHandler) Reset()
Reset clears interruption state for a new turn.
func (*InterruptionHandler) SetBotSpeaking ¶
func (h *InterruptionHandler) SetBotSpeaking(speaking bool)
SetBotSpeaking sets whether the bot is currently outputting audio.
func (*InterruptionHandler) WasInterrupted ¶
func (h *InterruptionHandler) WasInterrupted() bool
WasInterrupted returns true if an interruption occurred.
type InterruptionStrategy ¶
type InterruptionStrategy int
InterruptionStrategy determines how to handle user interrupting bot.
const ( // InterruptionIgnore ignores user speech during bot output. InterruptionIgnore InterruptionStrategy = iota // InterruptionImmediate immediately stops bot and starts listening. InterruptionImmediate // InterruptionDeferred waits for bot's current sentence, then switches. InterruptionDeferred )
func (InterruptionStrategy) String ¶
func (s InterruptionStrategy) String() string
String returns a human-readable representation of the interruption strategy.
type JitterBuffer ¶ added in v1.5.4
type JitterBuffer struct {
// contains filtered or unexported fields
}
JitterBuffer is a bounded FIFO ring buffer of int16 PCM samples (mono). It is safe for concurrent use: Push from a producer goroutine while Pull or Clear are called from the duplex playback loop.
Overflow policy: when a Push would exceed capacity the OLDEST samples are dropped to make room for the newest audio, and the dropped count is added to the cumulative Drops counter.
Underrun policy: Pull always returns exactly n samples; when fewer than n are buffered the tail is zero-filled (silence).
func NewJitterBuffer ¶ added in v1.5.4
func NewJitterBuffer(capacitySamples int) *JitterBuffer
NewJitterBuffer returns a JitterBuffer with the given maximum capacity in samples. A capacity of zero is valid but all pushes will drop immediately.
func (*JitterBuffer) Clear ¶ added in v1.5.4
func (j *JitterBuffer) Clear()
Clear drops all buffered samples. The next Pull will return silence.
func (*JitterBuffer) Drops ¶ added in v1.5.4
func (j *JitterBuffer) Drops() int64
Drops returns the cumulative number of samples that have been dropped due to buffer overflow.
func (*JitterBuffer) Len ¶ added in v1.5.4
func (j *JitterBuffer) Len() int
Len returns the number of samples currently in the buffer.
func (*JitterBuffer) Pull ¶ added in v1.5.4
func (j *JitterBuffer) Pull(n int) []int16
Pull removes and returns exactly n samples from the front of the buffer. If fewer than n samples are available the returned slice is zero-filled from the point of underrun through index n-1.
func (*JitterBuffer) Push ¶ added in v1.5.4
func (j *JitterBuffer) Push(samples []int16)
Push appends samples to the buffer. If appending would exceed capacity, the oldest samples are discarded first and the drop counter incremented.
type MediaFrame ¶ added in v1.5.4
type MediaFrame struct {
// Kind identifies the media type.
Kind MediaKind
// Data is the raw payload — PCM16 little-endian for audio.
Data []byte
// PTS is the presentation timestamp from the session clock.
PTS time.Duration
// Format describes the encoding of Data.
Format Format
}
MediaFrame is a single unit of captured or synthesized media. PTS (presentation timestamp) is measured from the session clock and is the load-bearing field for AEC delay estimation and A/V sync.
type MediaKind ¶ added in v1.5.4
type MediaKind int
MediaKind identifies the type of media carried in a MediaFrame.
type MemSink ¶ added in v1.5.4
type MemSink struct {
// contains filtered or unexported fields
}
MemSink is an in-memory Sink for tests and headless use. It records every Write call; Flush drops the recorded frames. Safe for concurrent use.
func NewMemSink ¶ added in v1.5.4
NewMemSink creates a MemSink that accepts frames of the given kind.
func (*MemSink) Close ¶ added in v1.5.4
Close is a no-op for MemSink; it satisfies the Sink interface.
func (*MemSink) Flush ¶ added in v1.5.4
func (m *MemSink) Flush()
Flush drops all queued frames, simulating barge-in drain.
func (*MemSink) Write ¶ added in v1.5.4
func (m *MemSink) Write(f MediaFrame)
Write appends f to the internal frame log.
func (*MemSink) Written ¶ added in v1.5.4
func (m *MemSink) Written() []MediaFrame
Written returns a copy of the frames written since the last Flush. The returned slice is independent of the internal buffer, making it safe to read concurrently with ongoing Write or Flush calls.
type MemSource ¶ added in v1.5.4
type MemSource struct {
// contains filtered or unexported fields
}
MemSource is an in-memory Source for tests and headless use. Push frames via Push; close the channel via Close so that range-loops terminate. Safe for concurrent use.
func NewMemSource ¶ added in v1.5.4
NewMemSource creates a MemSource with a buffered channel of size buf.
func (*MemSource) Close ¶ added in v1.5.4
Close closes the underlying channel, signaling end-of-stream to consumers. It is idempotent; calling Close more than once is safe.
func (*MemSource) Frames ¶ added in v1.5.4
func (m *MemSource) Frames() <-chan MediaFrame
Frames returns the read-only channel of MediaFrames.
func (*MemSource) Push ¶ added in v1.5.4
func (m *MemSource) Push(f MediaFrame)
Push sends f onto the internal channel. It blocks if the buffer is full.
type Session ¶ added in v1.5.4
type Session interface {
// Start begins media flow. It runs until ctx is canceled or Close is called.
Start(ctx context.Context) error
// Sources returns all Sources in this session.
Sources() []Source
// Sinks returns all Sinks in this session.
Sinks() []Sink
// Close stops all Sources and Sinks and releases session resources.
Close() error
}
Session groups the Sources and Sinks that belong to one audio session (e.g. a single call leg: one microphone source + one speaker sink).
type SilenceDetector ¶
type SilenceDetector struct {
// Threshold is the silence duration required to trigger turn end.
Threshold time.Duration
// MaxBufferSize is the maximum audio buffer size in bytes.
// When exceeded, the oldest audio data is discarded to stay within the limit.
// Default: DefaultMaxAudioBufferSize (10MB).
MaxBufferSize int
// contains filtered or unexported fields
}
SilenceDetector detects turn boundaries based on silence duration. It triggers end-of-turn when silence exceeds a configurable threshold.
func NewSilenceDetector ¶
func NewSilenceDetector(threshold time.Duration, opts ...SilenceDetectorOption) *SilenceDetector
NewSilenceDetector creates a SilenceDetector with the given threshold. threshold is the duration of silence required to trigger end-of-turn.
func (*SilenceDetector) GetAccumulatedAudio ¶
func (d *SilenceDetector) GetAccumulatedAudio() []byte
GetAccumulatedAudio returns audio accumulated so far.
func (*SilenceDetector) IsUserSpeaking ¶
func (d *SilenceDetector) IsUserSpeaking() bool
IsUserSpeaking returns true if user is currently speaking.
func (*SilenceDetector) Name ¶
func (d *SilenceDetector) Name() string
Name returns the detector identifier.
func (*SilenceDetector) OnTurnComplete ¶
func (d *SilenceDetector) OnTurnComplete(callback TurnCallback)
OnTurnComplete registers a callback for when a complete turn is detected.
func (*SilenceDetector) ProcessAudio ¶
ProcessAudio processes an incoming audio chunk. This implementation delegates to ProcessVADState and expects VAD to be run separately. Returns true if end of turn is detected.
func (*SilenceDetector) ProcessVADState ¶
ProcessVADState processes a VAD state update and detects turn boundaries. Returns true if end of turn is detected.
func (*SilenceDetector) Reset ¶
func (d *SilenceDetector) Reset()
Reset clears state for a new conversation.
func (*SilenceDetector) SetTranscript ¶
func (d *SilenceDetector) SetTranscript(transcript string)
SetTranscript sets the transcript for the current turn.
type SilenceDetectorOption ¶ added in v1.3.10
type SilenceDetectorOption func(*SilenceDetector)
SilenceDetectorOption configures a SilenceDetector.
func WithMaxAudioBufferSize ¶ added in v1.3.10
func WithMaxAudioBufferSize(size int) SilenceDetectorOption
WithMaxAudioBufferSize sets the maximum audio buffer size in bytes. When the buffer exceeds this limit, the oldest data is trimmed.
type SimpleVAD ¶
type SimpleVAD struct {
// contains filtered or unexported fields
}
SimpleVAD is a basic voice activity detector using RMS (Root Mean Square) analysis. It provides a lightweight VAD implementation without requiring external ML models. For more accurate detection, consider using SileroVAD.
SimpleVAD embeds *vadStateMachine, which promotes State(), OnStateChange(), and base Reset() — together they satisfy the VADAnalyzer interface.
func NewSimpleVAD ¶
NewSimpleVAD creates a SimpleVAD analyzer with the given parameters.
func (*SimpleVAD) Analyze ¶
Analyze processes audio and returns voice probability based on RMS volume.
func (SimpleVAD) OnStateChange ¶
func (m SimpleVAD) OnStateChange() <-chan VADEvent
OnStateChange returns a channel that receives VADEvent values on each state transition. The channel is buffered; events are dropped when it is full.
type Sink ¶ added in v1.5.4
type Sink interface {
// Write enqueues a MediaFrame for playback/storage.
// Note: hardware sinks fix the playback sample rate at session construction
// (WithPlaybackRate, default 24 kHz); MediaFrame.Format is informational and
// is NOT resampled. Callers must write frames at the session's playback rate.
// (Phase 3 adds resample-at-sink.)
Write(MediaFrame)
// Flush drops all queued output immediately (e.g. on barge-in).
Flush()
// Kind returns the MediaKind consumed by this sink.
Kind() MediaKind
// Close stops the sink and releases any underlying resources.
Close() error
}
Sink is a write-only media stream. Implementations include hardware playback devices (speaker), file writers, and the in-memory MemSink test double.
type Source ¶ added in v1.5.4
type Source interface {
// Frames returns a channel that delivers captured MediaFrames.
// The channel is closed when the source ends or Close is called.
Frames() <-chan MediaFrame
// Kind returns the MediaKind produced by this source.
Kind() MediaKind
// Close stops the source and releases any underlying resources.
Close() error
}
Source is a read-only media stream. Implementations include hardware capture devices (microphone), file readers, and the in-memory MemSource test double.
type TurnCallback ¶
TurnCallback is called when a complete user turn is detected. audio contains the accumulated audio for the turn. transcript contains any accumulated transcript (may be empty).
type TurnDetector ¶
type TurnDetector interface {
// Name returns the detector identifier.
Name() string
// ProcessAudio processes an incoming audio chunk.
// Returns true if end of turn is detected.
ProcessAudio(ctx context.Context, audio []byte) (bool, error)
// ProcessVADState processes a VAD state update.
// Returns true if end of turn is detected based on VAD state.
ProcessVADState(ctx context.Context, state VADState) (bool, error)
// IsUserSpeaking returns true if user is currently speaking.
IsUserSpeaking() bool
// Reset clears state for a new conversation.
Reset()
}
TurnDetector determines when a speaker has finished their turn. This is separate from VAD - VAD detects voice activity, turn detection determines conversation boundaries.
type VADAnalyzer ¶
type VADAnalyzer interface {
// Name returns the analyzer identifier.
Name() string
// Analyze processes audio and returns voice probability (0.0-1.0).
// audio should be raw PCM samples at the configured sample rate.
Analyze(ctx context.Context, audio []byte) (float64, error)
// State returns the current VAD state based on accumulated analysis.
State() VADState
// OnStateChange returns a channel that receives state transitions.
// The channel is buffered and may drop events if not consumed.
OnStateChange() <-chan VADEvent
// Reset clears accumulated state for a new conversation.
Reset()
}
VADAnalyzer analyzes audio for voice activity.
type VADEvent ¶
type VADEvent struct {
State VADState
PrevState VADState
Timestamp time.Time
Duration time.Duration // How long in the previous state
Confidence float64 // Voice confidence at transition
}
VADEvent represents a state transition in VAD.
type VADParams ¶
type VADParams struct {
// Confidence threshold for voice detection (0.0-1.0, default: 0.5).
// Higher values require more confidence before triggering.
Confidence float64
// StartSecs is seconds of speech required to trigger VADStateSpeaking (default: 0.2).
// Prevents false starts from brief noise.
StartSecs float64
// StopSecs is seconds of silence required to trigger VADStateQuiet (default: 0.8).
// Allows natural pauses without ending turn.
StopSecs float64
// MinVolume is the minimum RMS volume threshold (default: 0.01).
// Audio below this is treated as silence.
MinVolume float64
// SampleRate is the audio sample rate in Hz (default: 16000).
SampleRate int
}
VADParams configures voice activity detection behavior.
func DefaultVADParams ¶
func DefaultVADParams() VADParams
DefaultVADParams returns sensible defaults for voice activity detection.
type VADState ¶
type VADState int
VADState represents the current voice activity state.
const ( // VADStateQuiet indicates no voice activity detected. VADStateQuiet VADState = iota // VADStateStarting indicates voice is starting (within start threshold). VADStateStarting // VADStateSpeaking indicates active speech. VADStateSpeaking // VADStateStopping indicates voice is stopping (within stop threshold). VADStateStopping )
type ValidationError ¶
ValidationError represents a parameter validation error.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string