A lightweight, always-on audio playback service.
"pygame, simplified. Two lines of code instead of an all-day project."
Built by Stark Technologies — Lead Developer: Tim Stark Developed with assistance from Claude Code (Anthropic) and Walter (Hermes Agent, Nous Research)
Playing audio reliably in Python is surprisingly painful. pygame's mixer needs to be initialized, kept alive, and managed carefully — or you get clipped playback, intermittent failures, and audio that cuts off mid-sentence.
Loudmouth solves this by staying loaded. It runs as a persistent HTTP service, keeping pygame's mixer warm and ready. Any application — a web app, an agent, a script — can trigger audio playback with a single HTTP call. No initialization overhead, no clipping, no surprises.
import pygame
import time
pygame.mixer.init()
time.sleep(1.0) # wait for driver to initialize
pygame.mixer.music.load("audio/announcement.mp3")
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
time.sleep(0.1)
pygame.mixer.quit()import httpx
httpx.post("http://127.0.0.1:8765/play", json={"file": "announcement.mp3"})- Always-on — Windows consumer installer (starts at logon), systemd service (Linux), or Windows Service; zero babysitting
- Zero-dependency Windows install — one download, one installer; no Python, no pip, no console window
- HTTP API — play, stop, check status, and health-check from any language or tool
- Multi-format — MP3, WAV, OGG, FLAC, OPUS, WV, MOD, MIDI (via pygame/SDL_mixer)
- Base64 audio — stream raw audio bytes directly without touching the filesystem
- Path-safe — directory traversal attacks blocked at the API layer
- Secure by default — binds loopback only; CORS restricted to an explicit allowlist
- Lightweight — FastAPI + pygame, no database, no config files
- Download the installer: idealoom.ai/download/loudmouth (redirects to the latest GitHub Release asset
LoudmouthSetup-<version>.exe) - Run it — installs to
%ProgramFiles%\Stark\Loudmouth, registers a logon task, and starts Loudmouth immediately - Verify: open
http://127.0.0.1:8765/health— you should see JSON with"status": "ok"
Uninstall from Settings → Apps — removes the scheduled task, the install directory, and log files. No leftovers.
Known limitation: the logon task is per-user, so Loudmouth runs only while that Windows user is logged in. This is the correct behavior for consumer machines.
Silent install (fleet/enterprise):
LoudmouthSetup-<version>.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART
pip install fastapi pygame-ce uvicorn
python server.pypython server.py applies the standard policy: bind 127.0.0.1, port 8765 (fallback 8766). Override via environment:
| Variable | Default | Purpose |
|---|---|---|
LOUDMOUTH_HOST |
127.0.0.1 |
Set 0.0.0.0 for fleet/LAN use — deliberate opt-in, since /play drives the machine's speakers |
LOUDMOUTH_PORT |
8765 → 8766 |
Explicit port; skips the fallback probe entirely |
LOUDMOUTH_CORS_ORIGINS |
see CORS | Comma-separated origin allowlist |
LOUDMOUTH_KEEPALIVE_HZ |
21000 |
Keep-alive tone frequency |
LOUDMOUTH_KEEPALIVE_DBFS |
-20 |
Keep-alive tone level |
Linux (systemd):
sudo bash install/install.sh
systemctl status loudmouth # check
journalctl -u loudmouth -f # logs
sudo bash install/uninstall.sh # removeWindows (from source, pywin32 service):
pip install pywin32
python install/install_service.py install
python install/install_service.py startPrimary 8765, fallback 8766. Fixed and permanent.
- On startup Loudmouth tries
8765; if occupied it tries8766; if both are occupied it logs clearly and exits. GET /healthalways reports the actual bound port.- Clients should probe
8765first, then8766. - Port
8000(pre-1.0) is abandoned.
Service identity — used by IdeaLoom's detectLoudmouth() and by humans debugging "voice isn't working."
Response:
{
"status": "ok",
"version": "1.0.0",
"protocol": 1,
"port": 8765,
"uptime": 18342
}protocolis an integer API-compatibility constant. Consumers must gate onprotocol >= minimumProtocol— never compare version strings. It bumps only on breaking API changes.versionis the human-readable release version (single source:server.__version__).uptimeis seconds since the audio mixer came up.
Play an audio file or raw audio bytes. Only one stream at a time; a new call replaces the current track.
Play a file from the audio/ folder:
{ "file": "loudmouth-test.mp3" }MP3 files are auto-detected — just pass the filename. For other formats, pass the format hint:
{ "file": "sound.ogg", "format": "ogg" }Play base64-encoded audio:
{ "audio": "<base64-string>", "format": "mp3" }Response: { "status": "playing" }
Errors: 400 invalid base64 / unsupported format / missing fields, 404 file not found.
Response: { "playing": true }
Response: { "status": "stopped" }
The browser-side detectLoudmouth() flow consumes these states:
GET http://127.0.0.1:8765/health (fallback :8766)
→ 200 + JSON → AVAILABLE (check protocol >= minimum)
→ 200, old protocol → INCOMPATIBLE_VERSION
→ connection refused → NOT_FOUND (not installed or not running)
→ LNA prompt pending → PERMISSION_REQUIRED (browser-side state)
→ user denied → PERMISSION_DENIED (browser-side state)
→ wrong browser → UNSUPPORTED_BROWSER (Safari/macOS)
| Browser | Windows | macOS |
|---|---|---|
| Chrome | ✅ (LNA permission prompt on first use, Chrome 142+) | ❌ |
| Edge | ✅ | ❌ |
| Firefox | ✅ (no LNA prompt; failures surface as network errors) | ❌ |
| Safari | — | ❌ unsupported until a macOS package exists |
Loudmouth is an actuator — /play has a physical effect on the machine — so CORS is a restricted allowlist, not *. Default allowed origins:
https://idealoom.ai
https://www.idealoom.ai
http://localhost:5173 (loom dev)
http://100.64.58.91:8910 (tailnet clean-machine test path)
Override with LOUDMOUTH_CORS_ORIGINS (comma-separated). Requests from non-allowlisted origins get no CORS headers.
Preflights (OPTIONS) are answered for all endpoints, including the Access-Control-Allow-Private-Network: true response header — required for public-site → loopback requests under Chrome's Private Network Access / Local Network Access model.
A sample audio file ships with every install. With the service running:
curl -X POST http://127.0.0.1:8765/play \
-H "Content-Type: application/json" \
-d '{"file": "loudmouth-test.mp3"}'You should hear: "Loudmouth audio test. If you can hear this complete sentence, your installation is working correctly. One. Two. Three."
Logs (packaged Windows build): %LOCALAPPDATA%\Loudmouth\loudmouth.log
| Format | Extension | Format Hint | Notes |
|---|---|---|---|
| MP3 | .mp3 |
Not required | Auto-detected from extension |
| WAV | .wav |
"wav" |
Uncompressed; best latency |
| OGG Vorbis | .ogg |
"ogg" |
Open format; recommended for web |
| FLAC | .flac |
"flac" |
Lossless |
| OPUS | .opus |
"opus" |
High quality at low bitrate |
| WavPack | .wv |
"wv" |
Lossless compression |
| MOD | .mod |
"mod" |
Tracker music |
| MIDI | .mid |
"mid" |
Requires system soundfont |
Tip: When in doubt, always pass the
formathint — it never hurts and prevents ambiguity.
Requires Python 3.10+, PyInstaller, and Inno Setup 6:
powershell -ExecutionPolicy Bypass -File packaging\build.ps1Produces dist\Loudmouth\ (onedir app) and dist\LoudmouthSetup-<version>.exe plus dist\SHA256SUMS.txt. See packaging/ for the spec, installer script, and signing wiring (Azure Trusted Signing — unsigned builds are fine for internal testing).
Releases are automated: push a tag v<version> matching server.__version__ and CI builds, signs (when secrets are configured), and publishes the GitHub Release. Manual pre-release smoke test checklist: docs/smoke-test.md.
- Confroom — room audio announcements and TTS playback
- IdeaLoom / ApexFuture.AI — website audio and AI agent voice output
- Home automation — trigger audio from any smart home event
- Any Python app — add reliable audio in two lines
MIT — see LICENSE
This project uses pygame-ce, distributed under LGPL-2.1.
PRs welcome. Open an issue first for anything beyond a bug fix.