Skip to content

Voice Studio

ProNewv1.2.0

Turn text into natural speech with a palette of on-device voices

Voice Studio is a Pro app

It needs a free KimtOS account and a Pro subscription. Everything still runs on your device — Pro unlocks the app, it does not send your data anywhere. Install it without Pro and the app opens to an upgrade wall. See what Pro includes

Overview

Voice Studio turns written text into spoken audio. Pick one of 28 voices, type or paste anything from a single word to a whole article, and the app reads it back in a clip you can play, keep and export as a WAV file. It is part of the kimtos new-tab app suite, and it is a Pro app.

Everything happens on your device. The speech model runs inside your browser, so the text you type and the audio it produces never leave the machine — there is no server doing the synthesis, and nothing to upload. After the one-time model download the app works with no network at all.

Highlights

  • A picker of 28 voice cards — US and UK, female and male, each a distinct speaker built into the model, shown as a card with its accent, gender and quality grade. Search by name and filter by accent, gender or the top picks.
  • Play a sample before you commit — every card has a play button that speaks a short sample line in that voice. Auditioning does not change your selection, does not block anything else you are doing, and each sample is kept, so hearing a voice a second time is instant and works offline.
  • Settings that stick — your voice, speed and whether every clip is saved automatically are app settings, reachable from the settings control in the window's title bar or from Settings ▸ Apps. They survive closing the window.
  • Long-form narration — paste an entire article. The text is split at sentence boundaries, spoken piece by piece, and stitched back into one continuous clip with no audible seams.
  • Speed control — slow a passage down to 0.5x or push it to 2x, in 0.1x steps.
  • Play, save, export — every render gets a player, a Download WAV button and a Save to clips button.
  • Saved clips — kept in a history sidebar with the text that produced them, so you can reopen a clip, replay it, or download it again later.
  • Multi-select delete — switch the sidebar into select mode (or drag across rows) to remove several clips at once, with one-tap undo.
  • Cancel a long render — a Cancel button stops narration between chunks rather than leaving you waiting out a mistake.
  • Agent tools (declared, not active) — Voice Studio declares tools to list voices, speak text, list clips and delete a clip. No on-device model shipped today can drive them, so they do not run; they are there for when one can.

Privacy model

Voice Studio makes exactly one kind of network request, and it is not about your text:

  • Synthesis runs in your browser. The Kokoro-82M speech model executes locally on WebAssembly, in a background worker. Your text is never sent anywhere to be spoken.
  • The model downloads once (~90 MB) from the kimtos content host, along with a small style file (~0.5 MB) for each voice you actually use. Both are cached by the browser, so later sessions work offline.
  • Your clips stay local. Saved audio and the text beside it live in your browser's local storage on this machine. There is no sync, no account data in the clip, and no third-party call.

Being a Pro app, Voice Studio does require a signed-in Pro account to install — that check is about entitlement, not about your content, which never leaves the device either way.

What it is good at, and what it is not

Kokoro-82M is a small model, and the honest picture is worth having up front:

  • English only. The model's repository carries voices for other languages, but they were trained against a different pronunciation frontend from the one the extension runs, so they would be mispronounced. Only the English voices ship.
  • Voices differ in quality. The model's authors graded their own speakers, and those grades are on every card: the A and B voices are noticeably cleaner than the D and F ones. The four strongest are one filter away, under Top picks.
  • Pronunciation is rule-based. Unusual names, invented words and heavy jargon can come out wrong. Speaking a single word is the fastest way to check one.
  • It is a reading voice, not an acting one. There is no emotion, emphasis or pause control beyond speed — the model exposes speed and nothing else, and the app deliberately does not invent controls the model cannot honour.

Getting Started

Open Voice Studio from the dock. You land in the studio: a voice bar at the top naming the voice you are about to use, the text box under it, and the saved-clip sidebar on the left. As a Pro app, Voice Studio shows an upgrade wall for free users before the app itself opens.

Pick a voice

The voice bar shows the current voice with its accent, gender and quality grade, a Preview button to hear it, and Change voice to open the picker.

The picker is a grid of voice cards. Each card carries the voice's initial, its name, its accent and gender, and the model authors' quality grade. Above the grid are a search box and three sets of filters — accent (US / UK), voice (Female / Male), and Top picks, the four strongest voices: Heart, Michael, Emma and George. Heart is the default.

Each card does two separate things, on purpose:

  • The card itself selects that voice and closes the picker.
  • The play button speaks a short sample line in that voice ("Hi, I'm Kore, one of the voices in Voice Studio on Kimt OS. This is a short preview, so you can hear how I sound before you press Speak."). Listening is not choosing: you can audition as many voices as you like without touching your selection, and press the button again to stop.

Auditioning never blocks the app — Speak stays live, and starting another sample cancels the one playing.

A voice has to be downloaded before it can speak

A voice you have never played is not on your machine yet, and the card says so: it reads Not downloaded, and its control is a download button rather than a play one. Pressing it fetches what is missing and then plays the sample, with a progress bar the whole way.

What gets fetched, and once only:

  • The speech model, about 90 MB — shared by every voice, so only the first voice you play pays for it. (Installing Voice Studio usually fetches it in the background, so in practice it is already there.)
  • That voice's own file, about 0.5 MB — per voice, the first time you use it.

The picker states this above the grid, so you know before you press anything. The rendered sample is then kept, so playing that voice again is instant and needs no network at all.

Fetch as many as you like at once. Starting a second voice does not cancel the first: each card runs its own download and shows its own progress, and pressing a card that is working cancels just that one. The files are fetched in parallel; the speech itself is rendered one voice at a time, because the model is a single on-device engine — a card waiting its turn says Queued. When they land, the voice that plays is the one you asked for last, so several finishing together never talk over each other.

If your browser refuses to start the audio on its own — some do, when playback begins seconds after the click that asked for it — the app says so and offers a Play button in the message. Pressing that plays it immediately.

Settings

Voice Studio has three settings, in the settings control in the window's title bar (and in Settings ▸ Apps):

  • Voice — the voice the studio starts with. Picking a voice in the app writes this too, so your choice survives closing the window.
  • Speaking speed — the same 0.5x-to-2x value as the studio's slider.
  • Save every clip — off by default. Turn it on and each finished clip is filed in the sidebar automatically, with no Save to clips step.

Speak some text

Type or paste into the text box. A character count sits below it, and Speak lights up as soon as there is something to say.

Press Speak and a progress bar appears. On the very first run it shows the model download (The voice model downloads once (~90 MB) and is then cached for offline use.); after that it goes straight to narrating. Long text reports progress chunk by chunk, so you can see a big article moving.

Cancel appears while a render is running and stops it at the next chunk boundary — a mistyped article does not have to be waited out.

Adjust the speed

The Speed slider runs from 0.5x to 2x in 0.1x steps, with the current value shown beside it. It applies to the next render; it does not change a clip you have already made, and it is remembered for next time. Speed is the one delivery control the model genuinely exposes, so it is the only one the app offers.

The result

A finished render appears below the composer with the voice name, the clip size, and a player. Two buttons sit under it:

  • Download WAV — saves the clip to your computer as a 24 kHz mono WAV, named after the first few words of the text.
  • Save to clips — files it in the sidebar and opens it there. With Save every clip turned on the app has already done it, and the result says Saved to clips instead.

Downloading and saving are independent: a clip you download is not automatically kept, and a clip you save can be downloaded again later from the sidebar.

Long-form narration

Voice Studio is built for whole articles, not just phrases. Behind the scenes a long text is split at sentence boundaries into chunks the model can speak without truncating, each chunk is synthesised separately, and the pieces are joined with a short crossfade so the joins are inaudible. You get one clip, not a playlist, and no sentence is dropped along the way.

There is no hard limit on length, but longer text means a longer render — the work is real computation happening on your machine, and the progress bar reflects that.

Saved clips

The sidebar keeps your saved clips, newest first, labelled with the opening words of the text. From here you can:

  • Open a clip to replay it and read the text that produced it.
  • Download WAV from the row's menu, without opening it.
  • Delete several at once — click Select (or drag across rows), tick the clips you want, then Delete (N). A toast lets you undo for a few seconds.

The list keeps the 40 most recent clips; older ones are trimmed automatically as you add new ones. The sidebar's New button returns you to the composer.

The first-run download

The first render — or the first sample — downloads the speech model once, about 90 MB. Installing the app from the App Store warms this up in the background, so in practice your first render usually starts straight away.

Each voice also has its own small style file (about 0.5 MB), fetched the first time you use that voice and cached from then on. Auditioning the whole palette therefore costs a few megabytes in total, not a download per render — and each rendered sample is kept locally, so the second play of a voice needs neither.

Once everything is cached the app is fully offline. Uninstalling Voice Studio offers to delete both the cached model and your saved clips.

Architecture

Voice Studio follows the suite convention: a thin descriptor owns the UI and the storage wiring, and the expensive work — running an ONNX speech model — is a self-contained engine that lives off the main thread. The app code is in extension/src/apps/voicestudio/; the engine and its helpers are shared modules under extension/src/lib/.

Modules

FileRole
apps/voicestudio/index.tsThe app descriptor — the voice bar and picker, the composer (text, speed, Speak/Cancel), the result card, the saved-clip sidebar, the settings schema, the agent tools, and the uninstall data hooks.
components/appSettingsFields.tsThe shared settings-schema renderer. Voice Studio declares voice, speed and autosave; this turns them into controls for both Settings ▸ Apps and the window's own settings dialog.
lib/engines/voiceCatalog.tsThe voice palette as data — the 28 English voices, their accent, gender and the model authors' own quality grade, plus grouping and lookup helpers. No DOM, no engine.
lib/engines/voice.tsThe engine's main-thread half — owns one long-lived worker, correlates its messages by job id, stitches the returned chunks, and exposes synthesise / prefetch / dispose.
lib/engines/voice.worker.tsThe worker — loads the model and tokenizer once, fetches a voice's style vectors, and synthesises one chunk of text at a time.
lib/engines/voicePhonemize.tsThe grapheme-to-phoneme frontend — normalises English text (numbers, currency, abbreviations, quotes) and converts it to the phoneme string the model's tokenizer wants.
lib/voiceChunk.tsSplitting long text into synthesis chunks at sentence boundaries. Pure and unit-tested.
lib/audioAssembly.tsCrossfaded concatenation of the per-chunk audio, and WAV encoding. Pure and unit-tested.
lib/blobHistory.tsThe saved-clip history — records plus their audio blobs, with pruning and undo-friendly removal.
components/historySidebar.tsThe sidebar — clip list, per-row menu, select mode and bulk delete with undo.
components/audioPlayer.tsThe playback control used for both a fresh render and a saved clip.
components/progressBar.tsThe normalised progress display, including the first-run download hint.
lib/dataStore.tsThe local data store — clip records and their WAV blobs, in IndexedDB.

Data flow

Why a worker

Synthesis is a loop of awaited model calls, one per chunk. On the main thread that starves the event loop and freezes the whole new-tab page — the same defect already measured and fixed for the Denoise and transcription engines. So it runs in a Web Worker, and the main thread only stitches the audio the worker hands back.

The worker is long-lived: the app keeps one alive for as long as it is open, so the model and tokenizer are loaded once and previews and re-renders never reload the weights. "Once" means once even under concurrent demand: the model load caches its in-flight promise, so several voices requested together share one load rather than each starting their own (lib/asyncOnce.tssharedByKey). The fetches then run in parallel while the inference is serialised through a queue from the same module — the WASM session is single-threaded, so overlapping runs buy nothing, and a voice waiting its turn is reported as Queued rather than left looking stalled. Closing the app terminates it, which frees the memory. Per-chunk audio is transferred rather than copied across the worker boundary, so a long narration does not duplicate megabytes of samples on every message.

A worker that dies — out of memory, a blocked WebAssembly load — rejects every in-flight job rather than leaving callers waiting on promises that can never settle.

Chunking, and why it exists

The model truncates any single utterance past roughly 510 phoneme tokens, which would silently drop the tail of a long paragraph. voiceChunk.ts therefore splits the input first:

  • Sentences. Text is broken at sentence terminators, but only where the next character looks like the start of a new sentence — so "Stop!" she cried. stays one sentence rather than two.
  • Packing. Consecutive short sentences are packed together up to a conservative character budget, well under the token cap because a phoneme string runs longer than its source text. Fewer chunks means fewer model runs.
  • Over-long sentences. A single sentence past the budget is split at clause punctuation first, then on whitespace, and only as a last resort inside a word longer than the budget itself.

No text is ever dropped: the chunks concatenated back together equal the input with runs of whitespace collapsed. That property is asserted by unit tests.

Stitching, and why it crossfades

Each chunk is its own utterance, so joining them with a plain copy leaves a discontinuity at every seam — a faint but audible click, the classic failure of naive concatenation. audioAssembly.ts instead crossfades each join over a short equal-power ramp, using the near-silence that brackets every utterance, so the click disappears without clipping speech. The result is encoded as mono 16-bit PCM in a WAV container at the model's native 24 kHz.

Voices and where their bytes come from

A "voice" is not a separate model. Kokoro is a single set of weights plus a per-speaker style vector file, voices/<id>.bin, that conditions it. The worker fetches a voice file the first time that voice is used and keeps it for the session.

Both the model files and the voice files come from the kimtos content host — the same host every other on-device model in the suite is served from, resolved from the shared configuration rather than written into the engine. There is no third-party fallback: a model asset either comes from our host or fails loudly. Everything is validated on arrival (a truncated response or an error page is rejected, not cached as if it were audio data) and stored in the browser's cache, which is what makes the app work offline afterwards.

The cached bytes are attributed to Voice Studio in the suite's storage console, so the space they use is visible per-app and can be freed per-app.

From letters to sounds, and why we wrote our own

This layer is deliberately built from permissively-licensed data. The obvious off-the-shelf choice for this job is eSpeak-NG, which is what almost every Kokoro integration uses — and it is GPL-3.0-or-later, so shipping a compiled copy of it inside a proprietary paid product is a copyleft conflict whatever the licence field of the npm wrapper around it says. Voice Studio carries its own permissive path instead:

LayerWhere it comes fromLicence
Pronunciation dictionaryThe CMU Pronouncing Dictionary — 126,037 words, a 767 KB gzipped packaged assetBSD-2-Clause
Phoneme alphabet mappingWritten for KimtOS (lib/g2p/arpabet.ts)Ours
Out-of-vocabulary wordsLetter-to-sound rules written for KimtOS (lib/g2p/lts.ts)Ours
Numbers, symbols, lettersWritten for KimtOS (lib/g2p/english.ts)Ours

Both attributions are in THIRD-PARTY-NOTICES.md, as their licences require.

Every stage runs in the browser. That is not incidental — a server-side phonemiser is the easy answer to this problem, and it was rejected outright because it would mean sending the user's text off the device to decide how to say it.

Two details in the mapping are load-bearing and easy to get wrong:

  • The voiced velar stop must be U+0261 SCRIPT G, not ASCII g. The model's tokenizer vocabulary has no ASCII g, so the wrong character deletes every hard g in the clip with no error anywhere.
  • The stress mark goes immediately before the stressed vowel, not at the syllable boundary. The dictionary marks stress on vowels too, so the two line up and no syllabifier is needed.

British output is a transform of the same American dictionary: non-rhotic, with the GOAT, TRAP and LOT vowels and the yod restored (tune is tjuːn), and no flapping. It cannot carry a distinction the dictionary does not make — bath comes out with the TRAP vowel — but neither does eSpeak's own British voice, which is what the model's British speakers were trained on.

Persistence

A saved clip is one record plus one blob:

{ id, name, at, audioKey, voice, text }   // record
audioKey -> the rendered WAV               // blob

Both live in the shared local database under the app's own partition, so they appear under Voice Studio in the storage console and travel in a backup. The list is capped at the most recent clips and older ones are pruned on save. Deleting a clip removes its record and its audio blob together; uninstalling the app can clear the whole collection and free the cached model.

Voice samples are cached the same way. The first time you play a voice's sample it is synthesised like any other clip and then stored as a blob under voicestudio:preview/, so the second play is a database read rather than a model run — instant, and offline. The key carries a version segment, so changing the sample line invalidates every stored sample rather than leaving stale audio behind. Uninstalling clears these along with the saved clips.

Settings do not live here. voice, speed and autosave are declared on the descriptor and persist in the sync store (store.apps.voicestudio), like every other app's settings — the studio reads them on open, writes them when you pick a voice or release the speed slider, and follows the store while it is open, so the app and the settings dialog can never disagree.

Agent tools

The descriptor declares four tools — list_voices, speak_text, list_clips and delete_clip — routed through the same engine and the same storage the UI uses, so they cannot drift from it. They are dormant: no on-device model shipped today can reliably drive tool calls, so the agent loop is off and these never execute. They are declared now so that switching the feature on later is a runtime change rather than a rewrite.

Reference

The voice palette

All 28 voices, in the order the app shows them. Grade is the model authors' own assessment of that speaker, from A (best) down to F — it is reproduced verbatim rather than reinterpreted, because it is a genuinely useful signal about which voices are worth using. The four marked Top are the shortlist behind the picker's Top picks filter, one per group.

US · Female

VoiceIdGradeTop
Heartaf_heartAYes
Bellaaf_bellaA--
Nicoleaf_nicoleB--
Aoedeaf_aoedeC+-
Koreaf_koreC+-
Sarahaf_sarahC+-
Novaaf_novaC-
Skyaf_skyC--
Alloyaf_alloyC-
Jessicaaf_jessicaD-
Riveraf_riverD-

US · Male

VoiceIdGradeTop
Fenriram_fenrirC+-
Michaelam_michaelC+Yes
Puckam_puckC+-
Echoam_echoD-
Ericam_ericD-
Liamam_liamD-
Onyxam_onyxD-
Santaam_santaD--
Adamam_adamF+-

UK · Female

VoiceIdGradeTop
Emmabf_emmaB-Yes
Isabellabf_isabellaC-
Alicebf_aliceD-
Lilybf_lilyD-

UK · Male

VoiceIdGradeTop
Fablebm_fableC-
Georgebm_georgeCYes
Danielbm_danielD-
Lewisbm_lewisD-

A voice id encodes its group: the first letter is the accent (a for US, b for UK) and the second is the gender (f or m). Heart is the default.

The model's repository also carries voices for other languages. They are deliberately not shipped: they were trained against a different pronunciation frontend from the one the extension runs, so they would be mispronounced rather than merely accented.

Controls

ControlRange / valuesNotes
VoiceAny of the 28 aboveClick a card to select it. Persisted as the voice setting.
Play a sample-On every card and in the voice bar. Does not change the selection; click again to stop.
Download a voice-The same control on a voice not held yet: fetches the model (first time) and that voice's file, with a progress bar, then plays it.
Search / filtersName text, accent, gender, topNarrow the picker grid. The count above the grid says how many voices are showing.
Speed0.5x to 2x, 0.1x stepsApplies to the next render, and is remembered. The only delivery control the model exposes.
Speak-Enabled once the text box is non-empty and no render is running. A sample playing never blocks it.
Cancel-Visible during a render; stops at the next chunk boundary.
Download WAV-Mono 16-bit PCM at 24 kHz, named from the first words of the text.
Save to clips-Files the clip in the sidebar and opens it. Replaced by Saved to clips when auto-save is on.

Settings

Declared on the descriptor, so they appear both behind the settings control in the app window's title bar and in Settings ▸ Apps.

SettingTypeDefaultEffect
VoiceSelectHeartThe voice the studio starts with; picking one in the app writes it.
Speaking speedSlider1.0xThe studio's speed slider — the same stored value.
Save every clipToggleOffFiles each finished clip in the sidebar automatically.

Output format

PropertyValue
ContainerWAV (RIFF)
Encoding16-bit signed PCM
ChannelsMono
Sample rate24 kHz (the model's native rate)

Downloads and storage

ItemSizeWhen it is fetched
Speech model~90 MBOnce, on install (prefetched) or on the first render.
Voice style file~0.5 MBThe first time you use that particular voice.
Saved clipsYour WAVsNever fetched — created locally and stored in the local database.
Voice samplesSmallNever fetched — rendered once per voice on play, then kept for instant replay.

Saved clips are capped at the 40 most recent; older ones are pruned as new ones are added. Uninstalling the app offers to delete both the cached model files and your saved clips.

Agent tools (declared, dormant)

These are declared on the app descriptor and collected by the assistant's tool registry, but no shipped on-device model can drive the tool loop, so none of them runs today.

ToolParametersWould do
list_voices-List every voice with its id, accent and gender.
speak_texttext, voice?, speed?Synthesise the text and save it as a clip, returning its id.
list_clips-List saved clips with their id, name and voice.
delete_clipidDelete one saved clip.

An unknown voice falls back to the default rather than failing, and every tool returns a plain message on a bad argument instead of throwing.

Attribution

Voice Studio speaks with Kokoro-82M, an Apache-2.0 text-to-speech model. The voice palette and the pronunciation frontend are derived from the Apache-2.0 kokoro-js project. Both are attributed in the product's third-party notices, which travel with the extension.

Changelog

1.2.0 — 2026-07-27

  • Give the extension a session model, and run the apps in one tab
  • Fetch several voices at once, load the model once, and use the shared spinner
  • Say a voice must be downloaded, show the progress, and stop swallowing preview failures

1.1.2 — 2026-07-25

  • Several voices can be fetched at once: asking for a second no longer cancels the first, each card shows its own progress, and cancelling one leaves the others running
  • A cold start now downloads the speech model once however many voices you ask for — two overlapping requests each used to start their own copy of it
  • The download control uses the same spinner as the App Store's, instead of one this app drew for itself

1.1.1 — 2026-07-25

  • A voice you have not heard yet now says so on its card and offers a download, with a progress bar while it fetches — instead of a play button that appeared to do nothing while the speech model came down in silence
  • The picker states the cost up front: the one-time ~90 MB speech model, then about 0.5 MB per new voice, kept for offline use
  • A voice sample that will not play now says so, with a button to play it, instead of failing silently — and a failed preview no longer leaves the button stuck, so clicking it again really does try again