// AUDIO_HAXOR — WALKTHROUGH

Tutorial index Docs hub
Progress
06 / 19

06.BPM / key / LUFS batch analysis

Three native Rust analyzers (onset-autocorrelation BPM, Krumhansl-Kessler key, BS.1770-style LUFS), a batch queue you can pause/resume, per-file caches, and a direct pipeline into smart playlists.

Where the analysis lives

Analysis is a post-scan enrichment pass that fills the bpm, key, and lufs columns on the audio library table. Frontend triggers are in frontend/js/audio.js; backend algorithms are in src-tauri/src/bpm.rs, src-tauri/src/key_detect.rs, and src-tauri/src/lufs.rs. The database columns are updated through db_update_bpm, db_update_key, db_update_lufs, and the one-shot db_update_analysis.

Kick off a batch

  • Palette actionCmd+KStart BPM/Key/LUFS Analysis.
  • ShortcutCmd+Shift+V to start, Cmd+Shift+C to stop.
  • Auto on startup — Settings → Auto Analyze Startup. When enabled, the app runs the analysis pass on all unanalyzed rows shortly after boot.
  • Per-file — context menu on any Samples row, or the analysis button inside the expanded metadata panel.

The queue targets every row where bpm, key, or lufs is NULL. It's a stream — you can stop mid-batch and resume from where you left off without losing progress.

The IPC surface

  • estimate_bpm({ filePath })f32.
  • detect_audio_key({ filePath })"C Major", "F# Minor", etc.
  • measure_lufs({ filePath })f64 (dBFS-like LUFS).
  • batch_analyze({ paths }) — runs all three in one round trip per file.
  • db_get_analysis, db_unanalyzed_paths, db_backfill_audio_meta — backfill helpers.

BPM — onset-strength autocorrelation

src-tauri/src/bpm.rs. Supported formats: WAV, AIFF, MP3, FLAC, OGG, M4A, AAC, OPUS. The pipeline:

  1. Decode PCM via Symphonia, mixing to mono (first channel).
  2. Cap the signal at roughly 30 seconds at 44.1 kHz so the whole pipeline stays bounded in memory.
  3. Compute an energy envelope / onset-strength function.
  4. Autocorrelation over the 50–220 BPM range to find the dominant period.
  5. Convert the period back to beats-per-minute and return Some(bpm) or None.

No external deps, no model download, no Python. Works offline.

Key — 12-bin chromagram + Krumhansl-Kessler

src-tauri/src/key_detect.rs. The classic musical-key-detection pipeline:

  1. Decode first ~30 s of PCM.
  2. Frame with a Hann window.
  3. For each frame, run Goertzel against 84 target frequencies — C1 (32.7 Hz) through B7 (3951 Hz), 7 octaves × 12 pitch classes.
  4. Sum into a 12-bin chromagram (C, C#, D, …, B).
  5. Normalize by the maximum bin.
  6. Match against 24 key profiles (12 major + 12 minor Krumhansl-Kessler) using Pearson correlation.
  7. Return the highest-correlated key name, e.g. "D Minor".

Accuracy is good on tonal music; noisy percussive loops will sometimes come back with a close but not perfect guess.

LUFS — simplified BS.1770

src-tauri/src/lufs.rs. A simplified ITU-R BS.1770 implementation — integrated loudness without the K-weighting filter, computed across the first 60 seconds of the file:

  1. Read PCM, mix to mono.
  2. Compute mean square of samples (Σx² / N).
  3. LUFS = -0.691 + 10 × log10(mean_sq).
  4. Clamp silence to -70.0 LUFS.
  5. Round to one decimal place.

This gives you a comparable loudness number across your library — good enough for sorting loud vs quiet samples and building smart playlists like "LUFS > -14 AND BPM BETWEEN 120 AND 128". It is not a broadcast-certified EBU R128 measurement.

Caching & stopping

Results are written to SQLite immediately as they come in (db_update_bpm etc.), so stopping mid-batch never loses progress. The in-memory caches _bpmCache[path], _keyCache[path], and _lufsCache[path] are also updated so the UI reflects new values instantly.

To wipe analysis entirely and re-run from scratch, use Settings → Clear analysis cache (Cmd+Shift+9), then trigger the batch again.

Consuming the analysis

  • Sort the Samples table by BPM, key, or LUFS (click column headers).
  • Build Smart playlists with bpm_range, key, or LUFS-based rules.
  • View the BPM distribution and Key distribution cards in the Heatmap dashboard.
  • Compare analysis snapshots across scan runs in the History tab.
TipLet the analysis run in the background while you're organizing other things. The UI-idle backoff in ui-idle.js will lower the analysis priority when you're interacting with the app, so you won't feel a frame drop.