Skip to content

radiosilence/koan

Repository files navigation

kōan

A music player for people who give a shit about audio quality.

Screenshot 2026-03-04 at 18 30 07

Pure Rust, Ratatui TUI. Bit-perfect playback, gapless transitions, fast library indexing, Subsonic/Navidrome integration, fb2k-style format strings. No Electron. No subscriptions. No bullshit.

Screenshot 2026-03-04 at 18 30 32

Getting started

Install

# pre-built binary via mise (recommended)
mise use -g github:radiosilence/koan@latest

# or via cargo
cargo install koan-music

# or build from source
git clone https://github.com/radiosilence/koan.git && cd koan
cargo install --path crates/koan-music

Requires macOS (CoreAudio). Single binary, no runtime dependencies.

Set up your music

# Create config directory with sensible defaults
koan init

This creates ~/.config/koan/ with two config files. kōan needs at least one music source — local files, a remote server, or both.

Local files — point kōan at your music directory:

# ~/.config/koan/config.local.toml
[library]
folders = ["/Volumes/Music/library"]

Then scan:

koan scan

Indexing runs in parallel — fast even for large collections.

Remote server (Navidrome/Subsonic):

If you run Navidrome, Subsonic, or anything with a Subsonic-compatible API:

koan remote login https://music.example.com admin
koan remote sync

Remote and local tracks merge seamlessly into one library — if the same track exists in both sources (matched by artist + album + title + track number), it becomes a single entry. Local files always take playback priority; remote is only used as a fallback if the local file is missing. Remote-only tracks download on first play and cache locally — subsequent plays are instant.

Syncs are incremental by default — after the first full sync, subsequent runs only fetch albums added since the last sync. Use --full to force a complete re-sync. If a local drive is unplugged, tracks with remote backing are demoted to remote-only (streaming fallback) instead of deleted; when the drive comes back, the next scan re-merges them automatically.

You can use both sources together. Run koan remote sync periodically (or after adding music to your server) to pull new tracks.

Play something

# Open the TUI
koan

# Or play files/directories directly
koan play ~/Music/Aphex\ Twin/
koan play ~/Music/album/*.flac

# Interactive fuzzy pickers
koan pick              # search all tracks
koan pick --album      # browse albums
koan pick --artist     # browse artists

The TUI launches immediately — no waiting. If tracks need downloading (remote library), they appear in the queue with animated spinners while loading in the background.

The TUI

kōan is built around a full-screen terminal interface. The transport bar shows what's playing with album art (halfblock rendering) and a real-time spectrum analyzer, the queue groups tracks by album, and a hint bar at the bottom shows available keys for the current mode.

The basics: space to pause, </> to skip tracks, ,/. or arrow keys to seek. p opens a fuzzy track picker, a for albums, r for artists. l opens the library browser for tree-style browsing. L opens a lyrics panel. i shows track info with cover art. q to quit.

Building a queue: Use the pickers (p/a/r) or library browser (l) to find music. Enter appends to the queue, Ctrl+Enter appends and starts playing, Ctrl+R replaces the entire queue. You can also drag files from Finder straight into the terminal.

Editing the queue: Press e to enter edit mode. Select tracks with shift-arrows or ctrl-click, d to delete, j/k to reorder. Ctrl+Z undoes any queue change, Ctrl+Y or Ctrl+Shift+Z to redo. Everything is mouse-friendly too — click, drag, scroll wheel all work.

Your DAC matters: kōan sends bit-perfect audio to CoreAudio with automatic sample rate switching. No resampling, no mixing — the bits that left the encoder are the bits that hit your DAC. Run koan devices to see your audio outputs.


How it compares

Screenshot 2026-03-04 at 18 30 43

No TUI player combines bit-perfect audio, Subsonic streaming, album art, fb2k-style format strings, and file organization in one binary. Most either need a daemon, lack remote support, or skip the audiophile bits.

TUI / terminal players

kōan ncmpcpp cmus musikcube termusic rmpc stmp
Language Rust C++ C C++ Rust Rust Go
Standalone Yes No (MPD) Yes Yes Yes No (MPD) No (Subsonic)
Bit-perfect Yes Via MPD Via ALSA No No Via MPD No
Gapless Yes Yes Yes Yes Yes Yes No
Subsonic/Navidrome Yes No No No No No Yes
Local library Yes Via MPD Yes Yes Yes Via MPD No
Local + remote unified Yes
Album art Halfblock Kitty¹ No No Kitty/Sixel Kitty/Sixel No
ReplayGain Yes Via MPD Yes Yes No Via MPD No
fb2k format strings 55+ functions Column fmt Basic No No Basic No
File organization Yes No No No No No No
FTS search SQLite FTS5 MPD search Filter Text Filter MPD search Basic
Queue undo/redo 100-deep No No No No No No
Mouse support Full Yes Yes² Basic Yes Yes No
Media keys macOS CC Via MPRIS Via MPRIS Via MPRIS Via MPRIS
Drag & drop Finder → TUI No No No No No No
Lyrics Synced + plain Via MPD No Plugin No Via MPD No
Spectrum analyzer 48-band FFT No No No No No No
Favourites Yes (syncs) Via MPD No Yes No Via MPD Yes
Streaming playback Yes (256KB) Via MPD No No No Via MPD Yes
Tag editing Soon³ Via MPD No Yes Yes Via MPD No
DSP / EQ Soon³ Via MPD Yes Yes No Via MPD No
Platforms macOS³ Linux/macOS Linux/macOS/BSD Linux/macOS/Win Linux/macOS/Win Linux/macOS Linux/macOS
Maintained Yes Yes Yes (2.12.0) Slowing Yes Very active Stale

¹ PR pending, not merged. ² Added in 2.12.0. ³ Planned — see roadmap.

Desktop players (GUI)

kōan foobar2000 Strawberry DeaDBeeF
Type TUI GUI GUI (Qt) GUI (GTK)
Bit-perfect Yes Yes (WASAPI/ASIO) Yes (Linux) Yes (ALSA)
Gapless Yes Yes Yes Yes
Subsonic Built-in Plugin Built-in No
ReplayGain Track + album Scan + apply Yes Scan + apply
Format strings fb2k-compat The original Organizer only fb2k-like
File organization Yes Yes (component) Yes No
Queue undo/redo 100-deep Partial No Yes
Lyrics Synced + plain Plugin No Plugin
Spectrum analyzer 48-band FFT Plugin No Plugin
Tag editing Soon Yes Yes Yes
DSP / EQ Soon Yes (VST) Yes Yes
Platforms macOS¹ Windows/macOS All All

¹ Linux planned — see roadmap.

The gap kōan fills

  • Only standalone TUI with Subsonic + bit-perfect. stmp does Subsonic but has no gapless, no art, no local library. MPD clients need a separate daemon.
  • Only TUI with fb2k-compatible format strings. ncmpcpp has column formatting, but nothing close to $if($stricmp(%album artist%,Various Artists),...).
  • Only TUI with file organization. No other terminal player can rename/reorganize your library from inside the player.
  • Only TUI with queue undo/redo. 100-deep stack. DeaDBeeF added this in v1.10.0 (2025) — no TUI has it.
  • Only TUI with Finder drag & drop. Drop files from macOS Finder directly into the terminal to enqueue.
  • Only standalone TUI with synced lyrics. Auto-fetches synced (LRC) or plain lyrics from LRCLIB — no API key needed. Current line highlights and scrolls with playback.
  • Only TUI with a real-time spectrum analyzer. 48-band FFT on a dedicated thread, A-weighted amplitude, peak hold markers — runs at 60fps without blocking the UI.

Coming soon

  • Linux support — ALSA/PipeWire backends via trait-based audio abstraction (plan)
  • DSP pipeline — EQ, headphone correction profiles, crossfeed (plan)
  • Tag editing — inline editing, bulk operations, vimv-style external editor, MusicBrainz lookups (plan)
  • Artist metadata — artist bios, images, similar artists, discography from MusicBrainz/Last.fm (plan)
Screenshot 2026-03-04 at 18 31 01

What works

  • Bit-perfect playback — CoreAudio AUHAL, no resampling, automatic device sample rate switching
  • Gapless — decode thread keeps the ring buffer alive across track boundaries, AudioUnit never stops
  • Format support — FLAC, MP3, AAC, Vorbis, Opus, ALAC, WavPack, WAV/AIFF (via Symphonia)
  • Ratatui TUI — full-screen terminal UI with transport bar, album-grouped queue, fuzzy picker overlay, library browser, track info modal with embedded album art (halfblock rendering), real-time spectrum analyzer, scrollbar, mouse support (click-to-seek, click-to-play, drag-to-reorder, scrollbar drag, scroll wheel)
  • Spectrum visualizer — 80s hi-fi LED-segment spectrum analyzer rendered in the transport area. 48-band FFT on a dedicated analysis thread (never blocks the UI), configurable frequency scale (Bark/Mel/Log/Linear), sub-cell resolution using Unicode block characters, amplitude-based coloring (green/yellow/red near clipping), peak hold markers, and smooth exponential decay. Configurable via [visualizer] in config
  • Media keys — macOS Control Center integration via souvlaki (play/pause, next/prev, seek, now playing info with album art)
  • Library indexing — parallel metadata scanning with rayon, SQLite FTS5 full-text search
  • ReplayGain — track and album modes with peak limiting. Reads ReplayGain tags via lofty at decode time, applies gain with a configurable pre-amp. Zero overhead when disabled
  • Streaming playback — remote tracks start playing after just 256KB is buffered instead of waiting for the full download. The seek bar dims the not-yet-downloaded portion and prevents seeking past it. Duration always shows the full track length. When the download finishes, full metadata and cover art are re-read
  • Favourites — press f to star/unstar tracks (persisted to SQLite). Favourites sync bidirectionally with Navidrome — starring in kōan stars on the server and vice versa
  • Subsonic/Navidrome — incremental remote library sync (only fetches new albums after first full sync), unified local+remote browsing, streaming playback with progressive download. Resilient deduplication — unplugging a local drive demotes tracks to remote-only streaming instead of deleting them; re-scanning re-merges automatically
  • Format string engine — fb2k-compatible %field%, [conditionals], $functions() for library views and file organization
  • File organization — in-TUI organize modal: select tracks → context menu → pick a named pattern → preview moves → execute. Playlist paths update live, playback continues uninterrupted
  • Queue management — playlist-style display (played tracks stay visible dimmed), album-grouped headers, edit mode with Finder-style multi-selection (shift/option-click, shift-arrows), reorder/delete, multi-drag, undo/redo (Ctrl+Z/Y, 100-deep stack covering all playlist operations). Mouse editing (select, drag-reorder) works in any mode; double-click to skip to any track (forward or backward). Drag/drop files from Finder into the terminal to add them to the queue
  • Track deduplication — 3-strategy match (path → remote ID → content) merges local and remote into one DB row. No duplicates in search or browse. Playback priority: local file → cached download → remote stream
  • Lyrics — toggle a lyrics side panel with L. Fetches synced (LRC) or plain lyrics automatically; synced lyrics highlight the current line and scroll with playback. Lyrics are cached in the database per track
  • Proper artist handling — track artist stored separately from album artist; compilations/VA albums display correctly

Architecture

Pure Rust.

File → Symphonia → f32 samples → rtrb ring buffer → CoreAudio render callback → DAC

Lock-free audio thread. See ARCHITECTURE.md for the full technical manual.

Two crates:

  • koan-core — audio engine, player, database, indexer, format strings, file organization, remote client
  • koan-musickoan binary (Ratatui TUI)

Shell completions

Dynamic completions that know your library — artist/album IDs tab-complete from the DB.

# zsh (add to .zshrc)
source <(COMPLETE=zsh koan)

# bash
source <(COMPLETE=bash koan)

# fish
COMPLETE=fish koan | source

Then koan play --album <TAB> shows your actual albums with artist names.

CLI reference

# setup
koan init                     # create config directory with defaults
koan scan                     # scan configured library folders

# play
koan                          # open TUI (use `l` to browse library)
koan play --library           # open TUI in library browse mode
koan play ~/Music/album/      # play a directory (recursive)
koan play ~/Music/*.flac      # play specific files
koan play --album 5           # play album by ID (use tab completion)
koan play --artist 3          # play artist by ID

# search & browse
koan pick                     # fuzzy search all tracks
koan pick --album             # fuzzy browse albums
koan pick --artist            # fuzzy browse artists
koan search "radiohead"       # text search (CLI output)
koan artists                  # list all artists
koan albums                   # list all albums
koan library                  # library statistics

# remote
koan remote login URL user    # authenticate with Subsonic/Navidrome server
koan remote sync              # incremental sync (only new albums since last sync)
koan remote sync --full       # full re-sync of entire remote library
koan remote status            # show remote server info

# utilities
koan config                   # show resolved config from both layers
koan devices                  # list audio output devices
koan cache status             # show download cache size
koan cache clear              # clear cached remote downloads
koan probe track.flac         # show format/codec info for a file

Playback TUI

During playback, a full-screen Ratatui TUI shows the transport bar, queue, and key hints. The queue never goes blank during downloads — pending tracks appear immediately with animated spinners.

Key Action
space pause / resume
< > previous / next track
, . seek ±10s
seek ±10s
/ search queue (jump to track)
p pick tracks
a pick album
r pick artist
i track info
z zoom album art
f favourite / unfavourite
Ctrl+Z undo last queue change
l library browser
L lyrics panel
f filter library (in library mode)
e edit queue
g jump to start
G jump to end
PgUp / Ctrl+U page up
PgDn / Ctrl+D page down
q quit

Drag/drop: Drag files or folders from Finder into the terminal window to add them to the queue.

Picker confirm actions (track/album/artist picker):

Key Action
Enter Append to queue (don't start playing)
Ctrl+Enter Append and play first added track
Ctrl+R Replace entire queue and play

Mouse (works in any mode — modality is keyboard-only): double-click a queue track to skip to it (forward or backward); double-click a downloading track to prioritize and play it as soon as it finishes. Click the seek bar to jump, scroll wheel in queue. Single-click selects, drag to reorder. Ctrl-click for range selection, Option-click to toggle individual tracks, drag selected group to move all together. Scrollbar is clickable and draggable. In the fuzzy picker, click items to select, double-click to confirm, click outside to dismiss. In the library browser, click to select, double-click to expand/enter/enqueue; click queue pane to switch focus.

Queue edit mode (e):

Key Action
navigate
Shift+↑ extend selection
d remove selected track(s)
j / k move selected down/up
Ctrl+Z / Ctrl+Y undo / redo
Space context menu (organize)
g jump to start
G jump to end (shift-extends)
PgUp / PgDn page up/down
⌥-click toggle select
Ctrl-click range select
Esc exit edit mode

Queue display

Tracks are grouped by album with headers showing album artist, year, album title, and codec. Track artist is shown inline only when it differs from the album artist (compilations, VA albums). Downloading tracks show progress percentage, waiting tracks show braille spinners. Double-clicked priority tracks show > with progress.

 Limewax — (2007) Therapy Session 4 [FLAC]
   > 01 Agent Orange                              4:56
     02 Pigeons and Marshmellows feat. The Panacea 2:53
     03 SPL — Fade                                 1:52
     04 Icicle                                     2:27

File organization

Rename and reorganize your music library using fb2k-compatible format strings, directly from the TUI.

Select tracks in edit mode (e) → Space to open the context menu → Organize → pick a named pattern from your config → preview the file moves → execute. Playlist paths update automatically, playback continues uninterrupted (Unix rename preserves open file descriptors). Ancillary files (cover.jpg, .cue, .log) move with the music. Empty directories are cleaned up.

Files are organized into the first configured library folder (from [library] folders in your config). If you have multiple library folders, the first one is always the destination. The format pattern generates the relative path within that folder — e.g. with the standard pattern, a track becomes <library>/Artist/(Year) Album/01. Title.flac.

Define organize patterns in your config — see Configuration below and docs/format-strings.md for the full syntax reference.

Configuration

Two-layer config at ~/.config/koan/:

  • config.toml — shareable defaults, safe to commit to dotfiles
  • config.local.toml — machine-specific paths, credentials, overrides (gitignored)

Local values override base. Run koan config to see both layers and the resolved result.

Playback

# config.toml
[playback]
software_volume = false   # volume control in software (vs hardware/DAC) — not yet wired in
replaygain = "album"      # off | track | album — applies gain from ReplayGain tags with peak limiting
pre_amp_db = 0.0          # pre-amp gain in dB applied on top of ReplayGain (default: 0.0)

ReplayGain normalizes volume levels across tracks so you don't reach for the volume knob between a whisper-quiet jazz track and a wall-of-sound metal album. kōan reads standard ReplayGain tags (embedded by tools like loudgain, r128gain, foobar2000, etc.) at decode time and applies gain with peak limiting to prevent clipping.

Mode Description
off No gain adjustment. Original signal untouched.
track Per-track normalization. Every track plays at the same perceived loudness. Best for shuffled playlists.
album Per-album normalization. Preserves the dynamic range and intentional volume differences within an album (quiet intros, loud climaxes) while normalizing between albums. (recommended)

pre_amp_db adds a fixed gain on top of the ReplayGain adjustment. Positive values make everything louder (risk of clipping on hot tracks), negative values quieter. Useful if your ReplayGain-tagged library feels too quiet at the target level.

Library

# config.local.toml
[library]
folders = ["/Volumes/Music/library"]

Remote server

# config.local.toml
[remote]
enabled = true
url = "https://music.example.com"
username = "admin"

Password is prompted by koan remote login and saved to config.local.toml (gitignored).

Visualizer

# config.toml
[visualizer]
enabled = true                # show spectrum analyzer in transport area (default: true)
fps = 60                      # analysis thread update rate in Hz (default: 60)
scale = "bark"                # frequency scale (default: bark)
amplitude_scale = "aweight"     # amplitude scale (default: aweight)
bar_decay_ms = 50             # how fast bars drop — half-life in ms (default: 50)
peak_decay_ms = 180           # how long peak markers linger — half-life in ms (default: 180)

The spectrum analyzer renders above the transport text when album art is present. 48-band FFT with sub-cell resolution using Unicode block characters (▁▂▃▄▅▆▇█), peak hold markers (), and smooth exponential decay. Bars are colored by signal level — green at safe headroom, yellow when getting hot, red only near clipping (0dBFS). FFT runs on a dedicated analysis thread so the 60fps UI is never blocked.

Frequency scales (scale) — controls how FFT bins map to bars (the X axis):

Scale Description
bark Bark psychoacoustic scale — 24 critical bands, matches how your ears group frequencies. Best for music. (default)
mel Mel perceptual pitch scale — similar to Bark, widely used in speech/music analysis
log Logarithmic — equal spacing per octave. Familiar if you read spectrograms
linear Linear — equal Hz per bar. Bass is cramped, treble dominates. Analytical use

Amplitude scales (amplitude_scale) — controls how magnitudes map to bar height (the Y axis):

Scale Description
aweight A-weighted (IEC 61672). Bars reflect perceived loudness — bass and extreme treble are attenuated to match human hearing sensitivity (Fletcher-Munson). (default)
perceptual A-weighting + gentle gamma curve. Same frequency correction with a boost to quiet signals so more of the display stays active
sqrt Square root curve — gentle boost to quiet bands, no frequency correction
linear Raw dB-normalized magnitude. No correction. Quiet stuff barely visible, technically accurate

Set enabled = false to hide the visualizer entirely.

Organize patterns

The TUI organize modal picks from named patterns defined in your config. Format strings use fb2k syntax — %field% for metadata, $function() for transforms, [conditionals] to omit blocks when fields are missing. See docs/format-strings.md for the full reference.

# config.toml
[organize]
default = "standard"      # which pattern the modal selects by default

[organize.patterns]
standard = "%album artist%/(%date%) %album%/%tracknumber%. %title%"
va-aware = "%album artist%/$if($stricmp(%album artist%,Various Artists),,['('$left(%date%,4)')' ])%album% '['%codec%']'/[$num(%discnumber%,2)][%tracknumber%. ][%artist% - ]%title%"
flat = "%artist% - %title%"

The va-aware pattern handles compilations: if the album artist is "Various Artists", it includes the per-track artist in the filename and omits the redundant year prefix. The $stricmp, $if, $left, $num functions work the same as in foobar2000.

Database, download cache, and log file all live at ~/.config/koan/.

Dev

just check    # test + clippy
just fmt      # cargo fmt
just cli      # cargo run -p koan-music -- <args>

License

MIT

About

Bit-perfect terminal music player — Ratatui TUI, gapless playback, Subsonic/Navidrome streaming, spectrum analyzer, ReplayGain, lyrics, fb2k format strings. Pure Rust.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages