Skip to content

CodingFreeze/LivingTTS

Repository files navigation

Living TTS

Offline-first macOS menu-bar TTS — system voices or local AI models (Voxtral, Qwen3-TTS), all on-device.

License: MIT Platform: macOS 14+ Swift 6

⚠️ 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.

Why

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.

Architecture

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 TTSBackend protocol (availableVoices, startPlayback, pause, resume, stop, plus playback-state and progress callbacks). TTSBackendFactory maps a user preference to a concrete backend, so adding an engine never touches the rest of the app. Three backends ship today:
    • SystemTTSBackendAVSpeechSynthesizer with 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 local mlx_audio.server when available.
    • QwenLocalTTSBackend — Qwen3-TTS via a local MLX-Audio endpoint (http://127.0.0.1:8091/v1/audio/speech); model overridable with LIVING_TTS_QWEN_MODEL.
  • @Observable controller. A single @MainActor @Observable AppController owns app state (playback state, captured/cleaned previews, progress, available voices) and orchestrates the services. SwiftUI views observe it directly — no manual ObservableObject/@Published plumbing.
  • Service seams via protocols. Text capture (TextCaptureService), text cleaning (TextCleaningServiceMarkdownTextCleaningService), 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-pinned MiniPlayerPanelView, and a themed PreferencesView (ControlPanelTheme). Preferences persist through AppPreferences over UserDefaults.

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.

Run

./script/build_and_run.sh

The 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.

Core Controls

  • 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.

Permissions

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.

Optional Local AI Backends

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 8091

Status

Active, 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.

Verification

swift test
./script/build_and_run.sh --verify

About

Offline-first macOS menu-bar TTS — system voices or local AI models (Voxtral, Qwen3-TTS), on-device.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors