A local voice assistant for people who want the assistant they already use by text to answer by voice too.
computah listens for a wake word, transcribes the request, sends the transcript to a persistent assistant session, and speaks the answer back. The speech path runs locally on CPU with openWakeWord, faster-whisper, and Piper. The brain can run on the same host or behind a small file bridge on another machine.
computah is the mic-free core of a local voice assistant. Feed it a wav file and it runs the same stages a live loop will use later:
- Detect the wake word in the audio.
- Transcribe the spoken request.
- Send the transcript to an assistant session that keeps context.
- Render the answer as a wav file.
The project is built so each stage can be replaced. The wake-word detector, transcriber, brain bridge, and text-to-speech layer have narrow boundaries in pipeline.py.
Most voice assistants bundle wake-word detection, speech recognition, memory, and speech output into one service. That makes the assistant easy to start but hard to own. computah is shaped around different constraints:
- The wake word is configurable today, and a trainable
computahwake word is the goal. - Speech recognition and speech synthesis stay local.
- The voice interface talks to the same long-running assistant session used for text.
- Tests can run without a microphone by passing audio files through the pipeline.
- Model files, voices, local session paths, and hostnames stay outside git.
The name is also the intended wake word: “computah,” said in your own voice.
v0.1.0 is a working file-based pipeline, not a live microphone appliance yet.
| Area | Status |
|---|---|
| Wake-word detection | Works with installed openWakeWord models and custom .onnx files in models/. |
| Speech-to-text | Works through faster-whisper with CTranslate2 int8. |
| Brain | Supports a fallback CLI backend and the persistent file bridge. |
| Text-to-speech | Works through Piper by writing a reply wav. |
| Live loop | Planned. |
Custom computah wake word |
Planned; recording notes live in docs/recording-computah.md. |
audio in
└─▶ wake word openWakeWord, ONNX, 80 ms frames
└─▶ transcript faster-whisper, CTranslate2 int8
└─▶ brain persistent assistant bridge or CLI fallback
└─▶ wav Piper, ONNX voice model
| Stage | Default implementation | Boundary |
|---|---|---|
| Wake word | openWakeWord | detect_wake returns whether the configured phrase fired. |
| Speech-to-text | faster-whisper | transcribe returns text from the audio file. |
| Brain | bridge or CLI | brain returns short spoken text. |
| Text-to-speech | Piper | speak writes the answer to a wav file. |
Module-level caches keep the wake-word and Whisper models warm inside one process. Wake-word detection normalizes audio to 16 kHz mono int16; transcription passes the wav file to faster-whisper.
In a live turn, a mishear guard sits between transcription and the brain: it reads faster-whisper's own confidence signals (avg_logprob, no_speech_prob) and, when a transcript looks garbled or silence-derived, speaks a short re-prompt instead of dispatching. Because the brain acts on what it hears, this keeps a misheard command from triggering an action. Both live paths (run_turn and the live_driver hardware loop) gate through the same guard_transcript; the file-based run_pipeline reports the same signals for inspection but does not gate on them.
The bridge is the main design choice. computah does not need to create a fresh assistant call for every voice turn. It can append a user event to an inbox file and wait for the next reply block from an already-running assistant session.
brain_bridge.py keeps the transport injectable:
cli_sendandfile_reply_readertalk to a session on the same host.ssh_cli_sendandssh_reply_readeruse the same file contract on another host.local_sim_sendandsim_persona.pylet tests exercise bridge behavior without a live assistant.
brain_via_bridge snapshots the latest reply block, sends one transcript, then polls until a newer block appears. Voice turns are serialized, so this simple positional contract is enough for the current prototype.
A fresh clone uses brain_backend: "cli" so it can run without bridge setup. To use the persistent session path, copy config.local.example.json to config.local.json, set brain_backend to bridge, and keep deployment values there. config.local.json is gitignored and overrides config.json at runtime.
| Path | Purpose |
|---|---|
pipeline.py |
Pipeline stages, config loading, and CLI entry point. |
brain_bridge.py |
Bridge contract plus local, ssh, and simulated transports. |
sim_persona.py |
Test stand-in for a long-running assistant session. |
prep_wake_samples.py |
Converts wake-word recordings into training clips. |
config.json |
Committed defaults for wake word, model choices, and backend selection. |
config.local.example.json |
Template for gitignored local bridge settings. |
requirements.txt |
Python dependencies for the CPU-only speech path. |
docs/ |
GitHub Pages site and recording notes. |
models/, voices/, whisper_models/ |
Local model directories; generated or downloaded files stay out of git. |
test_*.py |
Mic-free tests for the bridge, dispatch logic, sample prep, and pipeline. |
The project is developed on Linux/ARM64 with Python 3.13 on a Raspberry Pi 5. Other Linux hosts should work if the same dependencies are available.
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m piper.download_voices en_US-lessac-medium --download-dir voicesfaster-whisper downloads its model into whisper_models/ on first use. Piper voices live in voices/. Custom wake-word models live in models/. These files are local artifacts and are not committed.
List available wake words:
.venv/bin/python pipeline.py --list-wake-wordsSwitch the active wake word and persist it to config.json:
.venv/bin/python pipeline.py --set-wake-word hey_jarvisUse --local to persist the choice to the gitignored config.local.json
instead, so a deployment can activate its own wake word (often a gitignored
computah model) without dirtying the tracked config.json a fresh clone
runs with:
.venv/bin/python pipeline.py --set-wake-word computah --localconfig.local.json overrides config.json at runtime, so the local choice
wins while the committed default stays a built-in.
Run the pipeline on a wav file:
.venv/bin/python pipeline.py clip.wav -o reply.wavThe output path receives the spoken reply.
config.json contains safe defaults that can be committed:
| Key | Meaning |
|---|---|
wake_word |
Active openWakeWord model name. |
wake_threshold |
Detection score required before the pipeline continues. |
whisper_model |
faster-whisper model size or path. |
whisper_compute |
CTranslate2 compute type, usually int8 on the target device. |
stt_confidence_guard |
When true, a live turn drops a low-confidence transcript before the brain and speaks a re-prompt. |
stt_min_avg_logprob |
Floor for the transcript's mean per-token log-probability; below it the turn is rejected. This is the gate. |
stt_max_no_speech_prob |
How silence-like the audio looked. Combined with a low avg_logprob it labels a reject as silence; following faster-whisper's own rule, a confident decode is never rejected for this alone. |
voice_model |
Piper voice name/stem; speak loads voices/<voice_model>.onnx. |
brain_backend |
cli for standalone fallback or bridge for the persistent session path. |
claude_model |
Model name for the fallback CLI brain. |
claude_timeout_s |
Timeout for the fallback CLI brain. |
Use config.local.json for machine-specific bridge values. It is gitignored and merged over config.json, so private hostnames, usernames, and assistant paths do not leak into commits.
Built-in openWakeWord phrases appear in --list-wake-words. To add a custom wake word, place a trained <name>.onnx model in models/. It appears as <name> and can be selected the same way as a built-in model:
.venv/bin/python pipeline.py --set-wake-word <name>A custom model overrides a built-in model with the same name. Keep custom model files local unless you intend to publish them.
Start with the fast tests. They do not load speech models:
.venv/bin/python test_brain_bridge.py
.venv/bin/python test_brain_dispatch.py
.venv/bin/python test_confidence_guard.py
.venv/bin/python test_prep_wake_samples.pyRun model-dependent tests when the voice and Whisper models are present:
.venv/bin/python test_pipeline_bridge.py
.venv/bin/python test_pipeline.pyOn a memory-constrained host, cap the full bridge test:
systemd-run --user --scope -p MemoryMax=1500M -p MemorySwapMax=0 \
.venv/bin/python test_pipeline_bridge.pyMeasured on a Raspberry Pi 5 with warm models and a simulated brain:
| Stage | Approximate time |
|---|---|
| Wake detection | 0.8 s |
| Speech-to-text | 3.3 s |
| Text-to-speech | 3.9 s |
The largest known cost is speak(): it shells out to Piper for each reply, so the voice model reloads every turn. Keeping Piper resident is the likely next latency win.
- Train and ship a custom
computahopenWakeWord model. - Add a live microphone loop with endpointing and playback.
- Keep Piper loaded between turns.
- Exercise the bridge against a live assistant session over the network.
- Explore a small wake-word satellite that streams audio only after detection.
The bridge correlates replies by position. It returns the next reply block after the transcript is sent. If one turn times out and its late reply arrives during the next turn, that reply can be misattributed. Voice turns are serialized, which lowers the risk, but the reply format needs an explicit correlation key before this is fully solved.
Keep changes small and tested:
- Read
CLAUDE.mdbefore changing architecture or public docs. - Use sentence case for headings and user-facing text.
- Avoid filler and hype; say what the project does in plain language.
- Update docs when behavior changes.
- Run the fastest relevant test first, then broader tests when local models are available.
- Keep generated models, voices, local config, and assistant session data out of git.
The site in docs/ is ready for GitHub Pages. In repository settings, deploy Pages from the docs folder on the current branch. The page includes an SVG favicon and PNG social preview metadata.
MIT. See LICENSE.