Voice OpenCode

Voice OpenCode

view artist

A real-time voice-controlled daemon that transcribes microphone audio via Whisper and routes it as commands or prompts to OpenCode — enabling hands-free AI pair programming from the terminal.

PythonWhisperPortAudioRichNumPyscipyuvVAD

Overview

Voice OpenCode is a Python daemon that runs in the background, listening to your microphone and translating speech into OpenCode commands. At its core it chains four responsibilities: capture audio in real time, transcribe it with Whisper, classify the transcribed text as a command or prompt, and execute it via opencode run as a subprocess.

Key Features

  • Real-time audio capture: Uses sounddevice (PortAudio bindings) to stream 16kHz mono audio in 0.5-second chunks
  • Energy-based VAD: Simple RMS thresholding with a rolling pre-buffer to avoid clipping utterance onsets
  • Whisper STT: Local speech-to-text via openai-whisper (base.en model), running entirely on-device
  • State machine: IDLEACCUMULATING modes allow trigger keywords, prompt accumulation, and command dispatch
  • OpenCode integration: Launches opencode run "<prompt>" in a daemon thread — the VAD loop continues while the LLM responds
  • Rich terminal UI: Styled startup panel showing model, agent, and available voice commands

Architecture

┌──────────────────────────────────────────────────────────┐
│                    voice-opencode                        │
│                                                          │
│  1. CAPTURE    microphone audio in real time             │
│       ↓                                                  │
│  2. TRANSCRIBE audio → text (Whisper STT model)          │
│       ↓                                                  │
│  3. ROUTE      text → voice command or prompt buffer     │
│       ↓                                                  │
│  4. EXECUTE    opencode run "<prompt>" as a subprocess   │
└──────────────────────────────────────────────────────────┘

Voice Activity Detection

The VAD uses a simple energy-based approach with a rolling pre-buffer:

  • Each 0.5s chunk is evaluated via RMS energy against a threshold
  • A 3-chunk (1.5s) rolling deque prepends audio before speech onset — preventing clipped recordings
  • Speech ends after 4 consecutive silent chunks (~2s)
  • Tunable constants: SILENCE_THRESHOLD, SILENCE_CHUNKS_TO_STOP, PRE_SPEECH_BUFFER

State Machine

          ┌─────────────────────────────────────────┐
          │                                         │
          ▼                                         │
        IDLE  ──── "input" / "hey code" ────→  ACCUMULATING
          ▲                                         │
          │                                         │
          ├──────── "cancel" / "clear" ─────────────┤
          │                                         │
          └──────── "send" / "run" ─────────────────┘
                    (fires opencode run)

In IDLE: only trigger commands are recognized. In ACCUMULATING: speech is appended to a prompt buffer until send or cancel is detected.

Command Matching

Substring matching against priority-ordered command sets: quit → cancel → send → start. This ordering ensures quit always works regardless of mode.


Technology Stack

ComponentTechnology
Audio Capturesounddevice (PortAudio)
Speech-to-Textopenai-whisper (base.en)
VADEnergy-based RMS thresholding
Terminal UIrich (Panel, Console, markup)
Packaginguv tool install (isolated venv)
RuntimePython ≥3.10

Key Design Decisions

DecisionRationale
subprocess.run over Python SDKDecouples from OpenCode’s internal API; survives SDK changes
Energy VAD over neural VADSimpler, zero dependencies, sufficient for clean mic input
fp16=FalseEnsures CPU compatibility; half-precision unsupported on non-NVIDIA hardware
Pre-buffer dequePrevents first ~0.5s of utterance from being cut off
uv tool installGlobal CLI install without manual venv management
Daemon thread for executionVAD loop continues while OpenCode processes the prompt

Packaging

The project uses pyproject.toml with a [project.scripts] entrypoint:

[project.scripts]
voice-opencode = "voice_opencode:main"

Installed via uv tool install ., which creates an isolated venv and exposes the voice-opencode binary globally. Dependencies include openai-whisper, sounddevice, numpy, scipy, and rich.