Skip to content

Repository files navigation

hypodj - The DJ Underneath

A single Rust daemon that speaks the MPD text protocol to clients (ncmpcpp, mpc, its own tools) and is itself an OpenSubsonic client + mpv audio player. It replaced a mopidy + mopidy-subidy Python stack in one process: MPD commands in, OpenSubsonic REST (browse / search / star / scrobble) and mpv-streamed audio out.

The name is from the Roman hypocaust (hypo- "beneath" + kaustos "burnt") - the furnace under the bath, tended out of sight. hypodj is the DJ underneath: it does the real work below while your client lounges in the warm room.

MPD client ──MPD text/TCP──▶ hypodj daemon ──OpenSubsonic REST──▶ Navidrome
(ncmpcpp, dj, dj-gui)             │                               (or any
                                  ▼                                OpenSubsonic
                             libmpv audio                          server)

HypoDJ (dj-gui) in the DJ View - the Queue beside the natural-language Claude Code DJ, with the now-playing card, up-next preview, and a live level meter

HypoDJ (dj-gui), the DJ View: the queue beside the natural-language DJ, with the now-playing card, up-next, and a live level meter.

Ethos

Driving music should be human-native and natural-language-first: say what you want in plain words ("play something calmer", "fade out in 20 minutes") and the server handles the complexity. Natural language is translated server-side into a validated plan and echoed back before it arms - never a surprise. Playback is startle-safe: pause, resume, and skip fade instead of cutting, and volume moves like a physical fader, not a step function.

Clients

HypoDJ (dj-gui) - the flagship

A ratatui terminal jukebox, pure MPD/TCP (no libmpv), event-driven via the daemon's idle-push socket with worker-thread IO so the UI never blocks on network.

  • Now-playing card with dithered album-art cover, up-next preview, and a physical volume fader
  • Four screens (F1-F4): Queue / Albums / Playlists / DJ - the DJ screen is a natural-language Claude Code DJ pane beside the queue (shown above)
  • Vim-like navigation: j/k, g/G, scrolloff; Shift+P jumps the Queue cursor to the currently-playing song
  • r starts an endless radio from the row under the cursor - a song, an album, an artist, anywhere including the search hits - and the queue keeps refilling itself
  • / incremental search with n/N match cycling and matched-substring highlight
  • : command line - verbs plus natural language with echo-before-arm confirm
  • Physical-potentiometer volume knob: perceptual dB detents, off-click pause
  • Queue markers on albums (fully / partially enqueued) and songs

dj - the CLI

Pure MPD/TCP, no libmpv. Bare dj prints a now-playing card; dj next, dj pause, dj vol 40 are quick verbs; anything else is natural language with the same echo-before-arm confirm: dj "play something calmer". dj radio starts the endless radio from whatever is playing (dj radio random from cold, dj radio off to stop).

Any stock MPD client (ncmpcpp, mpc) also works unchanged against the daemon.

What the daemon does

Area What
MPD server Hand-rolled TCP server: full ncmpcpp command surface, idle push, binary albumart/readpicture, sticker ratings, find/search, playlists
Library OpenSubsonic browse/search3, smart album lists, genres, first-class favorites (songs / albums / artists), scrobbling, TTL+LRU listing cache
Playback libmpv actor; startle-safe fades on pause/resume/skip; graduated + humanized absolute volume; sleep / wind-down / wake; smooth restart
Endless radio radio starts a never-ending radio from a thing (radio, radio random, radio song/<id>, radio album/<id>, radio artist/<id>): it plays what you pointed at - bare radio picks the deck up where it stands (resumes a paused one, starts a stopped one, never restarts what is already playing) - prefetches one batch of similar library tracks behind it, and hands the end-of-queue edge to the continuation walk, which re-seeds from what just played and so keeps moving through your own library. radio on arms the walk without touching the queue; radio off disarms and restores the configured continuation mode
Intent Deterministic capability core + typed Plan IR; the NL front-end (rules-first, optional local model) only ever emits a validated plan
Desktop MPRIS, so GNOME media controls work

Install and run (Nix)

nix run github:FamiliarTools/hypodj#dj       # the CLI (default app)
nix run github:FamiliarTools/hypodj#dj-gui   # HypoDJ, the TUI

The flake ships packages.hypodj (the daemon, libmpv wrapped), packages.hypodj-clients (dj + dj-gui), and one shared services.hypodj module for both NixOS and Home-Manager:

{
  inputs.hypodj.url = "github:FamiliarTools/hypodj";

  imports = [ hypodj.nixosModules.default ];   # or homeManagerModules.default

  services.hypodj = {
    enable = true;
    server.url = "https://navidrome.example.com";
    server.username = "you";
    server.passwordFile = config.sops.secrets."hypodj/password".path;
  };
}

The password is read at service start into a 0600 runtime config - never the Nix store (passwordCommand is the alternative to passwordFile). The service runs headless (audio = "null") by default; set audio = "device" when hypodj should own the speakers. Then point any client at mpd.bind (e.g. ncmpcpp -h 127.0.0.1 -p 6601, the default; 6600 is reserved for mopidy, so use it only if you set mpd.bind = "127.0.0.1:6600").

Layout

Crate What
crates/hypodj-core The library: config, model, subsonic, player, MPD handler, plan/executor, fade, MPRIS
crates/hypodj-daemon hypodj daemon binary + probe/play_probe live provers
crates/hypodj-nl Natural language to validated Plan IR (rules + optional local model)
crates/hypodj-client Shared client lib (MPD connection, nl handshake, routing)
crates/hypodj-cli dj
crates/hypodj-tui dj-gui (product name HypoDJ)

Building, testing, and deploying are covered in CLAUDE.md.

About

The DJ Underneath - a standalone Rust daemon that speaks MPD and is its own OpenSubsonic client + player (replaces mopidy+mopidy-subidy)

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages