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
metadata
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

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 /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

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.