nightwave / README.md
ratandeep's picture
Clean tags: dedupe, drop sponsor:openai (not eligible), plain list at top of frontmatter
d7807ed verified
|
Raw
History Blame Contribute Delete
6.93 kB
---
title: NIGHTWAVE
emoji: πŸ“»
tags:
- build-small-hackathon
- track:wood
- sponsor:openbmb
- sponsor:modal
- achievement:offbrand
- achievement:llama
- achievement:fieldnotes
- achievement:sharing
- tiny-titan
- best-demo
- llama-cpp
- minicpm
- modal
- audio
colorFrom: indigo
colorTo: gray
sdk: docker
app_port: 7860
pinned: false
license: apache-2.0
short_description: A late-night radio station with a live AI DJ + call-ins
---
# NIGHTWAVE πŸ“»
**A warm, hand-built 1970s late-night radio station you actually tune into β€” and the
DJ is a 1-billion-parameter model.**
Turn the dial to 98.6 and a late-night host (voiced by Kokoro's `am_michael`) keeps you
company through the small hours. He spins records by artists who don't exist, reads
dedications, gives the weather for towns that aren't on any map, muses into the dark β€” and
**takes your call, live, on air**. Every record is **generated in your browser** in real
time (no samples, no licensing). Pick up the handset β€” or just hit **Space** β€” and talk
to him.
It's the one entry you can review with your eyes closed.
## Demo video
- Demo: https://youtu.be/lA46z2mYjF0
## Field Notes write-up
- Article: https://huggingface.co/blog/build-small-hackathon/nightwave-radio
## Social post
- Thread: https://x.com/ratandeep/status/2066658403762798650
- Post: https://x.com/ratandeep/status/2066657171073532119
## Open trace
- Dataset: https://huggingface.co/datasets/build-small-hackathon/nightwave-traces
## What makes it tick
- **A live AI DJ.** A fine-tunable **MiniCPM5-1B** (β‰ˆ1.08B params) writes the patter β€” station
IDs, late-night thoughts, fictional weather, dedications, and song intros β€” fresh every
break. He answers callers improvised around their real words: unfakeable proof the model
is live and load-bearing.
- **Music made in the browser.** A small Web Audio engine renders a different ~60s lo-fi /
ambient instrumental per track β€” varied by key, scale, tempo, and timbre β€” from a curated
bank of fictional records (title + artist + vibe). No copyrighted audio; a documented slot
to drop in your own CC0 mp3s.
- **A real broadcast signal.** Kokoro's voice runs through an AM band-pass (300 Hz–3.4 kHz),
light compression, tape saturation, a plate reverb, and a continuous vinyl-crackle bed, so
it *sounds* like a transmission β€” and the DJ talks **over** each song's intro.
- **Never dead air.** The next talk break is generated while the current record plays; a
pre-cached "stall" line covers call latency; a canned segment keeps the show going if a
model call ever fails.
- **A radio you operate.** A skeuomorphic wood-and-amber cabinet: draggable tuning dial,
audio-reactive VU meter, ON-AIR tube, spinning reels, now-playing plate, and a push-to-talk
handset (Space = talk, Esc = hang up). Reduced-motion and captions respected.
## Architecture
```
Browser (this HF Space, free CPU) Modal (one GPU app, scale-to-zero)
---------------------------------- ----------------------------------
Show loop (state machine): @modal.asgi_app (gpu=T4)
break (DJ segments) -> song -> repeat /asr faster-whisper small -> text
Generative music engine (Web Audio) /brain MiniCPM5-1B GGUF -> {text,mood,arc_cue}
Broadcast audio chain + crackle bed /speak Kokoro am_michael -> {audio,words,times}
Now-playing plate Β· sonic logo Β· VU
| fetch (same-origin /api/* ONLY)
| GET /api/songs server-side proxy (FastAPI):
| POST /api/segment {kind, ctx} builds host-persona prompts (arc.py)
| POST /api/call {audio_b64} calls /brain then /speak; sanitizes text
v holds MODAL_URL + proxy token in Secrets
Space server (FastAPI + Gradio) browser NEVER sees Modal creds
```
- The radio UI is one self-contained HTML/CSS/JS document served at `/` (top-level, so mic +
autoplay work). A minimal Gradio Blocks is mounted at `/gradio` so the Space is a bona-fide
Gradio app (and the custom cabinet counts toward **Off-Brand**).
- The DJ is **one consistent warm-witty host persona** (`arc.py: build_host_prompt`). The app
owns the show structure; the model only voices the current segment.
- Segment kinds: `station_id`, `thought`, `weather`, `dedication`, `song_intro` (LLM-written
for thoughts/intros; templated from `content.py` for the rest). All spoken via `/speak`.
### Stack
| Layer | Choice |
|---|---|
| Brain | `openbmb/MiniCPM5-1B` GGUF (Q4_K_M) via **llama.cpp** |
| ASR | **faster-whisper** small (CPU, int8) |
| TTS | **Kokoro-82M**, voice `am_michael`, with word timestamps |
| Music | In-browser **Web Audio** generative engine (no model, no samples) |
| Compute | One **Modal** T4, `min_containers=0`, scale-to-zero |
| Frontend | Self-contained skeuomorphic radio (HTML/CSS/JS), real Web-Audio chain |
| Orchestrator | Free CPU Hugging Face **Gradio Space** (Docker SDK, FastAPI + `gr.Blocks`) |
## Configuration (Space Secrets)
| Secret | Meaning |
|---|---|
| `MODAL_URL` | Base URL of the deployed Modal asgi_app. |
| `MODAL_KEY` | Modal proxy-auth key (sent as header `Modal-Key`). |
| `MODAL_SECRET` | Modal proxy-auth secret (sent as header `Modal-Secret`). |
| `NIGHTWAVE_MOCK` | `1` (or leave `MODAL_URL` unset) to run the full UI with canned data + a silent WAV β€” no Modal backend needed. |
| `NIGHTWAVE_TRACE` | `1` to log a content-only show trace (segments + calls, no audio/PII) to `traces/session.jsonl` for the open-trace dataset. |
## API (same-origin; the browser only calls these)
- `GET /api/songs` β†’ the curated song bank (drives the music engine + now-playing plate).
- `POST /api/segment` `{kind, ctx}` β†’ `{text, mood, arc_cue, audio_b64, words, wtimes}` β€” one DJ segment.
- `POST /api/call` `{audio_b64}` β†’ the DJ's live answer (+ caller transcript).
- `POST /api/seek` / `GET /api/stalls` β†’ filming seek + pre-cached latency-stall clips.
## Local development
```bash
pip install -r requirements.txt
NIGHTWAVE_MOCK=1 python devserver.py # full radio + /api/* in mock mode, no Modal
# open http://localhost:7860
pytest tests/ # arc / content / brain-contract unit tests
```
On HF Spaces the container runs `uvicorn app:app`, which serves the radio at `/`, the
same-origin `/api/*` proxy, and mounts Gradio at `/gradio`.
## Location & privacy
If you allow the browser location prompt, NIGHTWAVE sends your coordinates to the
Space only to fetch your local weather, time, and city (via Open-Meteo and
BigDataCloud) so the DJ can speak to your actual night sky. Coordinates are **not
stored or logged** β€” only the resolved city name may appear in optional traces.
Decline the prompt and the station simply gives you weather for towns that do not
exist.