Documentation
¶
Overview ¶
Package console implements the audio pipeline for the lk console command. It connects microphone input and speaker output via PortAudio, applies WebRTC audio processing (echo cancellation, noise suppression), and communicates with an agent over TCP using protobuf-framed SessionMessages.
Architecture (3 goroutines, matching the Python console's PortAudio model):
micLoop — reads PortAudio input into the capture ring buffer.
speakerLoop — reads both rings, runs ProcessRender + ProcessCapture in
lockstep, writes to speakers, sends capture to agent.
Paced by outputStream.Write at the hardware output rate.
tcpReader — reads TCP messages: audio → playback ring, events → TUI.
All APM calls happen in speakerLoop, so they are single-threaded and guaranteed 1:1.
Index ¶
- Constants
- func BytesToSamples(data []byte) []int16
- func ReadSessionMessage(r io.Reader) (*agent.AgentSessionMessage, error)
- func SamplesToBytes(samples []int16) []byte
- func WriteSessionMessage(w io.Writer, msg *agent.AgentSessionMessage) error
- type AudioPipeline
- func (p *AudioPipeline) AECStats() *apm.Stats
- func (p *AudioPipeline) EnableAudio() error
- func (p *AudioPipeline) FFTBands() [NumFFTBands]float64
- func (p *AudioPipeline) HasAudio() bool
- func (p *AudioPipeline) IsPlaying() bool
- func (p *AudioPipeline) Level() float64
- func (p *AudioPipeline) Muted() bool
- func (p *AudioPipeline) SendRequest(req *agent.SessionRequest) error
- func (p *AudioPipeline) SetMuted(muted bool)
- func (p *AudioPipeline) SetPaused(paused bool)
- func (p *AudioPipeline) Start(ctx context.Context) error
- func (p *AudioPipeline) Stop()
- type PipelineConfig
- type RingBuffer
- type TCPServer
Constants ¶
const ( SampleRate = 48000 Channels = 1 FrameDurationMs = 30 SamplesPerFrame = SampleRate * FrameDurationMs / 1000 // 1440 APMFrameSamples = SampleRate / 100 // 480 (10ms) NumFFTBands = 14 CaptureRingFrames = 50 // ~1.5s — small, just absorbs jitter between mic and speaker loops PlaybackRingFrames = 4000 // ~120s — large, TTS pushes faster than real-time )
Variables ¶
This section is empty.
Functions ¶
func BytesToSamples ¶
func ReadSessionMessage ¶
func ReadSessionMessage(r io.Reader) (*agent.AgentSessionMessage, error)
ReadSessionMessage reads a protobuf-framed AgentSessionMessage.
func SamplesToBytes ¶
func WriteSessionMessage ¶
func WriteSessionMessage(w io.Writer, msg *agent.AgentSessionMessage) error
WriteSessionMessage sends a protobuf-framed AgentSessionMessage.
Types ¶
type AudioPipeline ¶
type AudioPipeline struct {
// Events channel receives AgentSessionEvents from the agent for the TUI.
Events chan *agent.AgentSessionEvent
// Responses channel receives SessionResponses (request completions) for the TUI.
Responses chan *agent.SessionResponse
// contains filtered or unexported fields
}
func NewPipeline ¶
func NewPipeline(cfg PipelineConfig) (*AudioPipeline, error)
func (*AudioPipeline) AECStats ¶
func (p *AudioPipeline) AECStats() *apm.Stats
func (*AudioPipeline) EnableAudio ¶
func (p *AudioPipeline) EnableAudio() error
EnableAudio lazily initializes audio devices. Returns an error if PortAudio is not available or devices cannot be opened.
func (*AudioPipeline) FFTBands ¶
func (p *AudioPipeline) FFTBands() [NumFFTBands]float64
func (*AudioPipeline) HasAudio ¶
func (p *AudioPipeline) HasAudio() bool
HasAudio reports whether the audio pipeline is active.
func (*AudioPipeline) IsPlaying ¶
func (p *AudioPipeline) IsPlaying() bool
func (*AudioPipeline) Level ¶
func (p *AudioPipeline) Level() float64
func (*AudioPipeline) Muted ¶
func (p *AudioPipeline) Muted() bool
func (*AudioPipeline) SendRequest ¶
func (p *AudioPipeline) SendRequest(req *agent.SessionRequest) error
func (*AudioPipeline) SetMuted ¶
func (p *AudioPipeline) SetMuted(muted bool)
func (*AudioPipeline) SetPaused ¶
func (p *AudioPipeline) SetPaused(paused bool)
SetPaused stops mic frames from being sent to the agent and drops any queued playback. The hardware streams keep running so the pipeline can resume instantly. Used to keep audio off the wire in text mode.
func (*AudioPipeline) Stop ¶
func (p *AudioPipeline) Stop()
type PipelineConfig ¶
type PipelineConfig struct {
InputDevice *portaudio.DeviceInfo // nil to skip audio (text-only)
OutputDevice *portaudio.DeviceInfo // nil to skip audio (text-only)
NoAEC bool
Conn net.Conn
}
type RingBuffer ¶
type RingBuffer struct {
// contains filtered or unexported fields
}
RingBuffer is a SPSC ring buffer for int16 audio samples. When the writer outruns the reader, the reader skips ahead to avoid stale data.
func NewRingBuffer ¶
func NewRingBuffer(size int) *RingBuffer
func (*RingBuffer) Available ¶
func (rb *RingBuffer) Available() int
func (*RingBuffer) Read ¶
func (rb *RingBuffer) Read(out []int16) bool
Read blocks until len(out) samples are available, then copies them.
func (*RingBuffer) ReadAvailable ¶
func (rb *RingBuffer) ReadAvailable(out []int16) int
ReadAvailable copies up to len(out) available samples into out (non-blocking). Returns the number of samples actually copied.
func (*RingBuffer) Reset ¶
func (rb *RingBuffer) Reset()
func (*RingBuffer) WaitForData ¶
func (rb *RingBuffer) WaitForData() bool
WaitForData blocks until samples are available in the buffer. Returns true if data is available, false if woken up with no data (e.g., after Reset or Broadcast for shutdown).
func (*RingBuffer) Write ¶
func (rb *RingBuffer) Write(samples []int16) int
type TCPServer ¶
type TCPServer struct {
// contains filtered or unexported fields
}
func NewTCPServer ¶
func (*TCPServer) Accept ¶
Accept waits for a single agent connection; subsequent connections are rejected.
func (*TCPServer) AcceptConn ¶
AcceptConn returns the next connection without closing the listener, so the server can keep accepting more. Used by the session daemon, which accepts both the agent connection and many CLI control connections on one port.