█▀█ █░█ █▀█ █▀ █▀█ █░█ █▀█ █▀█
█▀▀ █▀█ █▄█ ▄█ █▀▀ █▀█ █▄█ █▀▄
A CRT-glow terminal music player with six scope modes, a Game of Life that pulses with the beat, and a tiny mascot that blinks at you.
phosphor is a single-binary terminal music player written in Rust. It plays
your local library, draws the audio in real time as one of six scope modes, and
keeps everything (theme, scope, library path, volume, repeat, shuffle) in a
small TOML file so the next run picks up where you left off.
┌──────────────────────────────────────────────────────────────────────────┐
│ █▀█ █░█ █▀█ █▀ █▀█ █░█ █▀█ █▀█ │
│ █▀▀ █▀█ █▄█ ▄█ █▀▀ █▀█ █▄█ █▀▄ v0.4.0 Theme {PHOSPHOR} ▟▀▀▀▙ │
│ ▐ ▘ ▝ ▌ │
│ ▝▀▀▀▘ │
├──────────────────────────────────────────────────────────────────────────┤
│ AUTECHRE Gantz Graf (Gantz Graf) 320kb/s 44100Hz 2:14 / 4:18 │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━◆────────────────────────────────────────── │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌─ ART ──┐ ┌─ PLAYLIST (12) ────────┐ ┌─ VU ┐ ┌─ SPECTRUM ───────────┐ │
│ │ ▓▓▓▓▓▓ │ │ ▶ Gantz Graf │ │ ▌▌ │ │ ▄▆█ ▆ ▅ ▄▃▂▁ │ │
│ │ ▓▓██▓▓ │ │ Outpt │ │ ▌▌ │ │ ▆██▇ ▇ ▇ ▆▅▄▃▂▁ │ │
│ │ ▓▓▓▓▓▓ │ │ Cipater │ │ ▌▌ │ │ ███▇ █ ▇ ▆▅▄▃▂▁ │ │
│ └────────┘ └────────────────────────┘ └─────┘ └──────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ [PLAY] track:1/12 shuf:OFF rep:OFF vol: 80% scope:SPECTRUM │
│ [TRACK] AUTECHRE . Gantz Graf │
└──────────────────────────────────────────────────────────────────────────┘
- Plays
mp3,wav,flac,ogg,m4a,aacfrom a folder (recursively) or a single file. - Six scope modes, switched live with
Tab:- WAVEFORM . classic L/R oscilloscope.
- VECTORSCOPE . X-Y stereo phase, 45° rotated.
- SPECTRUM . live FFT bars, log-warped frequency axis.
- SPECTROGRAM . scrolling time-vs-frequency heat map.
- MID/SIDE .
(L+R)/2over(L-R)/2, useful for mastering. - LIFE . Conway's Game of Life seeded from audio transients.
- Album-art panel that renders embedded cover art using half-block (
▀) pixels for square-ish 2:1 cells. - Stereo VU meter with peak hold.
- Eight built-in themes:
PHOSPHOR,AMBER,MATRIX,VAPOR,ICE,BLOOD,INK,SUNSET. Cycle witht. - Settings persist to
~/.config/phosphor/config.toml(debounced, 1s). - In-app library swap: press
l, type a path (~/expands),Enter. - Mac media-key bindings (
F7toF12). - A subtle reactive mascot in the banner that fades in on activity and flashes on strong audio transients.
- ~30 fps while audio plays, ~5 fps while paused. No needless wakeups.
- Single static binary, no daemon, no IPC, no config wizard.
phosphor needs a recent Rust toolchain (1.85+, edition 2024). Any 24-bit-color terminal with a Unicode font will render correctly. Tested on macOS Terminal, iTerm2, Alacritty, kitty, WezTerm, and GNOME Terminal.
git clone https://github.com/matejnovak/phosphor.git
cd phosphor
cargo build --release
./target/release/phosphor ~/MusicOr install the binary into your Cargo bin path:
cargo install --path .
phosphor ~/Musiccargo run --release -- ~/Musicphosphor [OPTIONS] [PATH]PATH is a music file or a directory walked recursively. With no argument,
phosphor re-opens the library from the previous run (saved in the config),
falling back to the current directory.
phosphor # resume last library
phosphor ~/Music # play a folder
phosphor album.flac # play one file
phosphor --theme MATRIX --scope life # specific look + scope
phosphor --shuffle --repeat all # party mode
phosphor --list-themes # print theme names
phosphor --help # full flag list| Flag | What it does |
|---|---|
--theme <NAME> |
Initial theme. See --list-themes. |
--scope <MODE> |
Initial scope: waveform, vectorscope, spectrum, spectrogram, midside, life. |
--no-vu |
Hide the VU meter for this run. |
--no-scope |
Hide the scope panel for this run. |
--no-art |
Hide the album-art panel for this run. |
--shuffle |
Start with shuffle on. |
--repeat <MODE> |
off, all, or one. |
--volume <PERCENT> |
Initial volume, 0 to 150. |
--list-themes |
Print all theme names and exit. |
--no-config |
Ignore the saved settings file at startup. |
--ephemeral |
Don't save changes from this run. |
| Key | Action |
|---|---|
Space |
play / pause |
n / p |
next / previous track |
← / → |
seek -10s / +10s |
Shift + ← / → |
seek -5s / +5s (fine) |
↑ / ↓ |
navigate playlist |
Enter |
play selected track |
+ / - |
volume up / down |
m |
mute toggle |
s |
shuffle toggle |
r |
cycle repeat (off, all, one) |
| Key | Action |
|---|---|
F7 / F8 |
previous / play-pause |
F9 |
next track |
F10 |
mute |
F11 / F12 |
volume down / up |
| Key | Action |
|---|---|
Tab |
cycle scope mode (six modes) |
v |
toggle VU meter |
c |
toggle scope panel |
a |
toggle album art |
t |
cycle theme |
l |
load a different music library |
? / F1 |
toggle the help overlay |
Ctrl+C |
quit |
Press l to open the path prompt, type or edit a path (~/ expands to
$HOME), then Enter to load. Esc cancels. The chosen folder is
remembered for next time and walked recursively for supported formats.
| Name | Mood |
|---|---|
PHOSPHOR |
green CRT, default |
AMBER |
early-80s amber monitor |
MATRIX |
brighter green-on-green |
VAPOR |
magenta + cyan synthwave |
ICE |
cool blue |
BLOOD |
red on red |
INK |
grayscale, near-monochrome |
SUNSET |
warm orange + pink |
Cycle live with t or pin one with --theme NAME.
phosphor writes a single TOML file at ~/.config/phosphor/config.toml.
All keys are optional; missing keys preserve defaults. Saves are debounced
(at most one write per second, plus one final flush at exit). Example:
theme = "MATRIX"
scope_mode = "spectrum"
show_scope = true
show_vu = true
show_album_art = true
shuffle = false
repeat = "all"
volume = 80
muted = false
library = "~/Music"If the file is malformed at startup, phosphor backs it up to
config.toml.bak and continues with defaults instead of refusing to run.
Use --no-config to ignore the saved file for one run, or --ephemeral
to run normally but skip writing changes back to disk.
rodio + symphonia decode and play audio. Stereo samples are tapped into
a small ring buffer through audio::TapSource while passing through to the
sink, so the visualizations don't affect playback in any way. The run-loop
draws at ~30 fps while audio is playing, drops to ~5 fps when paused, and
performs a single event::poll per frame so input stays responsive without
busy-spinning.
For a deeper walkthrough (run-loop, persistence, scope mode anatomy, how to
add a key/theme/scope), see ARCHITECTURE.md.
cargo testEvery leaf module has its own #[cfg(test)] mod tests covering its public
surface: parsers, formatters, theme lookup, settings round-trip, audio
buffer cap, DSP edge cases, Game of Life invariants, audio-ext detection,
peak meter behaviour, transport mute round-trip, artwork cache invariants.
- Requires a Unicode-aware, 24-bit-color terminal. Most modern terminals qualify (macOS Terminal, iTerm2, Alacritty, kitty, WezTerm, GNOME Terminal, Windows Terminal).
- Cover art needs a font with half-block glyphs (
▀,▄); the default font on the terminals above ships them. - Mac media keys (
F7-F12) only do anything if your terminal forwards them to the foreground app rather than handing them to the system. iTerm2 and Alacritty pass them through; Apple's Terminal generally does not.
Issues and pull requests welcome. The codebase aims for one obvious place to
do each thing (one source of truth for status messages, one parser per enum,
one renderer per scope), so when in doubt, look for the existing pattern in
ARCHITECTURE.md before adding a parallel one.
cargo fmt and cargo clippy --all-targets should pass cleanly. cargo test
should stay green; if you change behaviour that has a test, update the test.
Licensed under either of
- Apache License, Version 2.0 (
LICENSE-APACHE) - MIT license (
LICENSE-MIT)
at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.