Offline-first macOS menu-bar TTS — system voices or local AI models (Voxtral, Qwen3-TTS), all on-device.
⚠️ Status — work in progress (paused). Update June 27, 2026: I started Living TTS and got the core working, but it doesn't fully behave as planned yet. I'm archiving it for now and intend to come back and finish it when I have the time.
Living TTS is a native macOS menu-bar utility that reads text aloud. Put your
cursor anywhere, hit a global hotkey, and it speaks from the caret forward —
cleaning away Markdown, links, bullets, and chat formatting before the words ever
reach a speech engine. It runs as a lightweight status-bar item with a floating
mini-player, and it stays private: by default everything happens on your Mac
with no network calls. When you want higher-quality narration, you can switch to
a local AI speech model (Voxtral or Qwen3-TTS) served on 127.0.0.1 — still
fully on-device, no cloud.
Most "read it to me" tools either ship your text to a cloud API or speak raw Markdown noise at you. Living TTS does neither: it's offline-first, it cleans the text first, and it lives one keystroke away in the menu bar instead of being a window you have to manage.
Living TTS is a Swift Package Manager executable (swift-tools-version: 6.0,
macOS 14+) with a small, protocol-driven core:
- Protocol-driven TTS backends. Every speech engine conforms to a single
TTSBackendprotocol (availableVoices,startPlayback,pause,resume,stop, plus playback-state and progress callbacks).TTSBackendFactorymaps a user preference to a concrete backend, so adding an engine never touches the rest of the app. Three backends ship today:SystemTTSBackend—AVSpeechSynthesizerwith macOS English voices. The default, fully-offline path.VoxtralLocalTTSBackend— talks to a local OpenAI-compatible speech endpoint (http://127.0.0.1:8000/v1/audio/speech), auto-starting a localmlx_audio.serverwhen available.QwenLocalTTSBackend— Qwen3-TTS via a local MLX-Audio endpoint (http://127.0.0.1:8091/v1/audio/speech); model overridable withLIVING_TTS_QWEN_MODEL.
@Observablecontroller. A single@MainActor @Observable AppControllerowns app state (playback state, captured/cleaned previews, progress, available voices) and orchestrates the services. SwiftUI views observe it directly — no manualObservableObject/@Publishedplumbing.- Service seams via protocols. Text capture (
TextCaptureService), text cleaning (TextCleaningService→MarkdownTextCleaningService), and the global hotkey (GlobalHotkeyService) are all behind protocols, which keeps the core pure and testable. - AppKit + SwiftUI shell. The UI is a menu-bar status item (
MenuBarMenuView), a floating, optionally-pinnedMiniPlayerPanelView, and a themedPreferencesView(ControlPanelTheme). Preferences persist throughAppPreferencesoverUserDefaults.
The pipeline is: capture (cursor-to-end or selection) → clean (strip Markdown/links/citations/emoji) → speak (selected backend) → report progress back to the mini-player.
./script/build_and_run.shThe script builds the SwiftPM app, stages dist/Living TTS.app, signs it with the
local Apple Development identity when available, and launches it as a menu-bar app.
- Use the menu-bar item to read from the cursor, read selected text, pause/resume, stop, open the mini-player, open preferences, or quit.
- The default global hotkey is
Control + Option + R. - Preferences let you choose backend, English voice, speed, read mode, hotkey preset, and floating mini-player behavior.
- The mini-player floats above other windows, can be pinned, and shows current segment progress.
Living TTS uses local macOS APIs only by default.
- Accessibility is required for selected-text and cursor-to-end capture.
- Input Monitoring may be required for the global hotkey and clipboard-preserving copy fallback.
- Preferences include buttons that open the relevant macOS privacy panes.
The System voice works out of the box. The AI backends are wired in but need a local MLX-Audio server. Qwen example:
mkdir -p ~/qwen-tts-local
cd ~/qwen-tts-local
python3 -m venv .venv
source .venv/bin/activate
pip install -U mlx-audio
pip install -U uvicorn fastapi pydantic webrtcvad python-multipart
pip install 'setuptools<81'Verify the server manually:
~/qwen-tts-local/.venv/bin/python3 -m mlx_audio.server --host 127.0.0.1 --port 8091Active, functional macOS app. The native menu-bar app, mini-player, preferences,
and all three backends (System / Voxtral Local / Qwen Local) are implemented;
swift build and swift test pass. The local AI backends require a local
MLX-Audio server (see above) — without it, the System voice remains fully usable.
The web-preview/ directory is a static HTML approximation for quick UI
inspection only; it is not the shipping interface and may lag behind the native
app.
swift test
./script/build_and_run.sh --verify