|
| 1 | +# rekordbox USB Export (PDB/ANLZ) for CDJ/XDJ hardware |
| 2 | + |
| 3 | +* **Owners:** @sparkly-quasar (openboxxx) |
| 4 | +* **Implementation Status:** Partially implemented. Standalone writer proven and verified against |
| 5 | + independent parsers; in-Mixxx exporter working in a fork that compiles + links on macOS |
| 6 | + (Intel + Apple Silicon), Windows, and Linux via CI. Pending rekordbox-desktop import |
| 7 | + (verification tier 3) and physical CDJ/XDJ playback (tier 4). |
| 8 | +* **Related Issues and PRs:** |
| 9 | + * mixxxdj/mixxx#9463 — original "export crates/cues/beatgrids to rekordbox USB" request |
| 10 | + * mixxxdj/mixxx#10321 — "Rekordbox USB Export (PDB/ANLZ)" |
| 11 | + * mixxxdj/mixxx#12126 — "Abstract over library exporters" (the architectural hook) |
| 12 | + * mixxxdj/mixxx#15556 — "Add OneLibrary support" (explicitly *out of scope* here; see Non-Goals) |
| 13 | + * mixxxdj/mixxx#13293 — updated rekordbox Kaitai definitions (import-side) |
| 14 | +* **Other docs or links:** |
| 15 | + * Mixxx fork with the working exporter: |
| 16 | + [sparkly-quasar/mixxx `feat/rekordbox-usb-export`](https://github.com/sparkly-quasar/mixxx/tree/feat/rekordbox-usb-export) |
| 17 | + * Downloadable tri-platform CI builds: |
| 18 | + [openboxxx Releases](https://github.com/sparkly-quasar/openboxxx/releases) |
| 19 | + * openboxxx design docs + component status table: `libopenboxxx-export/README.md`; plus |
| 20 | + architecture, export-design, legal, research-findings (linked inline) |
| 21 | + * pyrekordbox (Dylan Jones, MIT) — ANLZ/PDB reference implementation |
| 22 | + * Deep Symmetry `crate-digger` / `dysentery` `.ksy` specs (format documentation) |
| 23 | + * rekordcrate (Jan Holthuis, MPL-2.0) — Rust PDB parser/serializer |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +> **A note from the author.** I'm a long-time Mixxx user, not a professional C/C++ developer. This |
| 28 | +> exporter grew out of experimenting with AI-assisted ("vibecoding") development, and getting a real |
| 29 | +> stick to round-trip through the parsers felt like a genuinely worthwhile use of that time — so I'd |
| 30 | +> like to offer it back to the community as a gift. I have no attachment to *my* code being the |
| 31 | +> answer; if the right outcome is that this informs a rekordcrate-based path (or anything else the |
| 32 | +> maintainers prefer), that's a win. I'm mainly hoping to move #9463 forward and to help however is |
| 33 | +> most useful, including finding CDJ hardware testers. |
| 34 | +
|
| 35 | +## TL;DR |
| 36 | + |
| 37 | +Mixxx can *import* rekordbox libraries but cannot *export* to the USB format that |
| 38 | +Pioneer/AlphaTheta CDJ and XDJ hardware reads. This proposal adds a **rekordbox USB |
| 39 | +exporter**: a writer for the unencrypted `export.pdb` (DeviceSQL) and per-track `ANLZ` |
| 40 | +(`.DAT`/`.EXT`) analysis files, plus the `PIONEER/` USB directory layout and audio-file |
| 41 | +copy — the same kind of "export my whole library to foreign DJ hardware" flow Mixxx |
| 42 | +already ships for Engine DJ / Engine Prime. |
| 43 | + |
| 44 | +The core byte-writer is built as a **standalone, Qt-free C++ library** (`libopenboxxx-export`) |
| 45 | +so the hard, fiddly serialization can be tested in isolation, with Mixxx integration as a thin |
| 46 | +adapter modeled on the existing `EnginePrimeExportJob`. A **round-trip verifier** (parse our |
| 47 | +own output back with Mixxx's in-tree Kaitai parsers and diff it) is a first-class feature, so a |
| 48 | +DJ learns whether a stick is good *before* they leave for the gig. |
| 49 | + |
| 50 | +Scope is deliberately narrowed to the **legally clean, widest-reach** target: the classic |
| 51 | +unencrypted PDB/ANLZ format. The newer encrypted OneLibrary / Device Library Plus format |
| 52 | +(#15556) is explicitly a non-goal here for legal reasons (see Non-Goals). |
| 53 | + |
| 54 | +**This is not a paper design.** The standalone writer emits a full `export.pdb` (all 20 standard |
| 55 | +tables + the 8-colour palette) and per-track ANLZ, every output cross-checked against two |
| 56 | +independent real-format parsers and diffed field-for-field against genuine rekordbox files. An |
| 57 | +in-Mixxx "Export Library to rekordbox USB" button already exists in a fork that compiles and links |
| 58 | +on macOS (Intel + Apple Silicon), Windows, and Linux via CI — downloadable builds are on the |
| 59 | +[Releases](https://github.com/sparkly-quasar/openboxxx/releases) page. What remains is the real |
| 60 | +proof: importing into rekordbox desktop and playing on physical CDJ/XDJ hardware — testers welcome. |
| 61 | + |
| 62 | +## Why |
| 63 | + |
| 64 | +**Motivation and context.** "Export to rekordbox USB" has been an open, confirmed wishlist item |
| 65 | +since 2018 (#9463), re-filed more specifically in 2021 (#10321), with no assignee, no branch, and |
| 66 | +no merged PR. It is one of the most-requested missing features because it removes the last reason a |
| 67 | +Mixxx user is forced back into rekordbox: preparing a set in open software but being unable to play |
| 68 | +it on the club's CDJs. Mixxx already reads rekordbox libraries; the export direction is the missing |
| 69 | +half. |
| 70 | + |
| 71 | +Much of the groundwork already exists in-tree, which makes the gap bounded rather than green-field: |
| 72 | + |
| 73 | +* A **working Engine DJ / Engine Prime exporter** (`Library ▸ Export Library to Engine Prime`) |
| 74 | + establishes the exact pattern for walking the Mixxx library and writing a foreign DJ database. |
| 75 | +* An **exporter-abstraction plan (#12126)** already intends to replace the Engine-specific menu item |
| 76 | + with a generic `Library ▸ Export Library…` and a common per-format interface — the natural socket |
| 77 | + for a rekordbox exporter. |
| 78 | +* **rekordbox format definitions are already in-tree**: the importer uses Kaitai-generated |
| 79 | + `rekordbox_pdb` / `rekordbox_anlz` parsers (`lib/rekordbox-metadata/`, kept current by #13293), so |
| 80 | + the *read* side of PDB/ANLZ is already described. |
| 81 | +* A **mature importer (since v2.3)** already works out the Mixxx↔rekordbox field mapping (hotcues, |
| 82 | + memory cues, loops, colors, keys) in reverse; the exporter reuses that mapping the other direction. |
| 83 | + |
| 84 | +**Pitfalls of the current solution.** |
| 85 | + |
| 86 | +* **There is no export path at all** — a Mixxx user with a CDJ has to redo their prep in rekordbox. |
| 87 | +* **Kaitai Struct can only READ.** Its C++ target generates parsers, not serializers, so Mixxx can |
| 88 | + parse PDB/ANLZ today but has no generated code to *write* them. The single biggest chunk of |
| 89 | + net-new code — a hand-written PDB/ANLZ serializer — simply does not exist yet in the project. |
| 90 | +* **No abstraction to plug into yet.** The exporter interface envisioned in #12126 hasn't been built, |
| 91 | + so today a new exporter would have to bolt onto the Engine-specific plumbing. |
| 92 | + |
| 93 | +## Goals |
| 94 | + |
| 95 | +* Add a **rekordbox USB exporter** that produces a stick a real CDJ/XDJ can browse and play from: |
| 96 | + library, playlists, per-track beatgrids, and hot/memory cues. |
| 97 | +* Deliver the serializer as a **standalone, dependency-light C++ library** with its own tests, so the |
| 98 | + risky byte-layout work is verifiable independently of a Mixxx build and the eventual Mixxx PR is |
| 99 | + mostly adapter + dialog + CMake option. |
| 100 | +* Ship a **round-trip verifier** as a first-class trust feature: parse our own USB back with the |
| 101 | + in-tree Kaitai parsers and report fidelity ("playlists 12/12, hotcues OK, beatgrids OK") before the |
| 102 | + DJ relies on it. |
| 103 | +* Land as an **upstream contribution**, implementing (and helping shape) the generic exporter |
| 104 | + interface of #12126 rather than a one-off menu item — and, ideally, retiring #9463 / #10321. |
| 105 | +* Provide an **in-app "report an export bug" flow** that opens a prefilled GitHub issue from a |
| 106 | + structural diagnostic manifest (counts, sections written, verifier results, hashes — never the |
| 107 | + user's audio or full metadata), so every beta tester becomes a hardware-compatibility data source. |
| 108 | + |
| 109 | +**Audience.** Mixxx users who play on Pioneer/AlphaTheta CDJ/XDJ hardware and currently keep |
| 110 | +rekordbox around solely to export USB sticks; Mixxx maintainers who own the library-export subsystem; |
| 111 | +and beta testers with access to real CDJ/XDJ units. |
| 112 | + |
| 113 | +## Non-Goals |
| 114 | + |
| 115 | +* **OneLibrary / Device Library Plus (`exportLibrary.db`, #15556) is out of scope — for legal, not |
| 116 | + just effort, reasons.** That database is SQLCipher-encrypted (256-bit AES); writing it means |
| 117 | + circumventing an access-control measure with an extracted key, which is precisely the DMCA §1201 |
| 118 | + exposure the project is structured to avoid. It should be pursued **only** via an official |
| 119 | + AlphaTheta spec/partnership, never with a recovered key. This proposal writes **only** the |
| 120 | + unencrypted classic PDB/ANLZ format. (See openboxxx `docs/legal.md`.) |
| 121 | +* **Reading the encrypted rekordbox 6/7 app database (`master.db`).** The sanctioned import path is |
| 122 | + the user's own `rekordbox.xml`; we never decrypt their app DB. |
| 123 | +* **Full analysis fidelity in the first milestone.** Waveforms (`PWAV`/`PWV3`/`PWV4`/`PWV5`), extended |
| 124 | + `.EXT` cues with colors + labels (`PCO2`), `.2EX`, `PVBR`, My-Tags (`exportExt.pdb`), and history |
| 125 | + playlists are deferred to a follow-up phase (they are additive ANLZ sections and don't touch the |
| 126 | + PDB). The first milestone is a *playable* stick with a blank on-screen waveform, not zero playback. |
| 127 | +* **Bundling or shipping any AlphaTheta-extracted key or crate-digger (EPL) code.** Format knowledge |
| 128 | + is taken from public specs and permissively-licensed reference implementations only (see How ▸ |
| 129 | + Licensing). |
| 130 | + |
| 131 | +## How |
| 132 | + |
| 133 | +**Overview.** A standalone C++ library takes a plain intermediate model and emits bytes; Mixxx |
| 134 | +integration is a thin adapter that fills that model from the library. |
| 135 | + |
| 136 | +```text |
| 137 | +Mixxx library (tracks + cues + beatgrids + crates/playlists [+ waveforms, phase 2]) |
| 138 | + │ |
| 139 | + ▼ ExportModel (plain structs — no Qt) |
| 140 | + ┌──────────────────────────────────────────────────────────────────────┐ |
| 141 | + │ libopenboxxx-export (standalone, Qt-free) │ |
| 142 | + │ mapping/ Mixxx-model → rekordbox-model (colors, keys, ms↔frame,…) │ |
| 143 | + │ pdb/ export.pdb writer — LITTLE-endian DeviceSQL pages │ |
| 144 | + │ anlz/ ANLZ .DAT/.EXT — BIG-endian tagged sections │ |
| 145 | + │ usb/ PIONEER/ dir layout, ANLZ path assignment, audio copy │ |
| 146 | + │ verify/ round-trip: parse our output w/ in-tree Kaitai + diff │ |
| 147 | + │ diag/ structural diagnostic bundle (feeds in-app bug report) │ |
| 148 | + └──────────────────────────────────────────────────────────────────────┘ |
| 149 | + │ |
| 150 | + ▼ |
| 151 | + USB stick: /PIONEER/… + copied audio files |
| 152 | +``` |
| 153 | + |
| 154 | +The Mixxx-side adapter is a `RekordboxExportJob` that mirrors the existing `EnginePrimeExportJob`: |
| 155 | +a `QThread` with `loadIds/loadTrack/loadCrate/loadPlaylist` marshalling and |
| 156 | +`jobMaximum/jobProgress/completed/failed` signals, driving an export dialog, all behind a |
| 157 | +`REKORDBOX_EXPORT` CMake option. This is the concrete implementation of the exporter abstraction |
| 158 | +discussed in #12126, so the proposal both uses and helps define that interface. (The fork also adds |
| 159 | +a small "Cue Sheet to Tracklist" utility alongside it.) The fork compiles and links on macOS |
| 160 | +(Intel + Apple Silicon), Windows, and Linux under CI today. |
| 161 | + |
| 162 | +**Cross-platform by construction.** Byte layout is always explicit, never host-dependent: PDB integers |
| 163 | +written explicitly little-endian, ANLZ integers explicitly big-endian (separate helpers, never |
| 164 | +shared); strings encoded by hand per `device_sql_string` rules (short-ASCII mangling or `0x90` |
| 165 | +UTF-16**LE** for PDB, UTF-16**BE** for ANLZ paths/labels — no `std::wstring`/`wchar_t`); |
| 166 | +USB-internal paths always forward-slashed regardless of host OS; no FAT/exFAT-specific code (the DJ |
| 167 | +formats the stick). This is why macOS/Linux/Windows are all supported from day one, which also |
| 168 | +maximizes the beta-tester (hardware data) pool. |
| 169 | + |
| 170 | +**Testing and verification.** A four-tier ladder, cheapest first: |
| 171 | + |
| 172 | +1. **Round-trip self-check** — write a stick, parse it back with the in-tree Kaitai parsers, confirm |
| 173 | + it reproduces the intended model. Free; runs on every build in CI. |
| 174 | +2. **Diff vs a real rekordbox stick** — structural field comparison against a genuine |
| 175 | + rekordbox-produced stick, to catch layout mistakes the parser tolerates. |
| 176 | +3. **Import into rekordbox desktop** — behavioral smoke test. ⚠️ *Not* equivalent to hardware (the |
| 177 | + desktop app is more forgiving and may re-analyze). Standardize on **rekordbox 5** for this tier: |
| 178 | + a rekordbox 7 stick writes `export.pdb` as an empty compatibility shell and puts the real library |
| 179 | + in the encrypted OneLibrary DB, so rb7 is a misleading oracle for classic PDB. |
| 180 | +4. **Real CDJ/XDJ hardware** — the only true proof, run at milestones. Launch matrix: CDJ-2000NXS2 |
| 181 | + (legacy baseline), CDJ-3000/3000X (strict/modern), XDJ-AZ (modern all-in-one). |
| 182 | + |
| 183 | +Tiers 1–2 are exposed as a **library API** (`verify/`) and reused by the in-app verifier so the DJ |
| 184 | +gets the same check. **Current status: tiers 1–2 are green** — exports complete without errors and |
| 185 | +round-trip through both independent parsers. **Tiers 3–4 (rekordbox-desktop import and physical |
| 186 | +CDJ/XDJ playback) are the outstanding proof and have not yet been run**; this is the main thing |
| 187 | +hardware testers can help unblock. |
| 188 | + |
| 189 | +**Migration and downtime.** None — this is purely additive. It introduces a new |
| 190 | +export path (generalizing the existing Engine Prime menu item) behind a `REKORDBOX_EXPORT` CMake |
| 191 | +option, in the same spirit as the existing `ENGINEPRIME`/`__ENGINEPRIME__` gate. No existing data, |
| 192 | +schema, or user workflow changes; |
| 193 | +nothing is removed. Existing rekordbox *import* is untouched. |
| 194 | + |
| 195 | +**Licensing.** The module is **GPLv2** (Mixxx-compatible). ANLZ layout is *ported* from **pyrekordbox** |
| 196 | +(MIT → GPLv2-compatible; attributed). PDB layout is implemented from the **crate-digger `.ksy` spec** |
| 197 | +(EPL-1.0) used as a *format description*, not copied code — the same clean-room-ish posture by which |
| 198 | +Mixxx's existing rekordbox importer was built (a file format is not itself copyrightable; a specific |
| 199 | +code expression is). crate-digger's EPL code is never linked. rekordcrate (MPL-2.0) is |
| 200 | +GPL-compatible and is discussed as an alternative below. |
| 201 | + |
| 202 | +**Known unknowns / open questions.** |
| 203 | + |
| 204 | +* `track_row` "always X" magic constants and `bitmask` semantics — written from reference values; |
| 205 | + worth confirming behaviorally on hardware. |
| 206 | +* All 20 standard PDB tables (including the library-independent `columns` / `unknown_17` / |
| 207 | + `unknown_18` / `history` browse/sort menu tables) are now written, and output round-trips through |
| 208 | + both parsers. The remaining unknown is purely behavioral: whether CDJ firmware is satisfied with the |
| 209 | + exact contents — resolved only at tiers 3–4. |
| 210 | +* The **ANLZ-path hash quirk** (flagged by a prior community exporter): confirm whether newer firmware |
| 211 | + requires a specific folder-path hash before assuming free choice of `USBANLZ` folder names. |
| 212 | +* PDB page conventions and `first_page`/`last_page` semantics — pass the parsers; validate on hardware. |
| 213 | + |
| 214 | +## Alternatives |
| 215 | + |
| 216 | +1. **Use rekordcrate (Rust) for PDB export instead of a C++ writer.** rekordcrate (Jan Holthuis) is |
| 217 | + a maintainer-suggested path and already models the PDB format in Rust with serialization in view. |
| 218 | + *Objection to this proposal:* "why hand-write a C++ serializer when a Rust one exists?" |
| 219 | + *Counter-arguments:* (a) Mixxx is C++/CMake with no Rust in the build today, so adopting |
| 220 | + rekordcrate means introducing a Rust toolchain + FFI boundary into the core build — a larger |
| 221 | + architectural commitment than the exporter itself; (b) rekordcrate covers PDB but not the full |
| 222 | + ANLZ writer path, USB layout, verifier, and diagnostic bundle this proposal needs, so it is a |
| 223 | + partial solution regardless; (c) a Qt-free C++ library keeps the serializer inside the language |
| 224 | + and build system maintainers already use. **This is genuinely an open decision for maintainers**, |
| 225 | + and the standalone-library structure here is deliberately arranged so the writer could be swapped |
| 226 | + for a rekordcrate-backed one without disturbing the Mixxx adapter — the adapter/dialog/CMake work |
| 227 | + is reusable either way. Maintainer direction on C++-writer vs Rust-via-rekordcrate is explicitly |
| 228 | + solicited. |
| 229 | +2. **Generate a serializer from Kaitai instead of hand-writing.** Rejected: Kaitai's C++ write |
| 230 | + support is experimental/partial and not viable today; the read parsers stay Kaitai-generated, the |
| 231 | + writers are hand-written. |
| 232 | +3. **Hard-fork Mixxx and add export out-of-tree.** Rejected as the primary path: forking splits the |
| 233 | + community and forfeits Mixxx's test base, review, and credibility. Upstreaming into the existing |
| 234 | + export subsystem is preferred; a fork is a fallback only if upstream cannot accept the work. |
| 235 | +4. **Export `rekordbox.xml` only, and let users import that into rekordbox.** Insufficient: XML round- |
| 236 | + trips metadata but does not produce a USB a CDJ can play from standalone — it still requires the |
| 237 | + user to open rekordbox and re-export to USB, which is the exact dependency this feature removes. |
| 238 | +5. **Target OneLibrary because it's the "newest, sanctioned" format.** Rejected on legal grounds — it |
| 239 | + is encrypted; see Non-Goals. Counterintuitively the newest format is the one we must *not* |
| 240 | + reverse-engineer key-in-hand. |
| 241 | + |
| 242 | +## Action Plan |
| 243 | + |
| 244 | +* [x] Survey the state of the Mixxx export effort and confirm the in-tree hooks (Engine DJ exporter, |
| 245 | + `lib/rekordbox-metadata/`, #12126). — openboxxx `docs/mixxx-export-status.md`, |
| 246 | + `research-findings.md` |
| 247 | +* [x] **Phase 0 — standalone PDB/ANLZ writer + verifier, proven.** `libopenboxxx-export` emits a full |
| 248 | + `export.pdb` (all 20 standard tables + 8-colour palette) and per-track ANLZ (beat grids + hot/ |
| 249 | + memory cues); every output is cross-checked against two independent parsers (pyrekordbox for |
| 250 | + ANLZ, a crate-digger-spec Kaitai parser for PDB) and diffed field-for-field against genuine |
| 251 | + rekordbox ANLZ files. |
| 252 | +* [x] **Phase 1 reader path.** `openboxxx_from_mixxx` reads a real `mixxxdb.sqlite` into the export |
| 253 | + model with no Mixxx build required — validated on a ~2,900-track library. |
| 254 | +* [x] **Phase 1 in-Mixxx exporter, working in a fork.** "Export Library to rekordbox USB" button — |
| 255 | + `RekordboxExportJob` + dialog mirroring the Engine DJ exporter, behind a `REKORDBOX_EXPORT` |
| 256 | + CMake option (plus a "Cue Sheet to Tracklist" tool). Compiles + links on macOS (Intel + Apple |
| 257 | + Silicon), Windows, and Linux via CI; downloadable builds on the |
| 258 | + [Releases](https://github.com/sparkly-quasar/openboxxx/releases) page. — refs mixxxdj/mixxx#12126 |
| 259 | +* [ ] **Verification tier 3 — rekordbox desktop import.** Confirm a generated stick imports cleanly |
| 260 | + into rekordbox 5 (the classic-PDB target; not rb7, which prefers the encrypted OneLibrary DB). |
| 261 | +* [ ] **Verification tier 4 — real CDJ/XDJ hardware.** The true proof, on the launch matrix |
| 262 | + (CDJ-2000NXS2, CDJ-3000/3000X, XDJ-AZ). Hardware testers very welcome. — refs mixxxdj/mixxx#9463, |
| 263 | + mixxxdj/mixxx#10321 |
| 264 | +* [ ] Resolve the residual behavioral unknowns at tiers 3–4 (ANLZ-path hash quirk, `track_row` magic |
| 265 | + constants, exact firmware expectations for the menu tables). |
| 266 | +* [ ] Propose/align the shared exporter interface upstream so this lands as a contribution, not a |
| 267 | + fork. — refs mixxxdj/mixxx#12126 |
| 268 | +* [ ] Add the in-app "report an export bug" flow (structural diagnostic bundle → prefilled GitHub |
| 269 | + issue; no telemetry server; user reviews before it leaves the machine). |
| 270 | +* [ ] Publish a hardware compatibility matrix with beta-tester results. |
| 271 | +* [ ] Phase 2 (follow-up, additive): waveforms and extended `.EXT`/`PCO2` cues with colors + labels, |
| 272 | + `.2EX` for CDJ-3000, `PVBR`. |
0 commit comments