wavefm
v0.2.0
Published
WAVELENGTH in your terminal — a synced, terminal-styled YouTube listening room. Real audio via mpv, the full web UI as an Ink TUI.
Maintainers
Readme
wavefm
WAVELENGTH in your terminal. A synced, terminal-styled YouTube listening room as a rich Ink TUI — with real audio. Join a room, queue tracks, chat, react, vote-skip, and stay sample-accurately in sync with everyone else listening, all from your shell.
The in-room UI is a full-window, two-panel player (think cmus / ncmpcpp): a full-width header bar, a left PLAYER panel (now-playing + a big animated spectrum visualizer), and a right CONSOLE panel (scrollback + a single command input). It fills the whole terminal and re-flows on resize. You still drive everything by typing one command line — no modal popups, no hotkeys to memorise.
This is a CLI front end for the live WAVELENGTH server at https://timeogilson.be/wave. It speaks the same Socket.IO protocol and REST API as the web app and the SSH gateway.
Quick start
npx wavefmThat's it. On first run you'll be prompted for the room password (the same one
the web login screen takes). Your session cookie is then saved to
~/.config/wavefm/session.json (valid ~30 days), so subsequent runs skip the
prompt.
Install it permanently
npx wavefm downloads and runs the latest build each time. To get a standing
command you can just type, install it globally — then launch with wave or
wavefm (the same program, two names):
npm install -g wavefm
wave # or: wavefmUpdate a global install with npm install -g wavefm@latest.
After login you land on the room gate (exactly like the web app): create a
new room or join one by id — there is no default/auto-join room. Pass --room
to deep-link straight into a specific room and skip the gate:
npx wavefm --room ABCD-1234 --name yourhandleAudio: mpv (preferred) or ffplay
wavefm plays real audio by streaming from the server and driving an external
player:
- mpv (strongly preferred) — gives sample-accurate sync (live seek + micro speed-nudges), live volume, and a 10-band EQ.
- ffplay (fallback, ships with ffmpeg) — "close" sync only (~1–2s, re-seek based). No live volume/EQ/speed; pause becomes stop-and-respawn.
- neither —
wavefmstill runs fully (silent) in--no-audiomode and prints an install hint. The UI, progress bar, and sync all work; you just won't hear anything.
Installing mpv
| OS | Command |
| ------- | ---------------------------------------------------- |
| macOS | brew install mpv |
| Windows | winget install mpv.io or scoop install mpv |
| Linux | sudo apt install mpv / dnf install mpv / pacman -S mpv |
ffplay comes with ffmpeg (brew/apt/dnf/pacman install ffmpeg, or
winget install Gyan.FFmpeg on Windows).
Flags
| Flag | Description |
| -------------------- | ------------------------------------------------------------------------ |
| --server <url> | Base URL. Default https://timeogilson.be/wave (origin + path prefix). |
| --room <id> | Deep-link straight into a room (skip the gate). Omit it to pick/create at startup. |
| --name <handle> | Display name to join as. |
| --password <pw> | Skip the login prompt (e.g. for scripts; prefer the prompt). |
| --no-audio | Run the UI without spawning a player (silent). |
| --player <path> | Force a specific player binary (mpv or ffplay; basename decides kind). |
| wave logout | Clear the saved session for the current server and exit. |
Environment
MPV_PATH/FFPLAY_PATH— explicit player binary paths.WAVE_CLI_DEBUG=1— write a debug log to a file (never to stdout, which Ink owns).
The console (how you drive it)
Once you're in a room the screen is a full-window, two-panel layout under a
full-width header bar (▌WAVELENGTH · <room>#<id> · <N listening> · <clock>):
╭ ▌WAVELENGTH ─────────────────── late night#AB12 · 2◉ · 14:59 ╮
│ > Harder Better Faster Stronger │ timeo joined │
│ Daft Punk │ ❯ search daft punk │
│ 1:23 ━━━━━━━━─────────── 3:45 │ 1 One More Time 5:20 │
│ ♪█████░ 80% ⟳off ⤮off │ 2 Around the World 7:09 │
│ │ timeo this slaps │
│ ▄ █ ▆ ▃ │ │
│ █ █ ▅ █ ▇ ▃ █ ▄ █ ▂ │ │
│ next › Genesis │ ❯ type a song… ▌ │
╰─────────────────────────────────┴─────────────────────────────╯- PLAYER panel (left) — the visual centerpiece. Top: the read-only,
auto-updating now-playing readout (play/pause glyph + title + author, a live
progress bar with times, and a meta line —
♪vol% · repeat · shuffle, plusbuffering…/host-lockwhen relevant). Bottom: a compact spectrum visualizer (a short row of discrete bars, audio-reactive on mpv) anchored above thenext › <up-next>line. - CONSOLE panel (right) — the scrollback log fills the panel: system events
(
X joined), chat (name message), your command echoes (❯ play 1), and numbered search/queue results, newest at the bottom. The single, always-focused command input (❯) is pinned at the bottom — the only place you type.
The whole thing fills the terminal and re-renders on resize; the visualizer and panels recompute their size to fit. On very narrow terminals (below ~56 columns) the two panels stack vertically instead (player on top, console below) so nothing is squeezed into an unreadable column.
After you press Enter, the command runs, is echoed into the scrollback, and the input clears immediately and stays focused — the typed line never lingers in the field.
Input keys (the input owns the keyboard — there are no global hotkeys):
| Key | Action |
| -------------- | -------------------------------------------- |
| enter | run the line (then the field clears) |
| ↑ / ↓ | command history |
| tab | autocomplete the command name |
| ctrl+l | clear the console |
| ctrl+c | quit |
The visualizer
A compact, bottom-anchored EQ — a short row of discrete vertical bars
(block glyphs ▁▂▃▄▅▆▇█, one blank column between bars, ~16–28 bars by width),
capped to a few rows tall and pinned to the bottom of the PLAYER panel (the
now-playing block and empty space take the rest). Each bar carries a slow
peak-hold cap, and its tip reads brighter than its body. Bars rise fast and
fall gracefully (attack/gravity smoothing); paused/stopped settles to a low
baseline.
Real audio reactivity requires mpv. With the mpv backend the bars pulse with
the track's actual loudness: mpv runs a pass-through astats audio filter
and exposes its per-frame RMS over the JSON IPC (af-metadata), which wavefm
maps to a smoothed energy level and shapes across the bars. With ffplay or no
audio the Node process has no access to the audio signal, so the bars show an
honest stylized animation (layered, bass-weighted sines) — lively, but not
signal-driven. Install mpv (winget install mpv.io / brew install mpv /
apt install mpv) for the reactive version.
The golden rule
Anything that isn't a known command is searched, and the top result is queued
to play next. So daft punk one more time just plays it. Chat is the explicit
say verb, so plain text always means "play", never "send a message".
Numbered results
After a search (or a bare queue listing, or history), results are
numbered and addressable by index against that last list:
play 2— queue result 2 to play nextnext 2— play result 2 right now (alias ofskip 2)queue 2— append result 2 to the end of the queuefav 2— favourite result 2
A bare number is shorthand for play <n>. rooms numbers the room list too, so
join 2 joins the second room.
Commands
play, search, skip/next, prev, pause, seek, vol, mute,
queue, now, repeat, shuffle, fav, history, rooms, join,
create, leave, room, code, lock, say, me, react, vote,
theme, eq, lyrics, settings, clear, tune, help.
Type help in the console for the full grid, or help <command> for one. A few
that used to be popup panels are now inline:
eq— print the 10 bands;eq 3 +4edits a band,eq flatresets.lyrics— print the current synced/plain lyrics window.settings— print the room + listener settings; toggle withsettings repeat all,settings shuffle on,settings autoradio off,settings crossfade 4,settings lock.rooms— list joinable rooms (numbered), thenjoin <n>.
Themes
Five terminal palettes, set or cycled with the theme command (theme, or
theme <name>): amber (default), phosphor, ice, c64, ibm.
Parity notes (honestly scoped)
- CRT toggle has no terminal analogue (no per-cell scanline shader), so it is not surfaced as a command — the stored value is kept only for web parity.
- Crossfade (
crossfadeSec > 0) becomes an instant gapless swap (single mpv instance, no dual-deck). - Visualizer is a short, bottom-anchored discrete-bar EQ. It is audio-
reactive on mpv (driven by mpv's live
astatsRMS) and a stylized animation on ffplay / no-audio (the Node process has no signal there). Animated while playing, settles flat when paused.
Requirements
- Node >= 20 (for global
fetchandnode:net). - A terminal that supports truecolor for the full theme palette (falls back to the nearest ANSI otherwise).
