v0.34.0: Calculate BPM from the audio, on the right-click menu

Spectral-flux onsets + autocorrelation in numpy, decoded by the ffmpeg CLI;
no new dependency. Runs on a worker with the status-bar progress bar, for
one track or many, and the batch is one Ctrl+Z.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 14:55:35 -07:00
co-authored by Claude Opus 5.5
parent 4c0aaae463
commit b3d6c01774
7 changed files with 504 additions and 4 deletions
+12
View File
@@ -256,6 +256,18 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
it never belongs in git). Audio under ~3 s has no fingerprint at all
("Empty fingerprint"), which is a reported failure, not a crash.
- **`lintunes/bpm_detect.py`** — right-click → Calculate BPM (Round 73).
Spectral-flux onset envelope → autocorrelation → every tempo 60–200 scored
over four beat multiples, times a log-Gaussian prior at 120 BPM (the
half/double-tempo tiebreak), then `fold_bpm` into 70–180. Plain numpy plus
the `ffmpeg` CLI (decodes the first 120 s to mono 11 kHz) — **no librosa**,
which would drag numba/scipy/scikit-learn in for one function. `BpmWorker`
is ExportWorker's shape on the shared status-bar widgets (`_busy_worker`),
reports each result as it lands so a cancel keeps them, and the batch is
applied by `LibraryManager.set_tracks_bpm` as **one** undoable command,
overwriting existing BPMs (trav's call). The transport's tap button stays
for the songs it gets wrong.
- **`lintunes/filename_tags.py`** — the offline half of Identify Track, and the
answer to its biggest limitation: **AcoustID only knows music somebody
submitted**, so an underground/SoundCloud rip fingerprints perfectly and