YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Luuna Emotional Voice Engine
One identity, 27 emotions, continuous prosody, streaming, interruptible.
Luuna is a persistent AI companion. This engine decides how she speaks (semantic meaning + context + emotional state + previous state + urgency + punctuation), plans it as structured control data, and executes it on a swappable local TTS stack. It runs fully offline on one RTX 3090 24 GB; cloud APIs are optional adapters only.
Conversation Context
↓
LUUNA Emotion Director (rules + optional LLM via Model Router)
↓
Prosody / Emotion Plan {emotion, intensity, VAD, pace, pitch, energy, ...}
↓
Text Segmentation [pause] markers, soft→strong intra-utterance arcs
↓
Expressive TTS Engine adapters: chatterbox / voxcpm / xtts (swap freely)
↓
Audio Post Processor pace/pitch/energy/breathiness + loudness
↓
LUUNA Voice Output streaming chunks, crossfade-stitched, interruptible
Emotion state machine
- per-session temporal memory: previous state, target state, VAD, timestamps
- smooth transitions (distance-weighted, eased); same-family moves settle fast
- idle decay toward neutral (~90 s)
- emotion persistence: neutral text after a sad utterance stays in the sad family (state_blend), it does not snap to neutral
- intra-sentence performance:
"Tom... ma ei tea. [pause_500] See ei tundu..."renders segment 1 soft/low-energy, 500 ms pause, segment 2 with full weight - explicit instructions ALWAYS override inference
LUUNA VOICE V1 — verified state (2026-09-14)
GPU smoke on a10g-small (24 GB class): 8 Estonian emotions, all green.
Samples: samples/gpu-smoke/ · raw numbers:
samples/gpu-smoke/benchmark.json
| Metric | Value |
|---|---|
| Model | ResembleAI/chatterbox (multilingual), chatterbox-tts==0.1.7 |
| Model load | 34.6 s |
| VRAM peak | 3.41 GB (fits RTX 3090 with huge headroom) |
| Latency | 2.6–5.4 s per multi-sentence utterance (RTF 0.80–0.91) |
| Prosody control | f0 median varies by emotion: happy 228 Hz, sad 112 Hz, neutral 140 Hz |
| Estonian | synthesized via grapheme fallback (language_id=en); et not in the model's language list |
8 sample files (same voice identity across all): neutral, happy, sad, angry, whisper, excited, concerned, sarcastic — the concerned sample includes the
[pause_500] intra-sentence marker from Luuna's real usage.
CPU test suite: 29/29 green (pytest exit 0, commit 12f4f44).
Canonical voice anchor (the "this is Luuna" identity)
Luuna's identity comes from ONE neutral reference clip — emotional references bleed their mood into the performance, so the anchor must be calm speech:
- Place your chosen clip at
C:\Luuna\automation\voice-io\references\luuna_anchor.wav(10–20 s, clean mono ≥ 24 kHz, neutral delivery) - Either set it in
config/luuna.yamlunderengine.reference_voice, or copy it tomodels\reference\luuna-neutral.wav(auto-detected), orPOST /v1/luuna/voice/reference(multipart upload) - All 27 emotions render through that one anchor — the model modifies performance, not the speaker
Install (Windows 11, RTX 3090)
git clone <this-repo> ; cd luuna-emotional-voice
powershell -ExecutionPolicy Bypass -File scripts\install.ps1
# put Luuna's NEUTRAL reference clip at models\reference\luuna-neutral.wav
Run
# API server on 127.0.0.1:7890
powershell -ExecutionPolicy Bypass -File scripts\run_server.ps1
# Demo UI on 127.0.0.1:7891
powershell -ExecutionPolicy Bypass -File scripts\run_demo.ps1
# tests (CPU, no model downloads)
powershell -ExecutionPolicy Bypass -File scripts\run_tests.ps1
API
POST /v1/luuna/speak {text, session_id, language, emotion, intensity, stream}
POST /v1/luuna/emotion/analyze -> performance plan
GET /v1/luuna/emotion/state/{session_id} -> emotional memory snapshot
POST /v1/luuna/emotion/state/{session_id} -> manual state override
POST /v1/luuna/voice/reference -> upload reference voice (multipart)
POST /v1/luuna/speak/{session_id}/cancel -> interrupt
WS /v1/luuna/speak/ws -> streamed base64 PCM chunks
GET /health
Example:
POST /v1/luuna/speak
{ "text": "Tere Tom...", "session_id": "tom-main", "language": "et",
"emotion": "auto", "stream": true }
Streamed PCM starts with a header line LUUNAPCM1 24000 int16\n, then raw
16-bit mono PCM frames per chunk.
Estonian path
No open TTS ships Estonian (verified 2026-09 against official language
lists — Chatterbox multilingual: et raises "Unsupported language_id";
grapheme fallback renders õ/ä/ö/ü but rhythm is approximated from English).
So:
- EKKK (Estonian Emotional Speech Corpus, 1 female speaker, anger/joy/sadness/neutral, CC-BY) → Luuna's canonical voice + emotion labels
- Common Voice et (51.7 h, CC-0) → general Estonian fluency
- Fine-tune with
training/finetune.py(VoxCPM2 official LoRA trainer) - Emotion conditioning via inline
[emotion]tags — never from the reference audio (prevents reference-emotion bleeding)
See docs/ for the full model comparison, dataset spec and training runbook.
License notes
- Chatterbox: MIT. VoxCPM2: Apache-2.0. XTTS-v2: CPML (non-commercial, fallback/prototype only). Common Voice: CC-0. FLEURS: CC-BY. EKKK: CC-BY.