Skip to content

DJ Crate

Point it at the music you already own — and an AI DJ performs it. It learns your collection, builds the set, plans every transition, and plays it live through a real two-deck engine, hands-off.

A local macOS app where an AI actually DJs your albums. It analyses every track (tempo · Camelot key · genre · energy · kick pulse · source fidelity · structure · cue points · beat grid), builds a set along an energy arc with every transition guaranteed mixable, and has AI choose how each one is played — bass swap, long blend, quick cut, a filter sweep, a riser, an echo out. A real-time two-deck Web Audio engine then performs the set live: beat-locked, tempo-bent, bass swapped on the downbeat. When it's done, the whole thing exports to rekordbox.

All analysis runs on your machine. No audio is uploaded, ever. The AI planner is optional and bring-your-own-key; without it, the same engine performs the set as a deterministic beat mix.

The Perform tab mid-transition — two beat-locked waveforms, the plan ribbon naming the technique and effect, both decks bent to one tempo


What makes it different

Energy is library-relative. "Energy 8" means top ~20% of your crate, not some absolute scale that means nothing across genres. Raw features are stored per track, then z-scored and percentile-ranked into deciles 1–10 — so the number self-calibrates to whatever you actually own, and it's decoupled from tempo (a 96 BPM track can outrank a 128 BPM one). Popularity works the same way, ranked per source because Spotify's and Deezer's scales are unrelated.

Fake-FLAC detection. A 1–5 source rating measuring where the spectrum actually runs out, plus a ⚠ LOSSY flag for an MP3 wearing a lossless container. The flag is an accusation, so it's gated like one: it needs a wall — a step, then a plateau — in a lossless container, below 19.5 kHz. Measured across the development library, warm masters read 8–11 dB of step and known transcodes 13–50 dB, which is where the 12 dB threshold comes from. A dark master has low top end but no wall, so it's rated and never accused. Unlike energy this is absolute: a transcode is a transcode anywhere.

A real performance engine, not a crossfade. WSOLA time-stretch in an AudioWorklet, so a deck bends tempo without changing pitch. The incoming deck enters on a downbeat with its bass cut, mids and highs fade in, the bass swaps on a downbeat, the outgoing track fades, and the new deck glides back to its native BPM. Sync is closed-loop — the deck reports where it actually is and the rate is trimmed (±0.4%, 2 ms deadband) to null the error, rather than trusting a schedule laid down once. Decks play at a loudness trim, because the development crate spans 31.9 dB between its quietest and loudest master and an untrimmed bass swap hands over with a level jump.

AI-planned transitions that cannot touch the audio. Optionally, an AI picks a technique per transition — and the containment is structural, not a prompt instruction. sanitize() clamps every field; a model-supplied mix point is honoured only if it is literally one of that track's own analysed cues; a technique changes the EQ/fade choreography and nothing else, because planTransition is identical for all of them. No plan can move a downbeat or a playback rate. The model sees metadata — never audio.

The beat grid is three numbers. One anchor, one tempo, one bar phase — the rekordbox/Traktor model — so every beat is arithmetic and nothing can slip mid-track. Two estimators run, and the winner is whichever reproduces its own answer across the track's two halves, not whichever we prefer.

It never writes to a file it doesn't own. Add a folder as link and it's referenced in place: excluded from art embedding, and refused unconditionally by the duplicate-trash endpoint. Two irreversible operations, both fenced.


Install

Requires macOS 15+ (Apple silicon or Intel) and ffmpeg.

brew install ffmpeg
git clone https://github.com/hacksics/dj-crate.git
cd dj-crate
./djcrate start

First run creates the virtualenv, installs dependencies and launches. The app binds 127.0.0.1:8765 and opens a native window.

Run ./djcrate start once before using the Finder app. The launcher stub is compiled on demand rather than committed, so on a fresh clone double-clicking DJ Crate.app does nothing until that first terminal launch has built it. Afterwards the Finder app (and DJ Crate.command) work normally.

./djcrate restart    # after pulling code changes — the port is fixed, so stop first
./djcrate status     # is it running?
./djcrate stop
./djcrate test       # both test suites

Point it at your music in Settings → local folders, choosing per folder whether to copy it into the crate or link it in place, then press Update on the Sync & Analyze tab. Analysis is checkpointed per track, so an interrupted run resumes by pressing the same button.

Settings — local folders as a second inlet: Copy duplicates into the crate, Link reads in place and never writes


Optional: the ML tier

./djcrate setup-ml

Builds a second, isolated virtualenv (~3 GB: torch, demucs, allin1, NATTEN, madmom) and costs roughly 76 s per track on Apple silicon. Entirely optional.

What it buys you:

  • ML structure — a labelled section map (intro / verse / build / drop / breakdown / outro) instead of DSP-inferred boundaries, which gives phrase-aware mix points.
  • Vocal detection — demucs stems reveal where the singing actually is, so the planner and the ∿ mark can tell you whether there are vocals over the outro you're mixing under.
  • Materially better beat grids, and a stem-coloured waveform.

Without it, available() is False and everything falls back to DSP with no other behavioural change. The app is fully usable — sets build, transitions beat-match, performance works.

It lives in its own virtualenv because allin1 needs a torch/madmom/numpy stack that would break the main analysis env, so it is never imported in-process — it's called over a subprocess. It is also never redistributed: madmom's model files are CC BY-NC-SA, so they're fetched onto your machine from their publisher under their own terms. See THIRD-PARTY-NOTICES.md.


Optional integrations

Both are off by default and need your own credentials. Neither is required for anything core.

Integration What it adds What it needs What it sends
Spotify / Deezer ★1–5 popularity, release covers Your own Spotify app credentials — or nothing at all for Deezer An ISRC, or an artist + title string
Anthropic API AI-planned transitions Your own API key, bring-your-own-key Track metadata only — tempo, key, energy, cue times, section labels. Never audio.

The AI toggle is offered on a validated key, not a present one: POST /api/ai/test does a free models fetch first, and saving a changed key clears the validation, because a changed key is an untested key.

Artwork prefers the file's own embedded picture — free, offline, and usually the highest resolution available. Measured on the development crate, 78.5% of files already carry one, so the network path is a fallback rather than the main road. The cover URL arrives in the same response as the popularity number, so art costs no extra API calls.


The five tabs

Tab What it does
Library Browse and filter by energy, popularity, kick pulse, fidelity, key, BPM, genre. "mixes →" shows what blends well after any track
Set Builder Pick an intent — warm-up / build / peak-time / after-hours / journey / closing / chill-out — and get an ordered, beat-matched set following an energy arc. Reshape any slot and the rest re-flows
Sync & Analyze One button: import, analyse, dedup. Checkpointed and resumable
Perform Play the set hands-off, with live beat-locked waveforms
Settings Local folders, crate path, optional integrations, rekordbox export

The Library — 4,828 tracks with energy deciles, per-source star ratings, vocal and pulse marks, Camelot keys and fidelity flags

The Set Builder — a 12-track journey set against its target energy arc, every transition scored for key and tempo


How the analysis works

Each track gets BPM · Camelot key · genre · energy 1–10, plus kick coverage, source fidelity, popularity, and a full structure map with cue points and a beat grid.

  • BPM / key / genre come from embedded tags first, falling back to Essentia DSP when tags are missing.
  • Energy is always computed locally — ffmpeg loudness (EBU R128) plus librosa spectral, kick and onset features — then percentile-ranked across your library.
  • Kick coverage is the share of the track riding a beat-periodic 30–130 Hz attack. Periodicity, not just low-end level, so a sub-bass drone doesn't count. Its cuts (0.70 / 0.40 / 0.20) are deliberately round rather than fitted: the development library's distribution rises monotonically from 0.375 to 1.00 with no valley to fit to. "Low" is not "no drums" — the same record's Breaks Mix reads 0.259 where its Original reads 0.970.
  • Structure — mix points, sections, cue points, beat grid, waveform — is precomputed into the library, so it survives restarts and draws instantly.

Mix-in and mix-out land on 8-bar phrase lines with enough runway for a full transition. Cue points (beat start · mix in · drop · breakdown · outro · mix out) are snapped to the grid.

How tracks are matched: a hard tempo gate (~±6%, with half/double-time matching) and a hard key gate — a key clash is as audible as a tempo clash — plus soft weighting on energy step (±1 preferred) and genre proximity along a chill→driving axis. Sets are a greedy walk that follows the chosen arc while holding a per-intent BPM band, never repeating a song (mixes and edits of the same track collapse to one identity) and soft-penalising a repeated artist.

The full reasoning, with the measurements behind every threshold, is in docs/DESIGN.md.


Perform

▶ Perform set plays the built set through the two-deck engine. Dual beat-locked waveforms with the grid, cue points and stem colours. Upcoming slots stay editable mid-play (⤳ alternatives, 🎲 random fit); the playing track and the one already cued are locked. A background prepper upgrades upcoming tracks to ML mix points as it reaches them.

⏭ Skip to mix jumps to 8 bars before the current mix-out, to hear the transition now. ⚡ Quick-mix transitions every ~25 s as a demo.

Optional per-transition effects — filter sweeps, echo-out, noise risers — are budgeted against ~1 dB of master headroom, which is why every one of them is either strictly subtractive, a parallel send, or a hard-bounded source. The arithmetic is in docs/DESIGN.md, including why the risers are pink noise and not white (+10.1 dB of level tilt across the sweep versus pink's 1.2 dB).

Perform — the three-stage pipeline (analyse, AI plan, perform) above the structure waveform with mix-in and mix-out points


Export to rekordbox / CDJ · XDJ

DJ Crate writes the official rekordbox DJ_PLAYLISTS XML, so your crate and your sets load straight onto Pioneer players.

Everything goes into one file — <crate>/rekordbox/DJ Crate.xml — holding the whole collection plus a DJ Crate Sets folder (every saved set as an ordered playlist), All (by energy), By Genre, By Energy bands, and a Popular folder once popularity has been fetched. It's regenerated whole on every save, so saving one set never clobbers the others.

Each track carries BPM, key (Camelot → musical tonality), genre, and its energy as a 0–5 star rating plus a heat-map track colour and an E7 · melodic · 8A comment.

In rekordbox: Preferences → Advanced → Database → rekordbox xml, point it at the file, drag tracks or playlists into your collection, then Export to Device. (FLAC plays on CDJ-3000 / NXS2 / XDJ; older players need converted audio.)

Writing Pioneer's raw on-USB format (export.pdb + ANLZ) was implemented and then removed — it's only partially reverse-engineered, so hand-written media risks not loading. The XML hand-off is the supported route.


Command line

The engine runs headless:

.venv/bin/python -m dj_crate.cli analyze ~/Music/dj-crate   # scan + analyse a folder
.venv/bin/python -m dj_crate.cli list --sort energy         # browse the library
.venv/bin/python -m dj_crate.cli compat --track 8           # what mixes after track 8
.venv/bin/python -m dj_crate.cli set --intent peaktime --count 10
.venv/bin/python -m dj_crate.cli stats                      # library summary

Where things live

Path What
~/.dj_crate/config.json Settings and credentials (mode 0600, plaintext — see SECURITY.md)
<crate>/library.db The library — tracks, structure, saved sets, mix plans
<crate>/rekordbox/DJ Crate.xml The rekordbox export
<crate>/.artwork/ Cached covers — 500px thumbnails, plus originals for art we downloaded
<crate>/imported/ Copy-mode local imports, preserving each source folder's layout

Duplicates (grouped by ISRC — a genuinely identical recording, so Extended Mix vs Radio Edit is never collapsed) can be found and moved to the Trash from the Sync tab. The best copy is kept: ownership first, then lossless, bitrate, size — so a file you only linked can never be anything but the keeper.


Tests

./djcrate test                                       # both suites
node tests/run.js                                    # 137 — mixing maths, gain, effects
.venv/bin/python -m unittest discover -s tests -t .  # 1041 — structure, cues, sets, art, plans

The mixing and scheduling code is pure — times and numbers, no Web Audio calls — so the whole beat-lock path is exercised headlessly under node. Skips and expected failures in a normal run are the known-bad registry doing its job; see CONTRIBUTING.md.


Mirror tool (standalone)

flac_downloader.py is roughly wget -r --http-user=… --http-passwd=… against an HTTP Basic-Auth autoindex tree, with a resumable manifest, in one stdlib-only file. No dependencies, no install — it also runs perfectly well on its own, outside DJ Crate.

./flac_downloader.py --user USER --dest ~/Music/dj-crate --dry-run    # preview
./flac_downloader.py --user USER --dest ~/Music/dj-crate --workers 6  # fetch

You must have permission from the operator of any server you point this at.

A manifest records every URL already fetched, so re-runs never re-download and partial files resume — which is the whole reason it exists rather than a wget alias. It isn't music-specific: --ext pdf against a documentation archive works the same way. See --help for --ext, --limit, --url and crawl options; credentials can come from FLAC_DL_USER / FLAC_DL_PASS.

In-app, this is the second inlet — the power-user one. The primary route is local folders you already have. Each sync drops new files into a dated folder and skips anything already in the collection, whatever folder or URL it came from.


License

AGPL-3.0-or-later. See LICENSE.

Not a preference — an inheritance. essentia is AGPL-3.0-only, and its wheel links a GPL-built FFmpeg and FFTW, so a project importing it is AGPL or is not distributed. mutagen is GPL-2.0-or-later, compatible upward.

The practical consequence: you cannot take DJ Crate closed. Running it as a network service triggers AGPL § 13 — it binds loopback and is a single-user desktop app, so in its shipped configuration there is no one to notify; expose it and there is.

Per-dependency detail, including the one static-linking obligation that lands on anyone who packages the built virtualenv as a .app, is in THIRD-PARTY-NOTICES.md.


Contributing

Read CONTRIBUTING.md first — particularly the scope section. This is a solo-maintained project; feature requests may be closed, bug reports with a reproduction are always welcome, and PRs need a CLA line.

Comments in this codebase explain why, and cite what was measured — "resolving phase on the kick band was tried twice and BOTH made it worse (4.5 ms → 15.3 ms)". That history is why the code looks the way it does. Preserve those notes, and add the same kind when you change a heuristic.

Security issues go to a private advisory, never the issue tracker. See SECURITY.md.

About

An AI DJ for the music you own — on-device analysis, energy-arc set building, a real-time two-deck engine that performs the set, rekordbox export

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages