Stack: Python 3.9+, SQLite (stdlib only for core), librosa + mutagen (analysis), torch + allin1 (cue detection), CLAP + UMAP + HDBSCAN (embeddings/clustering)
Entry point: multidj (primary), mixxx-tool (legacy alias)
DB default: ~/.multidj/library.sqlite
Last verified: 2026-05-28
Package structure (pyproject.toml, __init__.py, __main__.py)
db.py — connect() (read-only URI + read-write), resolve_db_path(), ensure_not_empty(), migration runner
backup.py — timestamped .sqlite copies to ~/.multidj/backups/ before every write
models.py — LibrarySummary dataclass
utils.py — emit() for JSON / human-readable output
constants.py — genres, crate prefixes, regex patterns, CAMELOT_KEY_MAP, KNOWN_ADAPTERS
config.py — load_config(), save_config(), get_music_dir(), get_llm_config(); ~/.multidj/config.toml
cli.py — argparse with global --json / --db flag hoisting; all subcommands wired
adapters/base.py — SyncAdapter ABC (import_all, push_track, full_sync)
adapters/mixxx.py — MixxxAdapter: import + sync + crate sync + hot cue sync
adapters/directory.py — DirectoryAdapter: import tracks from filesystem paths
pipeline.py — run_pipeline(): chains all 13 steps, one backup at start, step isolation, --skip-* flags
Migrations: 001_initial.sql, 002_cue_points.sql, 003_crates_add_type.sql, 004_cue_points_add_confidence_source.sql, 005_embeddings.sql
import mixxx — one-time pull from ~/.mixxx/mixxxdb.sqlite; dry-run + apply
import directory PATH — scan a directory for audio files and import; --apply, --no-backup
sync mixxx — push dirty tracks + crates + hot cues back to Mixxx; dry-run + apply
scan — active track counts (genre, BPM, key, rating), crate count
backup — manual backup with optional --backup-dir
audit genres — genre distribution, case collisions, emoji/uninformative detection
audit metadata — field coverage percentages across active tracks
audit mismatches — detect artist/title swap mismatches vs filenames
clean genres — case normalization, uninformative → NULL, whitespace; --apply, --limit
clean text — whitespace strip/collapse + trailing garbage cleanup in title/artist/album; --apply
parse — extract artist/title/remixer from filenames; confidence tiers; --apply, --force, --min-confidence, --limit
enrich language — detect Hebrew tracks by Unicode range; read-only report
analyze bpm — librosa beat tracking (start/mid/end windows); reports variable-BPM; --apply, --force, --limit
analyze key — Krumhansl-Schmuckler key detection via librosa/chroma; --apply, --write-tags, --force, --limit
analyze energy — librosa RMS × spectral centroid, normalized 0–1; --apply, --force, --limit
analyze embed — CLAP 512-dim audio embeddings (3-window sampling, mean-pooled); --apply, --force, --limit
analyze cues — structural segmentation via allin1 (neural) + librosa cross-validation; detects intro/verse/chorus/drop/outro; high-confidence cues synced to Mixxx; --apply, --force, --limit
cluster vibe — UMAP (512d→10d) + HDBSCAN → Vibe/ crates; LLM naming via OpenAI-compatible API; --apply
similar <track> — KNN cosine distance in embedding space; --top N
cues clear — remove all auto-detected cues and reset intro_end/outro_start; --apply
crates audit — three-tier classification (catch-all / auto / hand-curated), threshold report
crates hide/show/delete — bulk crate management; --apply, --min-tracks
crates rebuild — delete all auto-crates, recreate from DB; config-driven: Genre:/Lang:/BPM:/Key:/Energy:/Vibe:; --apply
dedupe — artist+title and filesize+duration matching; --apply, --by
triage — mpv-based keyboard audition: KP0=soft-delete, Shift+KP0=hard-delete (rm file), KP1–5=rating, n=skip, ←/→=±30s; --crate, --limit
report dashboard — standalone interactive HTML dashboard; --output
pipeline — chains all 13 steps: import→fix_mismatches→parse→dedupe→bpm→key→energy→cues→embed→cluster→genres→clean_text→crates→sync→report; --apply, --skip-<step>, --music-dir, --limit
config set-db / set-music-dir / show — persistent config management
Phase 13 — Automatic Cue Detection (2026-05-28)
Phase 16 — Triage Player (2026-05-28)
Phase 12 — Semantic Embeddings + Clustering (2026-05-27)
multidj/migrations/005_embeddings.sql — embeddings table: (track_id PK, model_name, vector BLOB, created_at)
multidj/embed.py — analyze_embed(), find_similar(), load_clap_model(), store_embedding(), load_embeddings_from_db()
Model: laion/larger_clap_music (512-dim float32 vectors)
3-window sampling: start / mid / end × 30s, mean-pooled
multidj/cluster.py — cluster_embeddings(), cluster_vibe(), name_cluster()
UMAP: 512d→10d (cosine metric); HDBSCAN: automatic cluster count; noise → Vibe/Unclassified
LLM naming via [llm] config; fallback to Vibe/Cluster-NN
PoC verified (2026-05-27): 35 tracks encoded, 3 clusters found, 4 Vibe/ crates written
271 tests passing (0 failures)
Fixture: 10-track canonical dataset covering duplicates, Hebrew, case collisions, missing metadata
Tests: test_import, test_import_directory, test_sync, test_scan, test_audit, test_enrich, test_parse, test_clean, test_crates, test_dedupe, test_analyze, test_analyze_energy, test_config, test_mixxx_crate_sync, test_pipeline, test_embed, test_cluster, test_llm_config, test_triage, test_analyze_cues, test_mixxx_cue_sync, test_migrations, test_report, test_session_changes
Phase
Description
Status
0
Package rename mixxx_tool → multidj
Done
1
DB layer, migration runner, schema v1
Done
2
import mixxx — one-time pull from Mixxx
Done
3
Port all commands to MultiDJ schema
Done
4
sync mixxx — push dirty tracks back to Mixxx
Done
5
Remove mixxx-tool alias
Deferred
6
Standalone ingestion — import directory, analyze bpm/energy, config-driven crates
Done
7
Mixxx crate sync
Done
8
Fingerprint enrichment — pyacoustid → AcoustID for unknown tracks
Planned
9
Cue point detection
Done (as Phase 13)
10
Mixxx cue sync
Done (as Phase 13)
11
MCP server — expose commands as agent-callable tools
Planned
12
Semantic embeddings — CLAP → UMAP+HDBSCAN → Vibe/ crates
Done
12b
Similarity queries — multidj similar KNN search
Done
13
Automatic cue detection — allin1 + librosa → intro/drop/outro → Mixxx hot cues
Done
14
MCP embedding/playlist tools
Planned
15
Natural language DJ — LLM → playlist
Vision
16
Triage player — multidj triage mpv + Lua keyboard-driven audition
Done
Crate Name
BPM Range
BPM:<90
0 – 89
BPM:90-105
90 – 104
BPM:105-115
105 – 114
BPM:115-125
115 – 124
BPM:125-130
125 – 129
BPM:128-135
128 – 134
BPM:135-160
135 – 159
BPM:160-175
160 – 174
BPM:175+
175+
Note: 125–130 and 128–135 intentionally overlap — tracks at 128–130 BPM appear in both Tech House and Techno crates.
Known Issues / Open Items
Priority
Item
Medium
Fingerprint enrichment (Phase 8) not yet built — unknown tracks have no AcoustID lookup
Medium
MCP server (Phase 11) not yet built — commands not agent-callable
Low
mixxx-tool legacy alias still active
Low
Crates created directly in Mixxx are overwritten on next sync mixxx --apply
Low
Energy normalization is library-relative: single-track batch gets energy=0.5
python3 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/pip install -r requirements-dev.txt
source .venv/bin/activate
# Optional extras
uv sync --extra analysis # librosa — BPM, key, energy analysis
uv sync --extra embeddings # torch, torchaudio, transformers, librosa, umap-learn, hdbscan, openai, allin1 — embeddings, clustering, cue detection
# One-time bootstrap
multidj import mixxx --apply # from Mixxx
multidj import directory ~ /Music --apply # from raw files
# Daily workflow (all 13 steps)
multidj pipeline --apply
multidj pipeline --apply --skip-embed --skip-cluster --skip-cues # skip slow ML steps
# Analysis
multidj analyze bpm --apply
multidj analyze key --apply
multidj analyze energy --apply
multidj analyze cues --apply # auto-detect intro/drop/outro (requires embeddings extra)
multidj analyze embed --apply # CLAP embeddings (requires embeddings extra)
multidj cluster vibe --apply # Vibe/ crates from embeddings
# DJ tools
multidj similar " Artist - Track.mp3" # find sonically similar tracks
multidj triage --crate " New Tracks" # keyboard audition via mpv
multidj cues clear --apply # wipe all auto-detected cues
# Library maintenance
multidj scan
multidj audit genres
multidj clean genres --apply
multidj crates rebuild --apply
multidj sync mixxx --apply
multidj dedupe --apply
# Testing
.venv/bin/pytest tests/ -v # 271 tests