All Projects

Music Information Retrieval – Onset Detection & Tempo Estimation

Pair Project · March-April 2026

MUSI 2526: Intro to Audio Tech II

Overview

A signal processing pipeline in Python that detects beats in ballroom dance music and estimates each track’s tempo. Built with one other student on the 698-track Ballroom dataset, it combines several audio features into a tuned novelty function and is evaluated against MIREX standards.

Highlights

  • Worked with one other student to build, optimize, and evaluate an onset (beat) detection model on the 698-track Ballroom dataset, documented end to end in Jupyter Notebooks.
  • Extracted and compared 23 candidate features (RMSE, spectral flux, zero-crossing rate, and 20 MFCCs) with NumPy, SciPy, and librosa, then combined the top three into a weighted novelty function.
  • Wrote gradient ascent optimizers to tune the peak-picking threshold and multi-variate feature weights directly on F1 score over the training split.
  • Improved test-set results over the baseline on every metric: precision 42.5% → 47.1%, recall 60.5% → 63.2%, F1 score 46.7% → 50.4% (MIREX ±50 ms tolerance).
  • Extended the model to estimate tempo using autocorrelation; weighting the beat histogram by the training data’s tempo distribution raised accuracy from 36.4% to 45.7% (within MIREX’s 8% margin) by cutting tempo doubling and halving errors.
Bar chart comparing precision, recall, and F1 score for the baseline and improved onset detection models; the improved model scores higher on all three

The Problem

Onset detection asks a simple question: where are the beats? Given only the raw audio of a song, the model has to mark each moment a new note or hit begins, and from those, work out the tempo. It’s a core task in music information retrieval, and it sits underneath things like beat tracking, DJ software, and rhythm games.

We worked with the Ballroom dataset: 698 thirty-second excerpts across eight dance styles, each annotated with every beat and the track’s tempo. After converting the annotations to CSVs and caching per-track metadata (sample rate, style, length), we split the tracks into training and testing sets (140 test tracks) so every number below comes from audio the model never tuned on.

Measuring Success

We followed the evaluation standards from MIREX (the Music Information Retrieval Evaluation eXchange). A detected onset within ±50 ms of a real beat is a true positive, a beat with no detection nearby is a false negative, and a detection with no beat nearby is a false positive. From those we computed:

  • Precision: how many detected onsets were real beats
  • Recall: how many real beats were detected
  • F1 score: the harmonic mean of the two, which is what we optimized

Baseline Model

The baseline uses a novelty function: the first derivative of each track’s RMS energy (1024-sample frames, 512-sample hop), with negative values trimmed, since only increases in energy suggest a new onset. Any peak above a threshold counts as a detected beat.

A hand-picked threshold of 0.03 gave an F1 score of 45.0%. Rather than brute-forcing every value, we wrote a gradient ascent loop that nudges the threshold, measures how the average training F1 changes, and steps in the better direction. It settled on 0.03729, raising test F1 to 46.7%.

Line chart of training F1 score rising from about 0.475 to 0.488 over 100 gradient ascent epochs
Average training F1 over 100 epochs of threshold optimization
Bar chart: naive baseline at 36.7% precision, 66.9% recall, 45.0% F1; optimized baseline at 42.5% precision, 60.5% recall, 46.7% F1
Optimizing the threshold traded some recall for precision, for a net gain in F1

Plotting the novelty function against the ground truth showed the baseline’s weakness: some of its tallest peaks don’t land on beats at all, and many real beats sit on tiny peaks. Energy alone isn’t enough.

Improved Model

Feature selection

To give the model more to work with, we extracted several new features for every track: spectral flux (changes in the spectrum, not just loudness), the squared derivative of the zero-crossing rate (changes in rough pitch), and the derivatives of 20 Mel-frequency cepstral coefficients (changes in timbre). Each one was tested on its own as a novelty function, with its threshold tuned by the same gradient ascent.

Only MFCC 0 beat RMSE on its own, but the top three (MFCC 0, RMSE, and spectral flux) capture different kinds of change, so we combined them.

FeatureTest F1 (alone)
MFCC 0 ✓0.481
RMSE (baseline) ✓0.467
Spectral flux ✓0.380
MFCC 30.351
MFCC 20.333
Zero-crossing rate0.278

Top six of 23 features tested; ✓ marks the three combined into the improved model.

Weighting the features

Simply adding the three normalized features (with a fixed 0.5 peak threshold) actually scored slightly lower than the baseline. The fix was a weighted sum. A grid search over weight combinations found a strong starting point of (1.4, 1, 1.4) for RMSE, spectral flux, and MFCC 0, and then we extended the gradient ascent to multiple variables: each epoch nudges every weight individually, measures its effect on F1, and moves along the resulting gradient. The final weights were (1.595, 0.893, 1.630).

Bar chart: naive improved model at 51.4% precision, 47.9% recall, 46.0% F1; optimized at 47.1% precision, 63.2% recall, 50.4% F1
Weighting raised the model's sensitivity: recall jumped by over 15 points and F1 by nearly 5

Results

On one percussion-heavy test track, the difference is easy to see. Blue lines are the true beats and red lines are detections. The improved novelty function lines up with the beat grid far more cleanly, and on this track F1 rose from 59.5% to 65.4%.

Baseline RMSE novelty function over 30 seconds with ground truth beats as blue lines
Baseline novelty vs. ground truth beats
Improved weighted novelty function over 30 seconds with ground truth beats as blue lines
Improved novelty vs. ground truth beats
Baseline novelty function with detected onsets as red lines, clustered unevenly
Baseline detections (P 49.0%, R 75.8%, F1 59.5%)
Improved novelty function with detected onsets as red lines, more evenly spaced
Improved detections (P 54.3%, R 82.3%, F1 65.4%)

Across the full test set, both optimized models involved a precision/recall trade-off on their own, but the improved model beats the baseline on all three metrics at once, because it changes what the model listens to rather than just how sensitive it is.

Bar chart: baseline 42.5% precision, 60.5% recall, 46.7% F1; improved 47.1% precision, 63.2% recall, 50.4% F1
Optimized baseline vs. improved model on the 140-track test set

Extension: Tempo Estimation

With a better novelty function in hand, we used it to estimate tempo. Autocorrelating the novelty function shows how strongly it repeats at each lag; converting lags to BPM (using the hop length and sample rate) gives a beat histogram, whose peaks are candidate tempos. The naive model picks the strongest peak between 40 and 300 BPM.

Counting an estimate as correct within 8% of the true tempo (the MIREX standard), the naive model got 36.4% of test tracks right.

Beat histogram from autocorrelation with its strongest peak near 172 BPM and a smaller peak near 86 BPM
Beat histogram for one track: the top peak (172 BPM) is double the true tempo (84 BPM)
Histogram of training-set tempos clustered between 80 and 130 BPM, with a fitted normal curve
Training-set tempos with a fitted normal distribution

The scatter plots showed most errors were tempo doubling and halving: the model locked onto every other beat, or onto subdivisions, and those wrong answers sat at the extreme ends of the range. To counter that, we fit a normal distribution to the training tempos and used it to re-weight each peak’s confidence, so common tempos win close calls.

That raised accuracy to 45.7%, a gain of over 9 percentage points.

Scatter of estimated vs. actual tempo with many points on the doubled and halved tempo lines
Before: many points on the 2× and ½× lines (36.4% correct)
Scatter of estimated vs. actual tempo with more points on the correct diagonal
After weighting: most doubling and halving errors removed (45.7% correct)