Skip to content

Repository files navigation

Skewffle

Skewffle (skew + shuffle) is a weighted auto-queue service for MPD (Music Player Daemon). Like ashuffle, it watches MPD and keeps the play queue topped up with random tracks — but instead of a uniform shuffle, every track, album, and artist can carry a rating that skews its selection probability. Favorites appear more often, deprioritized music appears rarely but is never fully gone, and smart modes (whole-album adds, rediscovery of long-unplayed tracks) keep the queue both familiar and fresh.

Features

  • Keeps MPD's queue fed using ashuffle's proven event-driven core: blocks on MPD idle, reacts to queue/player/database events, maintains a configurable queue buffer, and reconnects forever after MPD outages.
  • Ratings with fallback — integer 0–10 on tracks, albums, and artists; the effective rating resolves track → album → artist → default (5). Selection weight is linear in the effective rating: a 10-rated track plays twice as often as a neutral one.
  • Rating 0 and hard bans — rating 0 removes a track from the shuffle but keeps it inside whole-album adds; a banned track never plays, not even as part of an album.
  • Exclude rules with ashuffle semantics (case-insensitive substring, AND within a rule, OR across rules), plus one-time import of an existing ashuffle excludes.yaml.
  • Album mode — occasionally (or exclusively) add a whole album, selected by album rating, with configurable probability, time-of-day/day-of-week windows, and an anti-repeat cooldown.
  • Rediscovery mode — a periodic pull toward tracks that have not played in months (or ever).
  • Play history — counts a play when ≥ 50 % (configurable) of the duration was actually listened, derived from MPD player events. Powers rediscovery and the stats page.
  • Last.fm loved tracks — periodic sync assigns loved tracks a configurable high rating (manual ratings always win; un-loved tracks revert).
  • Tag-based identity — ratings, bans, and history are keyed by normalized tags, not file paths, so everything survives renames, moves, and format changes. Fully exportable/importable.
  • Persistent shuffle state — the anti-repeat window and pool ordering survive restarts.
  • Web UI — status dashboard with live updates, library browser with inline rating/ban editing, rules management, history and stats, settings applied without restart.

Quick start

Skewffle needs Node.js ≥ 24 and a reachable MPD server.

npx skewffle

Then open http://127.0.0.1:4816. MPD location comes from the standard MPD_HOST/MPD_PORT environment variables or flags:

npx skewffle --mpd-host 192.168.1.10 --mpd-port 6600
npx skewffle --mpd-host /run/mpd/socket
npx skewffle --mpd-host password@mpd.local

Docker

docker run -d \
	-e MPD_HOST=mpd.local \
	-v skewffle-data:/data \
	-p 4816:4816 \
	ghcr.io/avol-v/skewffle

The image is published to both GitHub Container Registry (ghcr.io/avol-v/skewffle) and Docker Hub (docker.io/avolv/skewffle).

See docker-compose.example.yml for pairing with an MPD container.

Configuration

Bootstrap options (flags / environment)

These are needed before the database opens; changing them requires a restart.

Flag Environment variable Default
--data-dir SKEWFFLE_DATA_DIR $XDG_DATA_HOME/skewffle~/.local/share/skewffle
--http-host SKEWFFLE_HTTP_HOST 127.0.0.1 (Docker image: 0.0.0.0)
--http-port SKEWFFLE_HTTP_PORT 4816
--mpd-host MPD_HOST localhost (supports password@host and socket paths)
--mpd-port MPD_PORT 6600

Precedence: flag > environment variable > default.

Runtime settings (web UI, applied live)

Everything else lives in SQLite and is edited on the Settings page — no restart needed: queue buffer, shuffle window size, play-on-startup, suspend timeout, default rating, album mode (probability, minimum tracks, cooldown, time windows), rediscovery (probability, staleness threshold), play-count threshold, Last.fm credentials and sync interval. Settings and library data (ratings/bans/history) can be exported and imported as JSON/YAML (data also as CSV) from the same page.

The defaults mirror a typical ashuffle setup: queue-buffer 1, window-size 25, play-on-startup off.

How selection works

For every queue slot Skewffle decides which picker runs:

  1. Album mode (if enabled and inside an allowed time window) — with the configured probability, one album is drawn weighted by its effective rating and added whole, in album order. Banned and rule-excluded tracks are punched out; rating-0 tracks stay.
  2. Rediscovery (with its probability) — one long-unplayed track, weighted by rating.
  3. Weighted shuffle — ashuffle's window+pool algorithm with rating-weighted sampling: after a track plays it cannot repeat for at least window-size further picks, and within the pool, higher-rated tracks are drawn more often.

Tracks queued by any picker share one cooldown, so an album add will not be followed by the same songs from the shuffle. Full details in docs/selection-engine.md.

Mechanism In weighted shuffle? In album adds? In rediscovery?
Normal track (rating ≥ 1 or unrated) yes, weighted yes yes, if stale
Effective rating 0 no yes no
Banned track no no no
Matches an exclude rule no no no
Not in the MPD library anymore no no no

Migrating from ashuffle

  • The core behavior (queue buffer, window cooldown, suspend timeout, single-mode handling) is a faithful port; the defaults match ashuffle --queue-buffer 1 --tweak window-size=25 --tweak play-on-startup=no.
  • Import your excludes.yaml on the Rules page — the semantics are identical.

HTTP API

The web UI is backed by a JSON API under /api (status, SSE events, queue, player controls, library, rules, settings, history, stats, import/export, Last.fm sync). There is no authentication — Skewffle is a trusted local/LAN service and binds to 127.0.0.1 by default. Do not expose it to untrusted networks.

Development

npm install
node --run dev        # server from TypeScript sources (native type stripping)
node --run dev:web    # Vite dev server for the UI (proxies /api)
node --run typecheck
node --run test       # Node.js native test runner
node --run build      # dist/server (tsc) + dist/web (vite)

Architecture notes live in docs/. The stack is deliberately lean: Hono + @hono/node-server + yaml at runtime, node:sqlite for storage, a hand-rolled MPD protocol client, and SolidJS + Vite for the UI (dev-only dependencies).

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages