Skip to content

Rhythm Lab ​

Rhythm Lab is a separate local labeling and training tool. Its labels, predictions, checkpoints, and artifacts stay under tools/rhythm-lab/. It reads the main library mostly read-only. The explicit liked-track toggle is the narrow main-database write path. Rhythm Lab does not rewrite source audio.

powershell
python tools\rhythm-lab\rhythm_lab_cli.py serve --source .\data\library.sqlite --labels tools\rhythm-lab\database\rhythm_lab.sqlite

The writable default is the single stable database at tools/rhythm-lab/database/rhythm_lab.sqlite. Normal startup and collection saves use this same file. A database with legacy label columns must be migrated before the current Lab can open it.

Create and label a profile, train it from its declared inputs, then review its predictions before promoting a chosen artifact. Promotion makes a database-only scoring artifact available to the main runtime. Scoring does not decode audio and writes only its own classifier_key rows.

Promotion gates ​

Promotion writes one artifact pair, model.joblib and model.json, into models/classifiers/<artifact-prefix>/. The layout is flat, one current pair per prefix, and promoting again replaces it.

Three gates apply:

  • The artifact's source_catalog_uuid must equal the active library's catalog_uuid. An artifact trained against a different library is refused.
  • Calibration is required by default. Promoting an uncalibrated artifact takes an explicit opt-out.
  • The staged pair is exercised through the production scorer on a zero vector before it goes live.

The publication itself is staged. Both files are written and fsynced under a temporary .staging-<uuid> directory with their hashes fenced, then model.json is marked publication_status: "publishing", model.joblib is replaced, and model.json is replaced with the ready manifest. A pair caught half-published refuses to load.

After promoting a retrained artifact for a key that already has scores, reset that key in the main app before scoring. Scoring is incremental and skips tracks that already hold a row, so a job started without the reset finds zero work.

Classifier artifacts must record the ordered feature recipe and inputs they use. A missing or changed recipe is blocked until that profile is retrained and promoted. Missing inputs are incompatible rather than zero-filled.

The supported feature sources are SONARA, MERT, MAEST, CLAP, MuQ, and MuQ-MuLan (mulan). The default training recipe is sonara+mert+maest+clap+muq+mulan, combining all six sources.

The sonara source contributes 74 features. Those cover the Core scalars, the loudness and structure extras, the four mood values, and the MFCC, chroma, and spectral-contrast vectors. It deliberately excludes vocal_probability and the whole aggression family, to keep the baseline independent of SONARA's own bundled learned outputs.

Feature blocks are weighted during training so a 1024-dimensional MuQ block does not swamp the 74 SONARA features. Any non-empty source combination is accepted by the backend and CLI, including the five-source sonara+mert+maest+clap+muq.

The default benchmark-ablation matrix evaluates all 63 non-empty combinations of those six sources. Repeat --feature-set only to narrow the benchmark to an explicit subset. Promoted artifacts record exact ordered feature names, and runtime scoring accepts muq:<index> and mulan:<index> features when the stored vector has the expected dimension.

When a SONARA update changes data you want to replace, reset and reanalyze intentionally. Retraining, promotion, and rescoring remain separate choices for affected profiles.

Open Rhythm Lab from the main app ​

Choose a database in the main app, then click the Rhythm Lab flask in the top bar. The browser calls /api/rhythm-lab/launch, reuses an existing matching process when possible, and opens the returned local URL. The launch response is bound to the selected library catalog rather than to a numeric track ID alone.

The main set panel can also save its current ordered tracks as a Lab collection. That write carries catalog_uuid and track_uuid for every item. It does not change labels or source audio.

Standalone UI source readiness ​

The standalone UI reports current, missing, or stale state for SONARA, MERT, MAEST, CLAP, MuQ, and MuQ-MuLan. Coverage counts do not make a stale source trainable. The Training recipe selector exposes each source alone and every supported source combination; readiness and blocker text are recalculated for the selected recipe only.

Only tracks with a configured positive or negative training label in a binary profile display a LABEL badge immediately after TRAINED, using that label's display name in green for positive or red for negative, with no badge on review labels or multiclass profiles.

The training workflow uses the selected recipe to fit a model and refresh candidates. You can then benchmark variants before promoting an artifact. Library, Candidates, and Collection views use page sizes 50, 100, 200, or 500. The explicit liked-track toggle is still the only narrow main-catalog write from standalone Rhythm Lab.

Recover labels from a preserved legacy database ​

The transfer tool accepts sealed bundle format version 3. Keep the original byte-for-byte legacy Lab database backup together with its matching -wal and -shm files. That SQLite set remains the source recovery artifact. Exported JSON bundles and reports are derived working files, not replacements for the preserved database set.

Run the commands from tools/rhythm-lab/. Export uses one fixed read-only SQLite snapshot, including committed WAL frames. SQL NULL values remain null, and labels that share a duplicate path remain separate records:

powershell
python -m rhythm_lab.label_transfer export --lab-db <legacy-lab.sqlite> --output <export.json>
python -m rhythm_lab.label_transfer preview --bundle <export.json> --library-db <current-library.sqlite> --output <preview.json>
python -m rhythm_lab.label_transfer rebound --bundle <export.json> --preview <preview.json> --output <rebound.json>

Restore has this command shape:

powershell
python -m rhythm_lab.label_transfer restore --bundle <rebound.json> --library-db <current-library.sqlite> --lab-db <target-lab.sqlite> --report <report.json> [--accept-record-id sha256:...] [--apply] [--force]

The square brackets mark optional flags and are not literal command text. Run without --apply first. That default writes a report only and does not create or modify the target Lab database. Strong matches are eligible automatically. A weak match remains a recovery row unless you review it and pass its stable ID through --accept-record-id. Repeat the option to accept more than one.

Immediately before planning either a preview or an apply, restore reopens the current library database read-only. It requires the rebound target to still match the exact catalog_uuid, track_uuid, selected path, file size, and mtime. A changed binding becomes an unresolved recovery record instead of being written as a label.

Apply writes unresolved rows to classifier_label_recovery, including unaccepted weak matches, unmatched or ambiguous paths, changed file facts, stale bindings, and conflict losers. Conflict resolution is deterministic: the latest source updated_at wins; equal timestamps use the lexicographically smallest record_id. No source label record is silently discarded.

Before changing an existing target, apply copies the target Lab database and any existing -wal and -shm companions into a timestamped backup directory. Applying the same rebound bundle again is data-idempotent because bound labels and recovery rows are upserted. --force only allows the JSON report to replace an existing file. It does not override matching, conflict, or library revalidation.

Export, preview, rebound, and restore do not write the library database, source audio, or promoted model files. Only --apply writes the target Lab database; the commands also create their requested JSON files and the apply-time target backup.

Local-first DJ library analysis documentation.