Back to work
Project

djprep

Motivation

Rice has a strong culture of student DJs. Quality software exists — Mixed In Key being the standard — but after spending real money on CDJs and a mixer, paying more for analysis software feels wrong. I wanted the same workflows open-source and scriptable: BPM detection, key detection in Camelot notation, stem separation, and direct Rekordbox XML export so the results actually import cleanly into Pioneer hardware.

Mixed In Key is a black box. You can't tune BPM range priors, swap analysis backends, or script batch jobs across a library. djprep fixes that. It's a free CLI that analyzes MP3, WAV, FLAC, and AIFF files, outputs Rekordbox-compatible XML plus a JSON sidecar, and optionally separates stems using HTDemucs via ONNX Runtime.

Architecture

The project is a Cargo workspace with two crates:

  • djprep-core — Portable analysis library. Handles audio decoding (symphonia), BPM/key detection (stratum-dsp), and Camelot mapping. Compiles to native and WebAssembly. No C dependencies, no FFI — single-binary distribution.
  • djprep-cli — CLI orchestration layer. Owns file discovery (walkdir), parallel batch processing (rayon), metadata extraction (lofty), Rekordbox XML streaming export (quick-xml), and optional ONNX stem separation (ort 2.0).

The split was intentional. I wanted the analysis math reusable across targets — the CLI runs natively on macOS/Linux/Windows, and the same core compiles to WASM for the browser demo below. Feature gates control what ships where: wasm-lite for small browser builds, stems for GPU-accelerated inference in the CLI.

Key design decisions

Pure Rust audio stack. symphonia for decoding and stratum-dsp for analysis. No ffmpeg linking, no system library dependencies, no build matrix headaches across platforms. The tradeoff is less codec coverage than ffmpeg, but MP3/WAV/FLAC/AIFF covers the DJ use case.

Bounded concurrency for stems. Stem separation is GPU-bound; BPM/key analysis is CPU-bound. Running both unchecked creates thread contention. The pipeline uses rayon for parallel analysis with a bounded channel (capacity 4) feeding a dedicated stem worker thread. When the GPU is busy, analysis threads block on send, naturally matching CPU throughput to GPU capacity.

Rekordbox import workaround. Rekordbox has a long-standing bug where XML collection imports silently skip metadata updates for existing tracks. djprep generates a timestamped djprep_import_* playlist in every export. Importing via that playlist forces Rekordbox to reconcile BPM and key values. This is documented in the CLI output after every run.

Deterministic track IDs. Rekordbox uses signed 32-bit integers as primary keys. djprep generates them via FNV-1a hash of the normalized file path, masked to positive range. Same file always gets the same ID across runs.

Live browser demo

Browser Demo (WASM Lite)

The analyzer engine is lazy-loaded only when you click Load demo engine.

Limits: <= 30MB file, <= 300s decode, <= 300s analyzed at 22,050Hz mono.

state: idle

Choose an audio file to analyze in-browser

No file selected.

This demo runs the wasm-lite build of djprep-core — no server-side processing, everything happens in your browser.

How it works

  1. The page ships no WASM by default. Zero runtime cost for visitors who don't interact.
  2. Clicking Load demo engine lazy-loads the WASM module (~15KB gzipped).
  3. The browser decodes your audio file via Web Audio API.
  4. Audio is downmixed to mono, resampled to 22,050 Hz, and capped at 300 seconds.
  5. analyzePcm(...) from djprep-core runs BPM and key detection, returning Camelot notation and confidence scores.

Defensive limits

The demo enforces deliberate guardrails: 30 MB upload cap to prevent accidental large transfers, 300-second decode and analysis windows to bound browser memory, and a hard cap of ~6.6M PCM samples (300s × 22,050 Hz). A page shouldn't freeze someone's phone because they dragged in a 90-minute mix.

Why wasm-lite

The full WASM target includes byte-level audio decoding via symphonia, which bloats the bundle significantly. For a portfolio demo, first-load performance matters more than full parity with the CLI. The lite build exposes only analyzePcm(...) — it expects pre-decoded PCM from Web Audio API, keeping the artifact small and startup fast.

Heavy features (stems, batch processing, full decode stack) stay in the native CLI where resources are predictable.

Built with

RustWebAssemblystratum-dspSymphoniaONNX Runtime