#Voices
How narration works day to day — picking narrators, adding more, and what the free vs. premium tiers actually mean.
#The two tiers
Free, on-device (Kokoro) — 28 open voices, synthesized on your own machine when you publish. No account, no API key, no per-character billing. This is the default and what most brands should start with.
Premium, cloud (Azure Speech) — optional, bring-your-own-key. Set
AZURE_SPEECH_KEY / AZURE_SPEECH_REGION and use an Azure voice id in
brand.json's tts.voice. Costs per character synthesized.
Mixing is fine — your primary narrator can be Kokoro while an additional voice is Azure, or vice versa; the pipeline handles either per voice id.
#Picking your brand's narrator
deployment/<id>/deployment.json — TTS config is per-install, not part of the
portable brand (see build-your-own-theme.md):
"tts": {
"voice": "bm_george",
"rate": "0%",
"outputFormat": "Audio48Khz96KBitRateMonoMp3"
}
voice is the primary narrator every chapter gets by default. Some example
Kokoro voice ids (all 28 follow the <accent><gender>_<name> pattern —
a = American, b = British, f/m = gender):
| id | Name |
|---|---|
af_heart |
Heart — American, female |
af_bella |
Bella — American, female |
am_adam |
Adam — American, male |
am_michael |
Michael — American, male |
bf_emma |
Emma — British, female |
bm_george |
George — British, male |
bm_fable |
Fable — British, male |
#Adding more narrators (the picker)
Publish more than one voice and a "Narrator" picker appears in the app's Settings automatically — nothing to build, it's driven entirely by what the manifest says is available:
"tts": {
"voice": "af_heart",
"voices": ["bm_george", "am_adam"]
}
voice stays the library default (what plays if a reader never picks);
voices lists the extras. The publish pipeline synthesizes every listed
voice for every chapter — expect proportionally longer publish times
with more voices. A reader's chosen narrator syncs across their devices and
stays available offline in their downloads.
#Cost and time expectations
Kokoro voices run locally during publish.mjs — the cost is your own
machine's time (roughly real-time-ish per chapter per voice on a modern
CPU; a GPU speeds this up substantially). Azure Speech bills per character
synthesized — check current Azure Speech pricing before publishing a large
library with it. Either way, --no-audio skips narration entirely for a
text-only publish (listen mode falls back to on-device Web Speech).
#Regenerating narration
Editing a chapter's text and re-publishing automatically re-synthesizes
just that chapter (content-hash diffing — unchanged chapters are never
re-narrated). To force a full re-synthesis of everything without changing
the text, see the pipeline's state file at .storylark/state/<brand>.json
— deleting a chapter's entry there forces a re-publish of just that
chapter. Full mechanics: content-pipeline.md.
Found a gap? StoryLark is open source — improve these docs on GitHub.