Personal internet radio: Agentic AI DJ
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A personal internet radio station. One Icecast stream, one broadcast. Every listener hears the same thing at the same time. An AI DJ picks the tracks and talks between them: station idents, time checks, the weather, a quick intro for whatever's going out next. You can ask for music in plain language; the DJ works out what you meant and slots it in.
It's radio, not a playlist. No per-listener shuffle, no skip button, no "up next for you." You tune in and hear whatever is on.
https://github.com/user-attachments/assets/0a2ba78a-eda3-44c1-adce-bfa78ae992cd
Don't want to run a language model? SUB/WAVE DJ Brain
is a metered brain (and optional cloud voice) for the station you already self-host: one
key, pasted once, from £5/month. SUB/WAVE Hosted runs the
whole station for you at yourname.getsubwave.com. Both fund the project; the software
here stays complete and free either way.
The listener player. One shared broadcast, with in-app song requests.
The admin console. Where the operator runs the station.
| Dash — live status, the queue, the booth log | Personas — the DJ roster, each with its own voice | Schedule — programme the week, an hour at a time |
| Skills — what the DJ does between tracks | Stats — LLM and TTS usage at a glance | Debug — health, logs, recent LLM calls |
The Library Observatory. A data-art map of every track the DJ has tagged, placed by genre and lit by energy. Click a point for its full dossier: BPM, key, mood, embeddings, and nearest neighbours. Open it at /observatory.
tts-heavy sidecar (docker compose --profile tts-heavy up -d) that adds Chatterbox (zero-shot voice cloning) and PocketTTS (6× real-time, EN/FR/DE/IT/ES/PT). Cloud (OpenAI / ElevenLabs) and a Remote engine (any self-hosted HTTP endpoint, audio over the wire) round it out. Pick a different engine per kind of speech.A playlist is a list you control. Radio is a broadcast you join. SUB/WAVE is the second kind:
curl -fsSL https://cli.getsubwave.com | sh # installs, then offers to init + start
subwave setup # connect Navidrome + LLM
Two Enter prompts during the installer (Run subwave init now?, then
Bring the stack up now?) and the stack is on-air. subwave setup
connects Navidrome and your LLM, or do the same in the browser at
http://localhost:7700/onboarding.
No clone, no Node on the host. subwave status / logs / doctor / update
work from anywhere afterwards.
If you'd rather skip our binary on your host and stick to docker compose:
mkdir subwave && cd subwave
curl -O https://raw.githubusercontent.com/perminder-klair/subwave/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/perminder-klair/subwave/main/.env.example
mv .env.example .env
# Edit .env: set ADMIN_USER, ADMIN_PASS, SITE_URL (three vars, that's it).
docker compose up -d
# Then open https://your-host/onboarding. The web wizard collects Navidrome,
# LLM, TTS, DJ persona, and offers to render jingles.
Functionally identical: same images, same state layout, same persistence.
The CLI just saves you the curl-and-edit dance and gives you subwave logs,
subwave doctor, etc. for the rest of the lifecycle.
Chatterbox (zero-shot voice cloning) and PocketTTS (fast multilingual) live in
a separate subwave-tts-heavy sidecar that adds ~5–6 GB of PyTorch and is
not started by default. To enable:
docker compose --profile tts-heavy up -d
The controller is wired up to discover the sidecar automatically. Stop it
again with docker compose --profile tts-heavy stop tts-heavy; the rest of
the stack keeps running and Chatterbox/PocketTTS personas silently fall back
to Piper. The old docker build --build-arg WITH_CHATTERBOX=1 path still
works if you already have a custom-built controller image — see
docker/Dockerfile.controller.
Run just one engine. Both engines load by default, but each costs RAM and a
first-boot weight download. If you only use one, name it in .env and the other
never loads:
TTS_HEAVY_ENGINES=pocket-tts # PocketTTS only (no Chatterbox)
# TTS_HEAVY_ENGINES=chatterbox # Chatterbox only
# TTS_HEAVY_ENGINES=chatterbox,pocket-tts # default — both
Acoustic analysis (tempo/key/loudness) does not need the heavy TTS sidecar —
it runs in its own subwave-analyzer service, which starts by default
alongside the controller and web. The default image is lean and multi-arch
(~1.1 GB, no PyTorch — so it runs natively on arm64 NAS/Pi/Apple-Silicon). The
two heavier dimensions — CLAP "sounds-like" embeddings and Demucs vocal
ranges — are the opt-in tier; enable them by pulling the heavy image, no rebuild:
# in your root .env
ANALYZER_HEAVY=1
That repoints the analyzer service at subwave-analyzer-heavy (CLAP + Demucs,
~1.9 GB, amd64) on the next docker compose up -d. The subwave setup wizard
also offers it, and Unraid one-click users pull the subwave-aio-heavy image
instead. Only the expressive voices above need the separate tts-heavy sidecar.
If analysis already runs somewhere else — a GPU box you point ANALYZE_URL at —
you can drop the redundant local container entirely with one more line:
# in your root .env — 0 or 1 only; unset means 1 (the default-on station)
ANALYZER_REPLICAS=0
The next docker compose up -d removes it and it stays gone. This is a Compose
setting, so it does nothing on the all-in-one image, which runs the analyzer
in-process. See docs/tts-heavy.md.
Hosts with an NVIDIA GPU can run the heavy stack on CUDA instead. The CUDA
images use torch/torchaudio 2.7.1 with CUDA 12.8 wheels, including support for
RTX 50-series Blackwell GPUs. For a one-off run, layer the
docker-compose.analyzer-gpu.yml overlay:
docker compose -f docker-compose.yml -f docker-compose.analyzer-gpu.yml up -d
To keep the overlay across normal rebuilds and restarts, set COMPOSE_FILE in
.env to docker-compose.yml:docker-compose.analyzer-gpu.yml.
All-in-one installs have no analyzer service to swap, so they take the GPU on
the image instead: pull subwave-aio-cuda and hand the container the card
(--gpus all, or the Unraid steps in docs/unraid.md).
git clone https://github.com/perminder-klair/subwave.git && cd subwave
./scripts/setup.sh # scaffolds a 3-var root .env + state/
docker compose -f docker-compose.dev.yml up -d # Broadcast (icecast2 + liquidsoap) + Controller
cd web && npm install && npm run dev # web UI on :7700, separate and hot-reloading
# Then http://localhost:7700/onboarding to finish configuration.
Dev compose bind-mounts controller/src/, radio.liq, and sounds/ from the
repo. Controller runs under tsx watch so src/** edits hot-reload inside
the container; radio.liq edits just need a docker compose -f docker-compose.dev.yml restart broadcast.
The standalone subwave CLI works inside the cloned repo too. cd subwave && subwave start dev does the right thing. The contributor convenience is npm start, which tsx-runs the CLI source directly so unreleased changes are
exercised. Same commands, same flags, no npm install -g needed.
The same CLI doubles as the console for running the station. Run npm start
for a status-aware menu; every menu action is also a one-shot subcommand,
appended after npm start --:
npm start # interactive operator console (status-aware menu)
npm start -- setup # first-boot wizard: Navidrome, LLM, admin, env files
npm start -- status # compose env, services, now-playing, recent events
npm start -- doctor # full diagnostic sweep
npm start -- start dev # docker compose up -d (dev or prod)
npm start -- restart broadcast # plain restart (radio.liq is bind-mounted in dev)
npm start -- restart controller # rebuild + recreate (source is COPY-d at build)
npm start -- logs controller # tail one service
npm start -- listen # open the web player in a browser
npm start -- admin # open the admin console in a browser
npm start -- stop # docker compose down (confirms first)
Single Linux host, Cloudflare terminating TLS, Caddy routing to four internal
services. The no-CLI quickstart above is
the canonical path: curl two files, fill in three vars, docker compose up -d, finish setup in the browser. See DEPLOY.md for host
prerequisites, Cloudflare setup, updates, and backup.
On Unraid? One-click install from Community Applications
(search SUB/WAVE in the Apps tab) — or run the full split-container stack via
the Compose Manager Plus plugin. Both in docs/unraid.md.
Bring your own reverse proxy. If you already run Traefik, nginx, or your own Caddy in your homelab, swap the bundled-Caddy compose for the BYO variant:
docker compose -f docker-compose.byo.yml up -d
That exposes the web UI on :7700, the controller API on :7701, and the
Icecast stream on :7702 (all configurable). Point your proxy at those three.
docker/Caddyfile is the canonical route table. Copy-paste nginx, Nginx Proxy
Manager, Traefik, and Cloudflare Tunnel configurations are in
docs/reverse-proxy.md; deployment context remains in
DEPLOY.md.
Images on GHCR. Tagged releases publish to ghcr.io/perminder-klair/subwave-{caddy,broadcast,controller,web}, the default-on subwave-analyzer (lean, multi-arch acoustic analysis) sidecar, and the opt-in subwave-tts-heavy (expressive voices) sidecar. Heavy-analysis variants — subwave-analyzer-heavy and subwave-aio-heavy (CLAP + Demucs, amd64) — are published for operators who enable "sounds-like"/vocals, plus the NVIDIA CUDA builds of that heavy stack (amd64) — subwave-analyzer-cuda for split-stack hosts via the docker-compose.analyzer-gpu.yml overlay, and subwave-aio-cuda for one-click all-in-one hosts.
All compose files pull :latest by default; pin a version with
SUBWAVE_VERSION=v1.2.3 in the root .env.
docker-compose.yml Production deploy with bundled Caddy (default)
docker-compose.byo.yml Production deploy for hosts with their own reverse proxy
docker-compose.dev.yml Local dev (broadcast + controller only; web runs separately)
controller/ Node.js controller, the AI DJ brain
src/llm/ LLM layer (AI SDK): provider registry, prompts, tools
src/broadcast/ queue, session, DJ agent, scheduler, jingles
src/music/ Subsonic client, pool picker, library tagging
src/audio/ TTS engines: Piper, Kokoro, Chatterbox, PocketTTS, cloud, Remote
src/routes/ HTTP API split by surface (public, request, onboarding, settings, …)
liquidsoap/ radio.liq, the Liquidsoap mixing pipeline
web/ Next.js 15 web UI (player, landing, admin, setup)
docker/ Caddyfile, Dockerfiles, icecast.xml.template, supervisor entrypoint
scripts/ setup, jingle generation, update, health check
mcp-subwave/ MCP server that lets an agent request songs / drive the DJ
cli/ Operator CLI (TS, run via tsx loader, no build step)
bin/subwave Operator CLI entry: setup, status, doctor, lifecycle
COPYd at image build time. radio.liq is bind-mounted, so a Liquidsoap
restart is enough after editing it./skip for listeners. Track-end is the only natural
transition; operators have an admin-only skip endpoint.https://<your-station>/listen.pls
(or /listen.m3u) into Sonos, VLC, moOde, or a car receiver and it tunes
straight in. Both are public (no auth), point at the always-served MP3 mount
(plus Opus when you've enabled it), and resolve the origin from SITE_URL
when set, otherwise the address you reached the station on (LAN / Tailscale /
custom domain). /api/now-playing also carries a stream block
(mount, format, bitrate, sampleRate, channels) for clients that want
the broadcast's shape.sonicSimilarity extension. SUB/WAVE streams with format=raw
so the transcode limits never throttle the radio, and when sonicSimilarity
is enabled the picker automatically folds Navidrome's audio-based neighbours
in as an extra track-selection source — no config, capability-probed, and a
silent no-op when the extension is absent. Any reasonably recent Navidrome
still works.radio.liq, the crossfade, voice
ducking, the LLM layer) have non-obvious constraints that are easy to
regress. Read the relevant note in CLAUDE.md before
touching them.DEPLOY.md: production deployment, updates, backup.docs/updating.md: updating on every install shape, and the half people need at 2am — how to roll back to a previous version, and how to recover a broken login, a silent stream or a restart-looping controller.docs/concepts.md: the seven things that get asked over and over — the persona tone dials, Track Shortlist vs Agentic Tools, playlists vs shows, the stem cache, private player vs stream password, dayparts at high latitude, and what a heart actually does to rotation.docs/reverse-proxy.md: complete nginx, Nginx Proxy Manager, Traefik, and Cloudflare Tunnel route recipes — including public stream, LAN-only admin.docs/unraid.md: running on Unraid — one-click from Community Applications, or the Compose Manager Plus stack.docs/tts-heavy.md: the opt-in tts-heavy voices and the default-on acoustic analyzer service — what each does and how to toggle them, including running analysis on another machine (a GPU box elsewhere on the network).SUB/WAVE is playback and automation software. It does not grant you any rights to the music you broadcast through it, and it ships with no licensed content.
Owning a file — a purchased download, a CD you ripped, anything in your Navidrome library — covers your own private listening. It does not cover public performance. The moment SUB/WAVE streams to anyone but you, you are publicly performing copyrighted works, which in most countries requires licences for two separate rights:
You are the broadcaster and you are solely responsible for clearing those rights. If you don't want to obtain licences, run the station privately (see DEPLOY.md → Make the station private), or broadcast only content you're cleared to use — music you created or own the rights to, Creative-Commons-licensed tracks, royalty-free libraries, or public-domain recordings.
This is general information, not legal advice. If you run a public station, talk to a media/IP lawyer in your jurisdiction.
SUB/WAVE is built on Liquidsoap, Icecast, Navidrome, and many other open-source projects, models, and services. CREDITS.md lists them all, with their licences.
MIT.
state/skills/<kind>/ on first boot, so you can rewrite a brief or change the news feed (BBC → your own RSS) right from the admin console — no code, no redeploy. Add your own by dropping a SKILL.md (plus optional data-fetching code) into state/skills/, hitting Rescan, and enabling it — or write one in the admin UI's built-in editor, or install one other operators shared on the community exchange. See docs/custom-skills.md./admin/playlists and the builder turns it into a recipe, resolves it against your library, and keeps recipe-backed playlists topped up as new music lands./observatory — placed by genre, lit by energy, with a full dossier per track (BPM, key, mood, embedding fingerprints, and nearest-in-vector-space neighbours). Scales from a few hundred to tens of thousands of tracks.docker compose up -d on a single Linux host. Optional Cloudflare in front for TLS.docs/custom-tts.mddocs/gpu-tts.md: running Chatterbox on an NVIDIA GPU — via the OpenAI layer, or by GPU-enabling the bundled sidecar.docs/private-station.md: locking the player and the stream behind a password — and how to tune in from apps, VLC and hardware once it's on, including a station behind reverse-proxy HTTP Basic Auth.docs/navidrome-libraries.md: keeping audiobooks / seasonal collections off air with a dedicated, library-scoped Navidrome user.docs/navidrome-traffic.md: DNS counts versus HTTP requests, the two empty-room controls, and how to identify catalogue, analysis and playback traffic.CLAUDE.md: the architecture map and one line per
non-obvious constraint, each pointing at its reasoning in
docs/internals/.CONTRIBUTING.md: how to contribute.docs/community.md: the community catalog — sharing and installing DJ skills, personas, and shows, plus the public station map. The content itself lives in getsubwave/community.getsubwave/audiomuse-import: already analysed your library in AudioMuse-AI? Copy its moods, BPM, key, energy and genre into SUB/WAVE instead of tagging everything again.SECURITY.md: reporting security issues.mcp-subwave/README.md: the MCP server.docs/api.md: the HTTP API, the in-app Connect explorer + playground, OpenAPI export, and Home Assistant / Music Assistant recipes.