Skip to content

keltokhy/writ-fm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WRIT-FM

A 24/7 AI-powered talk radio station. AI writes the scripts, TTS speaks them, AI generates the music, and the stream runs forever.

What is this?

WRIT-FM is a talk-first internet radio station where:

  • 5 AI hosts rotate across 8 shows, each with a distinct voice and topic focus
  • Long-form talk segments (10-20 min) are the primary content
  • Short AI-generated music bumpers (1-2 tracks) play between talk segments
  • Scripts are written by Claude CLI, rendered by Kokoro TTS
  • Music is generated by ACE-Step via music-gen.server
  • A Claude Code operator loop keeps everything stocked and running 24/7

Architecture

┌──────────────────────────────────────────────────────────────┐
│  writ CLI                     (tmux-based process manager)   │
├──────────────────────────────────────────────────────────────┤
│  ezstream + feeder.py                                        │
│    ├── ezstream: Icecast source client (Ogg Vorbis)          │
│    ├── feeder.py: builds playlists per show schedule          │
│    ├── Interleaves talk segments with AI music bumpers        │
│    ├── Detects new content and reloads playlist (SIGHUP)      │
│    └── Runs API server as daemon thread (:8001)              │
├──────────────────────────────────────────────────────────────┤
│  Icecast :8000 ──► cloudflared tunnel ──► public URL         │
│  API :8001 ───► /now-playing /schedule /health /messages     │
├──────────────────────────────────────────────────────────────┤
│  content_generator/                                          │
│    ├── talk_generator.py        (Claude CLI + Kokoro TTS)    │
│    ├── music_bumper_generator.py (ACE-Step via music-gen)    │
│    ├── listener_response_generator.py                        │
│    └── persona.py               (5 hosts, station identity)  │
├──────────────────────────────────────────────────────────────┤
│  operator_daemon.sh             (Claude Code maintenance)    │
│  listener_daemon.sh             (message → on-air response)  │
└──────────────────────────────────────────────────────────────┘

Quick Start

1. Install dependencies

# Install uv (Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install system dependencies (macOS)
brew install icecast ffmpeg ezstream vorbis-tools

# Set up Python environment
uv sync

2. Set up TTS

cd mac/kokoro
uv venv
uv pip install kokoro soundfile
# Downloads ~200MB model on first run

3. Configure

cp config/icecast.xml.example config/icecast.xml
cp mac/config.yaml.example mac/config.yaml

# Edit mac/config.yaml — set Icecast password (must match icecast.xml)

4. Start the station

The writ CLI manages all components via tmux:

./writ start          # Start everything (icecast, stream, tunnel, music-gen, operator, listener)
./writ status         # Health check all components
./writ stop           # Stop everything

Start individual components:

./writ start icecast  # Icecast server
./writ start stream   # Streamer + API
./writ start tunnel   # Cloudflared tunnel
./writ start operator # Claude Code maintenance loop

Other commands:

./writ logs stream -f     # Tail streamer logs
./writ attach operator    # Attach to operator tmux window
./writ restart stream     # Restart a component

5. Generate content

./writ generate talk                          # 3 segments per show
./writ generate talk --show midnight_signal   # Specific show
./writ generate music                         # AI music bumpers
./writ generate status                        # Show segment counts

Or run generators directly:

uv run python mac/content_generator/talk_generator.py --all --count 3
uv run python mac/content_generator/music_bumper_generator.py --all --min 5

Hosts

Host Voice Focus
The Liminal Operator am_michael Philosophy, radio lore, morning reflections
Dr. Resonance bm_daniel Music history, genre archaeology
Nyx af_heart Dreams, night philosophy
Signal am_onyx News analysis, current events
Ember af_bella Soul, funk, music as feeling

Weekly Schedule

8 talk shows rotate across the day. See config/schedule.yaml for the full definition.

Daily base schedule:

  • 00:00-04:00 — Midnight Signal (Liminal Operator — philosophy)
  • 04:00-06:00 — The Night Garden (Nyx — dreams, night)
  • 06:00-09:00 — Dawn Chorus (Liminal Operator — morning reflections)
  • 09:00-12:00 — Sonic Archaeology (Dr. Resonance — music history)
  • 12:00-14:00 — Signal Report (Signal — news analysis)
  • 14:00-16:00 — The Groove Lab (Ember — soul, funk)
  • 16:00-18:00 — Crosswire (Dr. Resonance + Ember — panel debate)
  • 18:00-20:00 — Sonic Archaeology
  • 20:00-22:00 — The Groove Lab
  • 22:00-00:00 — The Night Garden

Weekly override:

  • Sunday 18:00-20:00 — Listener Hours (mailbag)

Segment Types

Long-form (primary content, 1500-3000 words):

  • deep_dive — Extended single-topic exploration
  • news_analysis — Current events through a late-night lens (uses RSS headlines)
  • interview — Simulated interview with a historical or fictional figure
  • panel — Two hosts discuss a topic from different angles
  • story — Narrative storytelling from music and culture
  • listener_mailbag — Listener letters and responses
  • music_essay — Extended essay on an artist, album, or genre

Short-form (transitions):

  • station_id — Station identification
  • show_intro — Show opening
  • show_outro — Show closing

Automated Operation

The operator daemon runs Claude Code on a 15-minute loop to:

  1. Health-check the stream, Icecast, and encoder
  2. Stock talk segments for current and upcoming shows (minimum 6 per show)
  3. Stock AI music bumpers when music-gen.server is available (minimum 5 per show)
  4. Process listener messages into on-air responses
  5. Detect drift between config, docs, and runtime state
./writ start operator   # Start via writ CLI
./run_operator.sh       # Run once manually
bash mac/operator_daemon.sh  # Run as a persistent loop

The listener daemon polls for new messages every 30 seconds and generates spoken responses:

./writ start listener

Customizing

Change hosts and personalities — Edit mac/content_generator/persona.py. Each host has an identity, voice style, philosophy, and anti-patterns.

Modify the schedule — Edit config/schedule.yaml to add/remove shows, change time slots, or assign different hosts and voices.

Use different TTS voices — Kokoro includes 28 voices (see mac/kokoro/tts.py). Assign voices per-show in config/schedule.yaml.

Add music styles — Edit mac/content_generator/music_pools_expanded.py to change the AI music generation prompts per show.

Files

├── writ                        # Station CLI (start/stop/status/logs/generate)
├── run_operator.sh             # Single operator run (Claude Code)
├── mac/
│   ├── feeder.py               # Playlist feeder (manages ezstream + API)
│   ├── radio.xml               # ezstream config (Icecast, Ogg encoding)
│   ├── next_track.py           # Track selector (schedule-aware)
│   ├── api_server.py           # Now-playing API (daemon thread in feeder)
│   ├── schedule.py             # Schedule parser and resolver
│   ├── play_history.py         # Track history and dedup
│   ├── music_gen_client.py     # REST client for music-gen.server
│   ├── operator_prompt.md      # Operator maintenance prompt
│   ├── operator_daemon.sh      # Operator loop (runs run_operator.sh)
│   ├── listener_daemon.sh      # Listener message polling daemon
│   ├── start_music_gen.sh      # Start music-gen + daemons in tmux
│   ├── kokoro/                 # Kokoro TTS wrapper
│   ├── content_generator/
│   │   ├── talk_generator.py              # Talk segment generator
│   │   ├── music_bumper_generator.py      # AI music bumper generator
│   │   ├── listener_response_generator.py # Listener message → audio
│   │   ├── music_pools_expanded.py        # Music generation prompts
│   │   ├── persona.py                     # Host definitions and station identity
│   │   └── helpers.py                     # Shared utilities
│   └── config.yaml             # Local config
├── config/
│   ├── schedule.yaml           # Weekly show schedule
│   └── icecast.xml.example     # Icecast template
├── output/
│   ├── talk_segments/{show}/   # Generated talk audio
│   ├── music_bumpers/{show}/   # AI-generated music bumpers
│   └── scripts/                # Script metadata
└── docs/                       # Web-facing pages

Requirements

  • Python 3.11+
  • ffmpeg, ezstream, vorbis-tools
  • Icecast2
  • Claude CLI (for script generation and operator loop)
  • Kokoro TTS (~200MB model)
  • music-gen.server + ACE-Step (optional, for AI music bumpers)
  • cloudflared (optional, for public tunnel)
  • Apple Silicon recommended

License

MIT

About

24/7 AI-powered internet radio station. Claude writes the DJ scripts, Chatterbox speaks them.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors