Skip to content

BPM Analysis

Every audio job is analysed for tempo. The detected BPM shows up on the job page, feeds the tempo distribution chart, guides trim selections, and is folded into the download filename.

The detection cascade

Two independent beat trackers run, and their answers are combined:

flowchart TD
    A[Decode to mono 44.1 kHz WAV] --> B[Essentia RhythmExtractor2013]
    A --> C[beat_this]
    B --> D{Both produced a result?}
    C --> D
    D -- one only --> E[Use that one]
    D -- both, within 5 BPM --> F[Confidence-weighted average, +0.1 confidence]
    D -- both, further apart --> G[Prefer the higher-confidence result]
Detector Role
Essentia RhythmExtractor2013 (multifeature) Fast baseline
beat_this (CPJKU) State of the art; BPM derived from the median inter-beat interval

When the two agree within 5 BPM the result is a confidence-weighted average with a confidence bonus, because independent agreement is itself evidence. When they disagree, the more confident detector wins. If one fails entirely, the other is used alone; if both fail, no tempo is recorded.

Preprocessing

Before detection, the audio is decoded to mono 44.1 kHz WAV with a highpass filter (highpass=f=40, removing sub-bass rumble that confuses beat detection) and truncated to the first 120 seconds. Loudness normalization is deliberately skipped — it can distort transients and harm beat detection. Tempo is a global property of most tracks, and two minutes is enough to establish it without paying for the whole file.

Normalization

Beat trackers routinely return a binary multiple of the perceived tempo — half-time or double-time. Every detector result is folded into the 70–180 BPM range by doubling or halving before it is reported, so a 75 BPM track and its 150 BPM double-time reading converge on the same answer.

Results below a confidence of 0.2 are discarded rather than reported as a low-quality guess.

Caching

Analysis is keyed by a hash of the audio content, not by job ID. Downloading the same track twice — at a different quality, from a different URL, or after deleting and re-adding it — reuses the cached tempo instead of running the detectors again.

Non-blocking by design

Analysis runs in a separate process after the download completes. The job enters the analysis status, which already counts as downloadable: the file can be fetched, played, and trimmed while the tempo is still being worked out. When analysis finishes, the job moves to analysis_done and the BPM appears via SSE without a reload.

Filename tagging

A detected tempo is added to the name handed to the browser, immediately after the title:

On disk Downloaded as
Some Track.source.mp3 Some Track_94bpm.source.mp3
Some Track_vocals.mp3 Some Track_94bpm.source_vocals.mp3

Files keep their plain names on disk deliberately — the MP3 cache and the Lalal.ai stem lookup both key off them. A job with no usable tempo, and every video job, keeps its name unchanged.

Tempo distribution

GET /api/stats/bpm-clusters groups every detected tempo into 5-BPM buckets, sorted by count. This is what the dashboard's tempo chart draws.

Limits

Setting Default Meaning
BPM analysis track limit 15 min Longest audio accepted for analysis; 0 means unlimited
BPM analysis timeout 5 min Per-analysis processing timeout
ANALYSIS_SEMAPHORE_LIMIT auto, capped at 2 Concurrent analyses (operator-level); auto-sizing never exceeds 2 regardless of CPU count

Audio longer than the maximum is skipped rather than analysed at cost. See Application Settings.

Model download

The beat_this checkpoint (~81 MB) is fetched from cloud.cp.jku.at on the first analysis. In the container TORCH_HOME points at ${DATA_DIR}/.cache/torch, so it is downloaded once and survives container recreation.

Offline hosts

Without network access to that host on first use, beat_this is unavailable and the cascade falls back to Essentia alone. Tempo detection still works, with somewhat lower accuracy.

Optional dependencies

In a standalone install without Essentia or beat_this, downloads work normally and analysis is simply skipped.