Spaces:
Sleeping
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
Field Notes write-up
Social post
- Thread: https://x.com/ratandeep/status/2066658403762798650
- Post: https://x.com/ratandeep/status/2066657171073532119
Open trace
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/gradioso 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 fromcontent.pyfor 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
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.