-
-
Notifications
You must be signed in to change notification settings - Fork 20
Wake Word Engines
Ava has two on-device wake word engines. Both run entirely locally — no audio leaves the device for wake detection. Compatible with Android 5-16.
0.7.0+ replaced the legacy vsWakeWord engine with OpenWakeWord — a native C++ streaming engine using a shared 96-dimensional acoustic embedding and lightweight ONNX keyword classifiers. 0.7.3 added Chorus Wake multi-device arbitration, graded WebView performance scheduling, and improved timer/announce handling. See Chorus Wake for multi-device wake arbitration.
The two engines are independent architectures, not two encodings of the same graph.
microWakeWord (default) uses TensorFlow Lite binary classifiers. Models are uint8 quantized at training time — typically 50–80KB, cheap enough to run always-on on Android 5+ devices, including 1GB RAM tablets.
OpenWakeWord is a native C++ streaming engine using a shared 96-dimensional acoustic embedding model and lightweight ONNX keyword classifiers. Feature extraction (melspectrogram), embedding inference, and keyword scoring all run in C++ on CPU. The engine is stateful and streams audio in 40 ms chunks (640 samples at 16 kHz). Model files are id.json (manifest) + id.onnx (float32 ONNX keyword classifier).
The engine runs inside a foreground service: the screen can be off, no browser is required, and detection continues in kiosk mode on Android 5+.
| Dimension | microWakeWord | OpenWakeWord |
|---|---|---|
| Architecture | TFLite uint8 binary classification | Native C++ streaming: shared embedding + ONNX keyword classifiers |
| Model size | 50–80KB, uint8 quantized at training | ~500KB–2MB keyword .onnx + shared embedding model |
| Inference | 10ms frame, stride 3 | 40ms chunks (640 samples), alternating A/B embedding phases |
| Frontend | microfeatures (TFLite melspectrogram) | Native C++ melspectrogram (32 mel bins, 76-sample window) |
| Shared embedding | No — each model is independent | Yes — all keywords share one 96-dim embedding model |
| Decision | 5-frame sliding window mean > threshold | Consecutive-window confirmation (default 2 hits) + per-keyword cooldown |
| Output | Scalar 0–1 probability | Scalar 0–1 score per keyword |
| Wake-word swap | Full retraining required | ONNX model hot-swap — drop in a new keyword classifier |
| CPU / memory | Minimal (always-on, by design) | Moderate; embedding computed once for all keywords |
| False wake defense | Threshold + dual-stage verification | Threshold + consecutive-hit gate + neural VAD + optional built-in verifier |
| Built-in models | 9 micro (.tflite) |
1 open (.onnx: ok_nabu) + shared embedding model |
| Best for | Low-end Android 5+ persistent background | Multi-keyword, noise-robust, community model ecosystem |
1. Stop-word detection is not always-on, and is not a model. Stop detection runs only when it is useful: when a timer alarm is ringing, or when a voice session is in progress (Listening / Processing / Responding). During idle standby it is skipped entirely. It is also no longer a TFLite classifier — Ava uses its own model-free native DSP detector (see Stop Words) whose per-frame cost is a tiny fraction of a model inference.
2. OpenWakeWord compute gate. After an adaptive 150–250 ms silence run, expensive native inference pauses. Confident silence closes quickly; VAD values near the speech boundary get more time. The gate reopens the moment voice-like audio returns.
3. Neural VAD gate. A TFLite MicroVad model runs before wake scoring. The native engine's only speech check is an RMS energy lookback, which passes any loud non-speech (TV, music, machinery). The VAD gate never blocks scoring — it only suppresses a wake the classifier already accepted, and it fails open if the model is unavailable.
4. Alternating A/B embedding phases. The native scheduler evaluates the two embedding phases on alternating 40 ms ticks, halving per-chunk compute. This is approximately an 86% reduction versus repeated full-window convolution work.
5. Shared embedding architecture. All active keywords share one 96-dimensional embedding model. Adding a keyword does not add a new full inference path — it adds a tiny classifier head that reads the shared embedding. 1 keyword or 8 keywords, the embedding runs once.
Net effect: In idle standby, wake-word CPU stays low because stop-word inference is skipped and OpenWakeWord's compute gate pauses during silence. When someone speaks, the VAD gate and consecutive-hit confirmation prevent false wakes while the shared embedding keeps multi-keyword cost flat.
microWakeWord is the default engine. It uses TensorFlow Lite with tiny uint8 models quantized at training time. Each wake word is a separate .tflite model file paired with a .json config. The detector runs a sliding window average over the last 5 frames and triggers when the average probability exceeds the cutoff.
Built-in micro models (9):
| Model ID | Wake Word | Author |
|---|---|---|
hey_jarvis |
Hey Jarvis | Kevin Ahrendt |
alexa |
Alexa | Kevin Ahrendt |
hey_home_assistant |
Hey Home Assistant | Michael Hansen |
hey_mycroft |
Hey Mycroft | Kevin Ahrendt |
hey_luna |
Hey Luna | adamlonsdale |
hey_peppa_pig |
Hey Peppa Pig | Michael Hansen |
okay_computer |
Okay Computer | Michael Hansen |
okay_nabu |
OK Nabu | Kevin Ahrendt |
choo_choo_homie |
Choo Choo Homie | Michael Hansen |
Stop word: "stop" — detected by Ava's own model-free native DSP detector (no TFLite model), independent of the wake-word engine.
microWakeWord performs a fast second verification over the captured wake phrase. A chance confidence spike is no longer enough: the same model must confirm the contiguous audio sample at a slightly stronger threshold before Ava opens the assistant. This dual-stage path specifically reduces false wakes caused by television and background conversation while preserving the existing voiceprint check when voiceprint protection is enabled.
OpenWakeWord loads float32 .onnx keyword classifier graphs that read from a shared 96-dimensional acoustic embedding model. The entire pipeline — melspectrogram extraction, embedding inference, keyword scoring, consecutive-hit confirmation, cooldown — runs in native C++.
Audio (16 kHz, 40 ms chunks)
↓
Native C++ melspectrogram (32 mel bins, 76-sample window)
↓
Shared embedding model (ONNX, 96-dim output, 16-frame window)
↓ Alternating A/B phases per 40 ms tick
Keyword classifier 1 (ONNX) → score
Keyword classifier 2 (ONNX) → score
...
↓
Consecutive-hit gate (default 2) + per-keyword cooldown
↓
Neural VAD gate (suppresses non-speech false wakes)
↓
Wake dispatched
All keywords share one acoustic embedding model. Adding a keyword does not add a new full inference path — it adds a tiny classifier head that reads the shared embedding. 1 keyword or 8 keywords, the embedding runs once.
The embedding model is a float32 ONNX graph that takes a short window of melspectrogram frames and produces a compact acoustic fingerprint. The melspectrogram is computed in native C++ — no separate melspectrogram model is shipped. Classifier tensor names carry a per-model hash suffix; the engine binds by index, not by name, so models from different training runs all load correctly.
| Model ID | Wake Word | Author |
|---|---|---|
ok_nabu |
OK Nabu | Home Assistant community, collected by Florian Wartner (fwartner) |
Source: fwartner/home-assistant-wakewords-collection (MIT license)
Each keyword model is a pair: id.json (manifest) + id.onnx (float32 ONNX classifier). The manifest defines the keyword's threshold, confirmation gate, and cooldown.
Key manifest fields:
| Field | Default | Description |
|---|---|---|
format |
openwakeword-v1 |
Manifest format version |
id |
— | Keyword identifier |
wake_word |
— | Human-readable wake word |
model |
id.onnx |
ONNX model filename |
openwakeword.threshold |
0.5 | Trigger cutoff (0.15–0.99) |
openwakeword.required_hits |
2 | Consecutive windows above threshold to trigger |
openwakeword.cooldown_ms |
2000 | Cooldown before same keyword can trigger again |
openwakeword.sliding_window_size |
1 | Sliding window size |
built_in_verifier |
null | Model carries its own second-stage verifier (ONNX If gate) |
stop_classifier |
false | Ava rejects these (stop detection is a built-in DSP detector) |
Community ONNX models are calibrated near 0.5. On the real engine they peak around 0.84–0.91; setting the cutoff too high drops recall rapidly — 0.92 fires nothing on ok_nabu.
Ava's sensitivity slider is bounded by what the model can actually score. An out-of-reach strict request clamps down to the ceiling, never falls back to the manifest default. This prevents a leftover high sensitivity value from a different engine from silently making the detector deaf.
Consecutive-hit gate: Every false positive observed in extended non-wake speech testing was a single-frame spike. Every genuine positive held multiple consecutive frames above threshold. Two consecutive hits reject every observed spike while preserving recall — a single-frame classifier spike can no longer trigger a wake.
Built-in verifier exemption: Models that carry their own second-stage verifier (an ONNX If gating a verifier network, e.g. official hey_jarvis) emit a single-frame spike per utterance by design — the verifier already did the debouncing. For them the manifest gate runs as written, because the verifier holds every genuine negative well below threshold.
Ava can burst-score the playback reference (echo) with the same keyword configs as the live engine. This offline pass runs in a separate engine instance with its own lock — never the live detect path — so a ~100–400 ms burst pass cannot stall live detection mid-playback. When the echo itself triggers the keyword, the wake is suppressed.
Saying "stop" interrupts the current conversation or stops Ava's response (e.g., timer alarm).
The bundled stop.tflite classifier was removed because it produced false triggers; it is replaced by Ava's own model-free native DSP detector. It matches the phonetic time structure of an isolated spoken "stop" — the specific sequence of fricative, closure, vowel, and abrupt ending that distinguishes "stop" from hisses, isolated vowels, and running speech.
All energy thresholds are relative to an adaptive noise floor, so detection is microphone-gain invariant. A quiet room produces zero false triggers — something the old classifier could not guarantee.
Stop detection only runs when actually needed: when a timer alarm is actively ringing, or when a voice session is in progress (Listening, Processing, Responding). During idle standby with no alarm, stop detection is skipped — cutting CPU load and heat.
The stop word is now configurable in Voice Configuration:
| Option | Description |
|---|---|
| Built-in DSP "Stop" | Default — no model, no sensitivity slider, zero inference cost |
| Any wake-engine model | Use any model from the current wake engine as the stop word, with its own sensitivity |
| Off | Disable stop detection entirely |
Path: Settings → Voice Config → Stop word
When a Home Assistant timer finishes, Ava enters timer-ringing mode:
- Ava ducks media audio and plays a timer-finished chime
- Stop-word detection activates (it is normally off during idle)
- Saying "stop" stops the timer ring
- The chime cycle is designed so the quiet gap between chimes is wide enough for a reactive "stop" to land in the quiet part — regardless of echo level
- When the timer is cancelled or stopped, media audio un-ducks and stop detection returns to its normal gating
Timer STARTED/UPDATED/CANCELLED events from HA do not affect the ring — HA drops a timer from its registry the moment it fires, so a cancel can only refer to a still-pending timer, never the one ringing.
Each engine stores wake words independently (microWakeWords / openWakeWords). Switching engines auto-restores the last selection — no more lost models or silent failures. Cross-engine ID mapping: micro's okay_nabu auto-maps to open's ok_nabu. HA-configured wake words also resolve correctly across engines. Service auto-restarts on engine switch, keeping the detector in sync with settings.
Legacy migration: Settings previously stored as vsWakeWords are automatically read into openWakeWords. The serializer maps VS_WAKE_WORD to OPEN_WAKE_WORD. No user action needed.
Ava ships with a built-in Wake Word Library — a complete model management system that lets you browse, download, import, and switch wake words without ever touching a desktop or editing APK files.
| Tier | What it is | Where it shows up |
|---|---|---|
| Curated catalog | Hand-picked models hosted in the Ava repository, tested and calibrated for Android | Wake Word 1/2 picker — the dropdown you use to select a wake word |
| Community catalog | Open community model collections, auto-synced from upstream sources | Wake Word Library — browse, download, then set as active |
The curated catalog is what you see in the wake-word picker. Community models live in the library — download one, then set it as your active wake word.
Ava reads a live micro_community.json from the Ava repository, which indexes the microWakeWords V3 community library — over 100 pretrained models covering dozens of wake phrases in multiple languages.
| Source | Author | Credit |
|---|---|---|
| TaterTotterson/microWakeWords | Kevin Ahrendt, Michael Hansen, and community contributors | The V3 training pipeline and community model library that Ava's micro catalog builds on |
| Bundled models | Kevin Ahrendt, Michael Hansen, adamlonslide | 9 models ship in the APK — Hey Jarvis, Alexa, Hey Mycroft, OK Nabu, Hey Home Assistant, Hey Luna, Hey Peppa Pig, Okay Computer, Choo Choo Homie |
Thanks: The microWakeWord project by Kevin Ahrendt and the V3 community library maintained by Michael Hansen (TaterTotterson) made on-device wake-word detection practical for Home Assistant. Ava builds on their work.
Ava reads a live open_community.json from the Ava repository, which indexes community-trained OpenWakeWord ONNX classifiers.
| Source | Author | Credit |
|---|---|---|
| dscripka/openWakeWord | David Scripka | The original openWakeWord project — shared acoustic embedding architecture and training pipeline |
| fwartner/home-assistant-wakewords-collection | Florian Wartner | Home Assistant community wake-word collection, including the bundled ok_nabu model |
| Bundled model | Florian Wartner (fwartner) |
ok_nabu ships in the APK — the only OpenWakeWord model bundled by default |
Thanks: David Scripka created the openWakeWord architecture. Florian Wartner collected and trained the Home Assistant community models. Ava's OpenWakeWord engine is built on their work.
The upstream openWakeWord project runs on desktop Python. Ava ported the entire inference path to native C++ on Android and added several layers that the upstream does not have:
| Capability | Upstream openWakeWord | Ava Pro |
|---|---|---|
| Runtime | Python on desktop | Native C++ on Android 5+ |
| VAD compute gate | No | Neural VAD gates the embedding — silent frames skip ~96% of compute |
| Echo self-trigger protection | No | Offline burst-scores playback reference; suppresses wake when the speaker itself triggers the keyword |
| Consecutive-hit gate | Single threshold | Requires consecutive frames above threshold — rejects single-frame spikes that a raw threshold cannot |
| Built-in verifier support | Partial | Detects models with their own second-stage verifier and adjusts the hit gate accordingly |
| Glue-rescue head | No | Optional sidecar classifier that rescues wake words spoken in one breath with the command |
| Adaptive idle scheduling | No | After confirmed silence, expensive inference pauses within 150–250 ms — idle CPU drops to near zero |
| Multi-keyword efficiency | One embedding pass per chunk | Same — 1 keyword or 8 keywords, the embedding runs once |
| Mirror racing for downloads | No | zh/ru locales race GitHub proxy mirrors concurrently; first success wins and sticks for subsequent downloads |
| SHA-256 verification | No | Every downloaded model is SHA-256 verified against the catalog entry |
Imagine a user named Riley who tried running upstream openWakeWord on a Raspberry Pi: it works, but it needs Python, a virtual environment, and constant CPU. On Ava, the same models run on a $30 Android tablet from 2018, with VAD gating, echo protection, and idle scheduling — things the upstream simply does not offer.
Beyond the catalogs, you can import your own trained models directly:
-
microWakeWord:
.tflite+.jsonpair (V2/V3 format) -
OpenWakeWord:
.onnx+.jsonpair (openwakeword-v1format), or just an.onnxfile (Ava auto-generates the manifest) - ZIP import: Pack multiple models in one ZIP — Ava unpacks and installs them in batch
See Custom Wake Words for the full import walkthrough.
Path: Settings → Voice Config → Wake Word Library
- Open Ava app
- Go to Settings -> Voice Config
- Find Wake Word Engine and choose microWakeWord or OpenWakeWord
- Find Wake Word 1 option
- Select your preferred wake word from the list
- Optionally configure Wake Word 2 for dual wake word mode
- New wake word takes effect after service restart (auto)
Adjust sensitivity to control how easily the wake word triggers:
- Higher sensitivity = easier to trigger, but more false positives
- Lower sensitivity = fewer false positives, but may miss quiet speech
OpenWakeWord sensitivity range: The slider is bounded by the model's calibration. For ok_nabu (manifest threshold 0.5), the slider ranges from 0.15 to 0.80. Above 0.80 the classifier no longer has headroom — ok_nabu measured 6/8 at 0.80, 5/8 at 0.87, 0/8 at 0.92. An out-of-reach strict request clamps down to the ceiling, never falls back to the manifest default.
Each wake word can have its own wake sound:
- Wake Word 1 Sound: Played when Wake Word 1 is detected
- Wake Word 2 Sound: Played when Wake Word 2 is detected
- Default Sound: Used if no custom sound is set
- None: Silent recording start
Ava provides clear visual feedback during wake and conversation:
Wake Instant:
- Colorful ripple expanding from screen center
- Android 13+: RuntimeShader with distorted halo + star particles
- Android 7: Soft circular diffusion
- Compatibility paths for other versions
Conversation (when Floating Subtitle is disabled):
- Full-screen edge glow that changes with state:
- Listening: Edge light breathes with microphone volume
- Processing: Slow breathing animation
- Speaking: Pulsates with TTS energy
Dual Wake-Word Color Coding:
- Wake Word 1 = green (default), Wake Word 2 = blue (default)
- Ripple and edge light match the triggered wake word
- Custom colors available in Settings → Extensions → Interface → Voice feedback colors
- 7 rainbow presets (red through purple) also available
Technical Notes:
- Edge glow uses pre-rendered Gaussian blur bitmaps for performance
- Ripple animation driven by system uptime (prevents Kiosk devices with "animation duration = 0" from killing the effect)
- Android 7.0/7.1 optimized to clean circular diffusion without Shader dependency
Three improvements that make wake feel instant and never drops a sentence.
The moment a wake fires, Ava starts buffering microphone frames — before the wake sound plays, before Chorus Wake arbitration, before the uplink opens. When the uplink finally opens, those buffered frames are spliced to the queue head. Home Assistant hears your voice as the first frame, not the wake-sound tail.
Imagine a user named Jordan who says "Hey Nabu, turn on the kitchen lights" in one breath. Without pre-roll, HA would miss "turn on the kitchen lights" because the uplink wasn't open yet. With pre-roll, the entire phrase is captured and delivered.
In continuous conversation mode, after TTS finishes a continue chime plays and the satellite re-enters listening with the same HA conversation ID — no wake word needed. You can issue command after command in a natural flow.
When the assistant's response text contains the wake phrase (e.g. "I've set the Nabu timer"), wake detection is held for the estimated TTS duration so the assistant cannot wake itself. Wake and stop channels are separated — a response mentioning the stop phrase does not lock wake, and a response mentioning the wake phrase does not lock stop.
Ava's wake-word engines were benchmarked against a deliberately difficult stress corpus and Sherpa-ONNX (a third-party ONNX speech recognition toolkit).
| Property | Value |
|---|---|
| Total clips | 1,056 |
| Positive clips | 864 |
| Negative clips | 192 |
| Speakers | 12 |
| Locales | 11 |
| Speech styles | Isolated and glued |
| Noise conditions | Clean, pink, babble, brown, white, room, room+pink, narrowband |
| Noise levels | ~5–20 dB SNR |
| Engine / Configuration | Recall | False-fire rate |
|---|---|---|
microWakeWord default (c=0.79, w=5) |
38.3% | 2.08% |
microWakeWord sensitive (c=0.50, w=1) |
56.7% | 7.29% |
| OpenWakeWord default | 24.0% | 6.25% |
| OpenWakeWord strict | 15.3% | 1.56% |
| Sherpa-ONNX | 4.3% | 0.00% |
| Condition | microWakeWord | OpenWakeWord | Sherpa-ONNX |
|---|---|---|---|
| Clean isolated | 94.4% | 38.9% | 19.4% |
| Noisy isolated | 56.6% | 26.8% | 3.0% |
| Glued speech | 16.9% | 20.1% | 4.2% |
| Isolated speech | 59.7% | 27.8% | 4.4% |
OpenWakeWord's shared embedding architecture keeps per-keyword cost flat. On a modern host, a single keyword processes a 40 ms chunk in under 1 ms — roughly 85× realtime. Adding more keywords adds only the tiny classifier head cost, not another full inference path. Even with 8 active keywords, the engine stays well above 15× realtime.
Caution: These are repository-host and stress-corpus measurements, not universal Android-device guarantees. The corpus is deliberately difficult. Sherpa-ONNX's zero false-fire rate reflects the selected threshold/test setup, not a universal statement about the entire Sherpa-ONNX ecosystem.
Back to Voice Control
- Quick-Start
- System-Requirements
- Voice-Control
- Chorus-Wake
- ESPHome-Encryption
- HA-Direct-Connection
- Browser
- Screensaver
- Floating-Windows
- Home-Launcher
- Home-Screen-Widgets
- Notification-Scenes
- Quick-Entity
- Sensors
- Backup
- Sendspin
- Bluetooth
- Voice-Messages-Calls
- Music-Playback
- Camera
- Screen-Control
- Intent-Launcher
- ADB-Commands
- Mod-Store
- Ava-Fleet