Import an EPUB, PDF, DOCX, TXT or Markdown file. Ka-Chaow pulls it apart into words, repairs whatever the file format did to the text on the way out, and plays the book back to you one word at a time — each word positioned so its pivot letter lands on the same spot as the last one. Your eye stops travelling. The words come to it. Before you've imported anything the empty library is already doing it, cycling reading · faster · flows · kachaow through the same red pivot every 1.6 seconds.
The pace is not a metronome. Every word carries flags from import — long, frequent, a number, a proper noun, ends a clause, ends a sentence — and each flag bends that word's own dwell time. "Antidisestablishmentarianism" gets 1.5×. "the" gets 0.75×. A full stop gets 2.2× so the sentence has somewhere to land. Default 250 wpm, tunable to 1000, and because the multipliers are computed at display time rather than baked in at import, you can drag a pacing slider mid-chapter and feel it change on the next word.
Same book, four ways to read it. Word is the flashing pivot above. Sentence flashes a whole sentence at once, held for its combined pace, for people who'd rather take a phrase in one look. Book is the actual page, scrolling itself, with a highlight walking the text at your pace — swipe down from Word mode and the page rises to meet your finger. Normal turns the engine off entirely and just gives you the book, with bookmarks. Audio narrates it, highlight following the voice, and it keeps narrating with the screen locked.
The import shows you its work before the book enters your library. PDFs arrive as a pile of positioned line fragments, not paragraphs. Ka-Chaow finds running headers and footers by measuring which normalized lines recur in the top or bottom 12% of the page across more than 30% of pages, strips TOC dot-leaders and footnote markers, rejoins hyphens split across line breaks, and merges the physical lines back into paragraphs. Then it hands you a receipt — words recovered, hyphens repaired, lines merged, blocks it wasn't sure about — and you decide whether to keep it or throw it away. A clean EPUB needs no repairs and is told so; the tally only appears when there was something to fix.
Dehyphenation that knows the difference between a broken word and a real one. "under-\nstand" becomes "understand". "well-\nknown" stays hyphenated. The pass checks the joined form against the frequency list and a lowercase-shape heuristic, and keeps the hyphen when the compound is the more plausible word.
Frame-quantized timing that doesn't drift. The engine runs off a vsync
ticker — the Flutter equivalent of CADisplayLink — and carries the leftover
millisecond budget across every advance. Onset error averages out instead of
accumulating, so word 40,000 is as on-time as word 4.
Neural narration that runs on the phone. Five Piper voices (English US/GB, Turkish) download on demand, ~64 MB each, and synthesize locally through sherpa-onnx. Each one ships with a short bundled sample rendered from the very model the catalog offers, so you can hear a voice before paying for the download. Synthesis blocks for 0.5–2 s per sentence, so it happens in a long-lived background isolate; Piper reports no word timings, so boundaries are estimated from word length and emitted on timers that go silent the moment a newer utterance displaces them.
Nothing leaves your device. There is exactly one outbound HTTP call in the entire codebase, and it fetches a voice model from the sherpa-onnx GitHub release. No account, no sync, no analytics, no telemetry. Your library, your reading positions and your streaks live in a local SQLite file.
Do I need an account?
No. There's no sign-up, no login screen, and no server to sign in to. Open the app, import a file, read.
Does my reading get sent anywhere?
No. The app makes one kind of network request — downloading an optional neural voice from GitHub — and that's it. There is no analytics SDK, no crash reporter, and no sync. Books, positions, bookmarks, sessions and streaks are stored in a local Drift/SQLite database on the device.
Will it mangle my PDFs?
It might, and that's exactly why the import preview exists. Before a book is committed to your library you see the recovered word count, the paragraph and sentence counts, and — when the pipeline actually had to fix something — a repair tally: how many header lines, page numbers, TOC lines and footnote markers were stripped, how many hyphens were rejoined, how many blocks it kept but couldn't confidently classify. If the numbers look wrong, hit Discard and nothing is saved.
Can I read my Kindle or Adobe books?
Only if they're unencrypted. DRM-locked files can't be opened by anything except the store that sold them, and Ka-Chaow doesn't pretend otherwise — the import will simply fail. Unencrypted EPUB, PDF, DOCX, TXT and Markdown all work.
What happens if I lock my screen halfway through a chapter?
In Audio mode, narration keeps going and the lock screen shows transport controls; play, pause, and next/previous sentence all work from there and from headphones. If a call or Siri interrupts, playback pauses and resumes when the system says it's allowed to. In the visual modes, closing the app saves your token position, and reopening the book puts you back on that word — optionally rewinding a few words first, and ramping back up to your target speed over a couple of seconds rather than slamming you into 400 wpm cold.
Am I going to lose my place with a stray thumb?
Gestures that move the reading cursor are treated as dangerous. They're disabled entirely while playing, they never fire from the left screen edge (that belongs to swipe-back), there's a scrub lock in the reader chrome that turns them all off, and when one does move you, a toast offers Undo with the index you were on before.
Can I change how it reads me a book without losing my place?
Yes — the mode picker is in the reader's top bar, and switching is instant because every mode reads the same token stream from the same cursor. Flip from Sentence to Book mid-paragraph and the page opens exactly where the flashing left off.
Does speed reading actually work?
Reasonable people disagree, and this app doesn't take a position. What it does do is remove the mechanical cost of moving your eyes across a line and back, and let you pick the pace. If 250 wpm with a longer beat on full stops is where you comprehend best, the sliders go there too — and Normal mode turns the whole engine off if you'd rather just read the page.
Is it free? Are there ads?
Free, no ads, no subscription, no in-app purchase. The source is here under AGPL-3.0.
Does it work offline?
Entirely, once a voice is downloaded. The reading fonts are bundled rather than fetched, the click and noise beds are synthesized in code instead of shipped as audio files, and neural narration runs on-device.
What's in it besides speed?
Bionic-style leading-letter bolding, OpenDyslexic alongside three bundled reading faces, Irlen-style peach/mint/parchment tints over the whole app, three full visual themes, a 20-20-20 eye-rest nudge, Pomodoro cycles, a sleep timer (5/15/30/60 minutes or end-of-chapter), collections, per-book stats, daily goals, streaks, and English/Turkish throughout.
Flutter + Riverpod, Drift over SQLite, pdfrx/syncfusion for PDF,
epub_pro for EPUB, sherpa_onnx for neural TTS, flutter_soloud for audio,
and a Swift method-channel plugin for AVSpeechSynthesizer and the iOS Now
Playing surface. 154 tests across 28 files, all passing on Flutter 3.47.
-
Tokens store flags, not durations. A book is persisted as a compact binary stream —
KCHWmagic, then per token a varint length, UTF-8 bytes, a u16 flag word, and two bytes of precomputed pivot indices. Paragraph and sentence indices are reconstructed during the decode pass rather than stored. A 200k-token book stays a few MB and decodes in well under a second, and because no pacing weight is ever written to disk, retuning the pacing sliders never requires re-importing anything. -
The audio device is only held when it will make a sound. SoLoud starts a CoreAudio output unit on init and keeps it running, and on iOS a running output unit is what tells the system your app is playing. An initialized-but- silent engine pins the Control Center button to "playing" for the entire session no matter what playback rate the app publishes. So the click/noise engine is initialized only when the current settings would actually produce sound, and released the moment they wouldn't.
-
The narration loop was pulled out of the widget so it could be tested. A wrong-sentence regression once shipped and survived several fixes precisely because none of that logic could run under test.
AudioNarratornow holds the invariants explicitly — narration starts at the cursor, the advance target comes from the utterance that was actually spoken rather than the live cursor, boundary seeks inside the spoken range never restart it, and every utterance carries a request id so events from superseded utterances are dropped on the floor.
flutter pub get
dart run build_runner build --delete-conflicting-outputs # Drift codegen
flutter runflutter test # 154 tests
./deploy.sh --help # iOS/TestFlight, macOS, Android APK, webRequires Flutter with Dart SDK 3.11+. The Android build produces a debug-signed APK — release signing isn't set up in this repo.
Phosphor icons are declared locally. phosphor_flutter 2.1.0 — its
latest release — extends IconData, which Flutter made a final class, so
importing it breaks the build on Flutter 3.47+. The package stays in
pubspec.yaml for its font assets, but the glyphs AppIconSet uses are
declared as plain IconData in lib/core/theme/phosphor_glyphs.dart. Add an
entry there when reaching for a new Phosphor icon; drop the file for the
package import once upstream unseals the class.
AGPL-3.0. Bug reports and feature requests go in Issues.









