# nsm A terminal music player written in Rust — something like [cmus](https://cmus.github.io/), but built from scratch, with an equalizer, cue sheet support, internet radio, and SMB shares. *[Читать по-русски](README.md)* ## Features - **Library**: recursive scanning of nested folders, fuzzy search (title/artist/album), grouping by album - **Playback**: open a file directly (plays the whole album in order) or add to a playlist; multiple playlists with reordering and deletion - **Transport**: shuffle, repeat (off/all/single track), playback speed (0.25×–1.75×), mute, real seeking with position tracking - **`.cue` sheets**: a single physical file (e.g. a whole album as one FLAC) is correctly recognized as multiple tracks with proper titles and boundaries - **Equalizer**: 8 bands, custom biquad filters - **Cover art**: rendered directly in the terminal (kitty/sixel/iTerm2 protocols, falling back to block graphics) - **Spectrum analyzer**: real-time FFT (not a fake animation) — toggle cover/spectrum with `v`, expand to fullscreen with `z` (transport controls — play/pause, next/prev, seek, mute — still work while fullscreen) - **Fade-out at track end**: the last 4 seconds smoothly fade to silence — toggle with `F`, off by default - **Loudness normalization**: ReplayGain tag takes priority if present in the file, otherwise an adaptive leveler (AGC) adjusts volume on the fly — toggle with `G`, off by default - **Internet radio**: add your own stations by URL, a curated built-in list (SomaFM, Radio Paradise), station search via the open [Radio Browser API](https://api.radio-browser.info) (auto-fails over between several mirrors if one is down), ICY metadata ("now playing" straight from the stream) - **SMB shares**: connect to network folders (including anonymous/guest access) — a pure-Rust SMB client (the `smb` crate), no system dependency on `gio`/gvfs - **Listening history**: asks on first launch whether to remember what you play; stats (artist/track/date) as a chart — hourly for today, daily for the last week/month; can be toggled on/off anytime, resettable with confirmation - **New releases**: checks for new albums/EPs/singles **from artists already in your library**, via the open [MusicBrainz](https://musicbrainz.org) database (no API keys) — not a global chart, but specifically "what's new from the artists you actually listen to" - **Keyboard layout independent**: hotkeys work the same on both English and Russian (or other Cyrillic) keyboard layouts ## Supported formats Anything [symphonia](https://github.com/pdeltuvia/symphonia) can decode: **MP3, FLAC, ALAC/AAC (in `.m4a`), WAV, AIFF, OGG (Vorbis), MKV/WebM**. **Not supported: Opus** — symphonia has no native Opus decoder in any feature set (only third-party libopus bindings, which aren't used here). `.opus` files are skipped by the library scanner. ## Building ### System dependencies **Linux** — ALSA headers are required (for audio output via [cpal](https://github.com/RustAudio/cpal)): ```bash # Debian/Ubuntu sudo apt-get install libasound2-dev pkg-config # ALT Linux sudo apt-get install libalsa-devel # Fedora sudo dnf install alsa-lib-devel ``` **macOS** *(untested — this project has only been built and tested on Linux)* — no separate audio dependency needed, `cpal` talks to CoreAudio directly. Just the standard build tools: ```bash xcode-select --install ``` **Windows** *(untested — this project has only been built and tested on Linux)* — no separate audio dependency needed either (`cpal` uses WASAPI); a normal Rust install via [rustup](https://rustup.rs) (MSVC toolchain) should be enough. For cover art to render well you'll want a modern terminal with true color support — Windows Terminal is recommended; the image protocols (kitty/sixel/iTerm2) are unlikely to be supported there, so cover art will fall back to block-graphics rendering. **The SMB feature (`c` in Library)** is implemented with its own pure-Rust SMB client (the [`smb`](https://crates.io/crates/smb) crate) — no dependency on the system's `gio`/gvfs stack or its per-distro quirks. Connecting and scanning a share run on a background thread — the interface doesn't freeze even on a slow network. **Requires Rust 1.85+ (edition 2024)** — at the time of writing, on Ubuntu/Debian this means separate versioned packages (`rustc-1.91`/`cargo-1.91` and similar) rather than the default (usually 1.75). If your `rustc` is older, either install versioned packages like these via `apt` if your distro has them, or use `rustup`. **Honest testing caveat**: the core SMB logic (connecting, recursive directory listing, reading files with accurate `seek`, decoding audio over SMB) was verified against a real Samba server and works. But the sandbox this was developed in has no real sound card — `cpal` (the audio library) reliably fails to start there, and in that specific state (plus an active audio-engine thread) a hang was found when listing an SMB directory. The root cause wasn't fully tracked down (something at the intersection of ALSA/cpal and tokio's reactor, specifically on a **failed** cpal device-open attempt) — it's possible this is specific to the no-sound-card condition and won't occur where cpal opens a stream successfully. The SMB connection now runs on a background thread regardless, so even if this hang does occur, the whole interface won't freeze — only that one connection attempt will. If you do hit this, let us know — with working audio to test against, it'll narrow things down a lot faster. No extra system dependencies are needed on any OS for internet radio (**SomaFM**, **Radio Paradise**, etc.) or MusicBrainz — TLS is handled by `rustls` (pure Rust, no OpenSSL). ### Build ```bash git clone cd nsm cargo build --release ``` The binary will be at `target/release/nsm` (on Windows — `target\release\nsm.exe`). ### Cross-compiling a Windows binary from Linux ```bash # 1. Add the Windows target (needs rustup — the normal Rust install via rustup.rs) rustup target add x86_64-pc-windows-gnu # 2. Install mingw-w64 — the Windows cross-compiler/linker sudo apt-get install gcc-mingw-w64-x86-64 # Debian/Ubuntu; use your own package manager otherwise # 3. Tell cargo which linker to use for this target mkdir -p .cargo cat >> .cargo/config.toml <