Keyboard warrior friendly desktop music player for self-hosted Navidrome servers (Subsonic-compatible API). Built with Python/PyQt6 and a custom C++ audio engine for gapless playback.
| Albums | Now Playing |
|---|---|
![]() |
![]() |
| Tracks & Search | Artist Page |
|---|---|
![]() |
![]() |
- Gapless playback — tracks cross-fade seamlessly via the C++ audio engine
- Waveform scrubber — real-time waveform display with turntable scratch mode
- BPM detection — automatic BPM analysis cached per track (via QM DSP Library)
- Dynamic theming — accent colour extracted from album art, or pick your own
- Spotlight search — global search across artists, albums, and tracks
- Media key support — play/pause/next/prev via keyboard media keys (Windows & Linux)
- Crossfade backgrounds — blurred album art as the window background
- Now Playing queue — drag-to-reorder, favourite toggling, context menus
- Reorganizable tabs — reorder browser tabs to match your workflow
- Cast support — stream to Chromecast and AirPlay devices
- Python 3.10+
- A running Navidrome server (or any Subsonic-compatible server)
git clone https://github.com/raudraido/Sonar.git
cd SonarThere are two native components: the audio engine (plain g++/libcurl) and the
scratch-mode waveform view, a Qt6 Quick module built with CMake. The second
one is optional — the app runs fine without it, you just won't get the
scratch-mode waveform — so build.py skips it with a warning if CMake/Qt6
aren't found instead of failing the whole build.
Windows — install MSYS2 (recommended) + a matching MSVC Qt6 kit
The audio engine (audio_core.cpp) and the scratch-waveform QML plugin are
two separate native builds with different requirements: the former is a
plain g++/libcurl build, the latter must be compiled against an MSVC-built
Qt6 at the exact version pinned in requirements.txt (PyQt6-Qt6). PyQt6
on Windows bundles its own private MSVC-built Qt6 — Qt's QML plugin loader
rejects any version or compiler-ABI (MinGW vs MSVC) mismatch outright, so a
MinGW-built plugin, or one built against a different Qt6 patch version, will
compile fine but silently fail at runtime (the whole footer panel fails to
load, since footer_bar.qml's import FooterNativeWaveform 1.0 is
unconditional).
- Download and install MSYS2 — provides g++/curl/cmake
for the audio engine only:
Add
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-curl mingw-w64-x86_64-cmake
C:\msys64\mingw64\binto your Windows PATH (Search "Edit the system environment variables" → Environment Variables → Path → New) - Install the matching MSVC Qt6 kit via aqtinstall
(the official Qt binaries, fetched without the GUI installer) — keep the
version in sync with
requirements.txt'sPyQt6-Qt6pin:pip install aqtinstall aqt install-qt windows desktop 6.10.2 win64_msvc2022_64 -O C:\Qtbuild.pyauto-detects this kit underC:\Qt\<version>\msvc*and picks it over any MinGW Qt6 kit it finds (preferring an exact version match toPyQt6-Qt6). If you ever bumpPyQt6/PyQt6-Qt6inrequirements.txt, reinstall this kit at the new version too.
Linux (Debian/Ubuntu)
sudo apt install g++ libcurl4-openssl-dev cmake qt6-base-dev qt6-declarative-devLinux (Fedora/RHEL)
sudo dnf install gcc-c++ libcurl-devel cmake qt6-qtbase-devel qt6-qtdeclarative-develmacOS
brew install cmake qt6(g++/libcurl ship with Xcode command line tools: xcode-select --install)
pip install -r requirements.txtLinux only — install evdev for media key support:
pip install evdevpython build.pyThis compiles audio_core.cpp and outputs audio_core.dll (Windows) or
audio_core.so (Linux) in player/components/. On Windows it also copies
the required runtime DLLs into libs/ automatically.
It then builds the scratch-mode waveform QML plugin via CMake (into
player/native/scratch_waveform/build/). If CMake or Qt6 aren't installed,
this step is skipped with a warning — the rest of the app still works, just
without the scratch-mode waveform view. On Windows, build.py looks under
C:\Qt\ for an MSVC kit matching PyQt6-Qt6's version and builds against
that in Release config (see the install step above for why); it falls back
to a MinGW kit with a warning if no MSVC kit is found, but that fallback
plugin won't actually load at runtime.
python main.pyOn first launch you will be prompted to enter your Navidrome server URL, username, and password. Credentials can optionally be saved to your OS keyring.
python build_exe.pyThis uses PyInstaller to produce a single-file executable in dist/. The C++ .dll/.so is bundled automatically.
The Linux media key listener reads directly from /dev/input/. Your user may need to be in the input group:
sudo usermod -aG input $USER
# Log out and back in for this to take effectThe player auto-detects the correct input device at startup.
All settings are stored via Qt's QSettings (registry on Windows, ~/.config on Linux). Passwords are stored in the OS keyring — never in plain text.
Pull requests are welcome. Please open an issue first for anything larger than a bug fix.
Icosahedron is free software released under the GNU General Public License v3.0.
See LICENSE for the full license text.
This means you are free to use, study, modify, and distribute Icosahedron, provided that any distributed version (modified or not) is also released under the GPL-3.0.
Icosahedron is built on the shoulders of several excellent open-source libraries.
See THIRD_PARTY_LICENSES.md for the full list of
dependencies and their respective copyright notices and licenses.
Key dependencies include:
| Library | License | Purpose |
|---|---|---|
| PyQt6 | GPL-3.0 | UI framework |
| miniaudio | MIT-0 / Public Domain | Audio playback engine |
| mutagen | GPL-2.0+ | Audio tag reading |
| Pillow | HPND | Image processing |
| requests | Apache-2.0 | HTTP / Navidrome API |
| qm-dsp | GPL-2.0 | BPM detection |
| pychromecast | MIT | Chromecast support |
| pyatv | MIT | AirPlay support |
| keyring | MIT | Secure credential storage |
| psutil | BSD-3-Clause | Memory monitoring |
| pynput | LGPL-3.0 | Media key support |



