← Vibeset · case note

Where's the one?

ProductVibeset Cue
Period
DomainBeat tracking · MIR
RoleAI Research Engineer

Cutting picture to music is not a beat-detection problem. It's a bar problem. Every tool will tell you where the beats are; the useful question is which of them is the one. We started with a one-line library call that guessed the bar once per song and never said whether it was right, and ended with a 20-million-parameter model we serve ourselves on CPU. The most valuable thing we found along the way was a defect that no published benchmark in this field is capable of showing you.

01 — The problem

Beats are the easy part. The downbeat is the product

An editor lands a cut on the 1. So does a camera move, a title card, a character hitting a pose. Get the beat grid right and the bar phase wrong, and every one of those lands on the 2. Steady, confident, and wrong in a way nobody can point at.

You hear it before you can name it. A lyric restarting in the wrong place. A hit that arrives a beat early for three minutes.

A noisy grid announces itself. A mis-phased grid passes every gate you have and feels off.

02 — Where it started

One line of librosa, and a guess we never checked

The first version called librosa.beat.beat_track, like most first versions do. It returns beats. It does not return downbeats, so bar phase came from a low-band kick heuristic that picked one global phase for the whole song. When that guess was wrong, every bar-aligned edit in the piece was aligned to a grid offset from the music's actual bars.

One measurement ended it, and one structural fact. Both had to be gone looking for; nothing in the product was failing.

03 — The seam

Change what's behind the door, not the door

Before swapping trackers, the beat grid moved behind one interface. The existing path was copied across verbatim, with a test asserting bit-identical output to the block it replaced. No behaviour change, on purpose.

That seam paid for itself within a day. The replacement went in and made the grid worse.

The provider handed a 22,050 Hz signal to a library that assumes 44,100. Every track read as half its real length. Every downbeat landed at the wrong time and the wrong phase.

The failure the swap existed to fix, reintroduced by the fix. Every unit test passed throughout, because no fixture ever crossed a sample-rate boundary.

04 — The instrument

Build the ruler before you trust the measurement

The next step wasn't a model. It was a benchmark harness, validated against numbers somebody else had already published. It reproduced Beat This!'s reported GTZAN row to within half a point, and madmom's to within one, before we read a single result of our own.

Unglamorous, and it decides whether anything after it means much. Until the ruler was trusted, every number we produced was a number about our own code rather than about the music.

What the numbers mean

Every figure below is downbeat CMLt. Read it as the share of a song where the predicted bar line stays locked to the real one. Not just close on average, but holding, beat after beat, without slipping. 100 would mean the bar is right everywhere. Published systems land in the low 70s, and the difference between 73 and 75 is a real one in this field.

librosa the first version n/a no downbeats — bar phase never measured madmom what Cue shipped before 53.7 bar wrong on 42% Beat This! stock the open backbone, MIT 73.6 bar wrong on 29% Cue Beat v1 fine-tuned, one pass 75.4 what we serve
librosa the first version no downbeats — bar phase never measured madmom what Cue shipped before 53.7 bar wrong on 42% Beat This! stock the open backbone, MIT 73.6 bar wrong on 29% Cue Beat v1 fine-tuned, one pass 75.4 what we serve
Downbeat CMLt, GTZAN, identical decoder on every row. Baselines scored on 992 tracks, Cue Beat v1 on 993; re-scored on the common 992 it reads 75.40. Higher is better.

05 — The find

The benchmarks are 30 seconds long. Songs aren't

GTZAN, Ballroom, SMC, Hainsworth: the corpora beat trackers are usually scored on are 30-second excerpts. One chunk. No seams. Full-length annotated sets do exist and we train on one, but no headline benchmark number in this field is computed over a song long enough to have a seam.

Real music isn't. A cue is three and a half minutes, and the model infers in 30-second chunks carrying no state across them. On an actual song it re-decides tempo and bar phase about six times, and no published metric can see it happen.

On three quarters of full-length songs, the bar count jumped somewhere mid-track, under the exact setup that had produced our entire stored catalogue.

The part worth keeping is what didn't work. Overlapping the chunks — the obvious fix, the one we built first and expected to win — measured no better than plain tiling. The problem was never the seam. It was that each chunk was allowed its own opinion about where the bar was.

chunked, stitched the config that built our whole catalogue 75% chunked, one decoder over the track what we shipped 30% overlapped chunks the fix we expected to win 33% no better than plain tiling
chunked, stitched the config that built our whole catalogue 75% chunked, one decoder over the track what we shipped 30% overlapped chunks the fix we expected to win 33% no better than plain tiling
Chunked, stitched: each 30-second window picks its own tempo and its own bar, and the results are laid end to end. One decoder over the track: same windows, but one pass reads all of them together and makes a single decision. Overlapped chunks: the windows overlap at the joins, nothing else changes.

Share of songs where the bar count jumped at least once · 60 full-length commercial tracks, averaging 3m28s with about six window joins each. Lower is better.

The difference is a grid that holds for three minutes versus one that is right until 0:47 and then quietly isn't. Anything downstream that trusts bar numbers inherits it: a cut placed on the 2, a loop spliced a beat out. None of it raises an error.

06 — The lever

It was the data. It kept being the data

Every public beat corpus is overwhelmingly Western. Ballroom, rock, jazz, classical, electronic. That is what the open models are trained on, and it is what they are good at.

So we scored the backbone on 99 held-out tracks of anime, game and rhythm-game music. It read 61.0 there against 75.0 on the standard benchmark. Same model, same decoder, a different part of the world.

We added 736 tracks of that repertoire to the training mix and fine-tuned. On the same held-out 99, the result went to 77.0.

Then we built a specialist just for it. On the specialist's own repertoire, the generalist trained on all sixteen corpora beat it by 4.4 points.

The specialisation wasn't what helped. Having the music in the mix at all was. That is also why the tempo story below matters here: 63% of that repertoire sits above 160 BPM, which is the band where everything, ours included, is weakest.

One honest limit, and it is the same one the API docs carry: 99 tracks cannot resolve a difference under about 3 points. This says what the model was trained on. It does not prove a margin.

07 — The cliff

We lowered the floor and forgot the ceiling

Accuracy is not evenly distributed across tempo, and that shape is the most useful thing here.

under 55 BPM n=16 14.8 55–90 BPM n=176 69.9 90–150 BPM n=624 83.5 150–215 BPM n=143 66.2 over 215 BPM n=34 50.0 was 8.7 until we raised the ceiling
under 55 BPM 16 tracks 14.8 55–90 BPM 176 tracks 69.9 90–150 BPM 624 tracks 83.5 150–215 BPM 143 tracks 66.2 over 215 BPM 34 tracks 50.0 was 8.7 until we raised the ceiling
Downbeat CMLt by true tempo · GTZAN, 993 tracks, band counts shown, decoder range widened to 20–400 BPM. 37% of this benchmark sits outside the middle band and scores 14 to 69 points worse. Published overall averages hide that completely.

The fast band has a story attached. The decoder searches a tempo range, and ours was 55–215 BPM. We had three configurations that lowered the floor for slow music and not one that raised the ceiling. We had simply never looked at the other end.

Above 215 BPM the tracker was scoring 8.7. Raising the ceiling took that band to 50.0: a 41-point move, from changing a constant. It helps on a checkpoint we never trained, so it's a property of the decoder rather than of our model.

Which matters for a fast catalogue: 63% of the anime and rhythm-game corpus sits above 160 BPM. On GTZAN those tracks are 3.4% of the set, diluted roughly thirtyfold in any pooled average. That is most likely why we let the axis go the first time we looked at it.

One caveat that belongs next to the chart rather than in a footnote. The served path decodes at 55–215 BPM by default and only widens when the caller supplies a tempo. Hand it a track with no catalogue BPM and the fast band is still 8.7, not 50.0. The 41 points are available; they are not automatic.

The counterweight is the more honest finding. We tried to repair the tails with data, oversampling the starved bands four times over. It moved the pooled score by 0.06 points.

Exposure was never the constraint. 190 training tracks is too few to learn a tempo regime from however often you show them, and the corpus does not contain more.

08 — The check

Then we pointed the instrument at ourselves

This programme runs on pre-registered gates. Every experiment carries its threshold, written down before it runs. That discipline exists because of one earlier result. We scored 23 variants and the best gained +0.80 — until we accounted for having taken 23 shots at the target, at which point the odds of seeing that by chance alone came out at 0.586. Better than a coin flip. The best result of the search sat below the median of its own noise.

The gates caught us again, harder. Our headline gain had been confirmed on a 646-track holdout at +1.38. Then we checked what that holdout was made of.

85% of it was the base model's own training data. Split by contamination, the same result reads +1.57 on corpora the backbone had memorised and +0.36 on clean music.

the headline, as we reported it n=646 · z +3.11 +1.38 on corpora the backbone memorised n=547 · z +3.19 +1.57 on music it had never seen n=99 · z +0.35 +0.36
the headline, as we reported it n = 646 · z +3.11 +1.38 on corpora the backbone memorised n = 547 · z +3.19 +1.57 on music it had never seen n = 99 · z +0.35 +0.36
The same model, the same measurement, split by whether the base model had seen the music before. Downbeat CMLt gain over the generalist. The headline is the top row.

So we wrote it down, demoted our own flagship number, and adopted the rule: a gain must appear on clean data to count.

Then we ran the audit in the other direction, checking whether our training set had absorbed benchmark recordings. Two methods sharing no statistic, each with a time-reversed decoy control. 23 tracks confirmed leaked. Re-scoring without them — not retraining — the fine-tune's margin went from +1.85 to +1.89.

The gain got slightly larger with the leak taken out. That's the number we'd want to see from somebody selling us a model, so it's the one we publish.

09 — The shape

Knowing when it's wrong beats being right more often

Running three models and averaging them buys about 1.5 points. Running three models and asking whether they agree buys considerably more.

no abstention · 73.6 0% 5% 10% 15% 20% 25% 30% 87.3
Downbeat CMLt on the tracks you keep, against the share set aside for review · GTZAN, 992 tracks, agreement measured across a four-checkpoint committee. Baseline is a single model.

Set aside the 30% of tracks the models disagree most about, and accuracy on everything you keep goes from 73.6 to 87.3. The three-model average is worth 1.5 points. The verdict is worth 13.7.

For a pipeline generating something on top of the grid, that ratio is the interesting one. Knowing which songs to send to a human beats being slightly better on all of them.

This is measured and not yet wired into the product. Numbers when it ships, same rules as everything above.

10 — What ships

20 million parameters, one forward pass, no GPU

Cue Beat v1 is a Beat This! backbone (MIT-licensed, from Foscarin et al., ISMIR 2024), fine-tuned on our corpus. The fine-tunes were then averaged into one set of weights, so serving a track costs a single pass through the model rather than running several and voting.

Audio goes in at 22,050 Hz mono and becomes a spectrogram at 50 slices per second. The model scores every slice for how much it looks like a beat, and how much like a bar line. A decoder then reads those scores end to end and picks the one grid that best explains the whole song.

It runs on CPU: 4.4 seconds of compute per minute of audio as deployed, 97% of it the forward pass.

It's served as its own async endpoint, POST /v1/beats, for two reasons. Its cost driver is audio minutes rather than API calls. And it's the only component in the estate that needs torch, which would otherwise land on every other endpoint's cold start.

  1. audio
  2. log-mel · 50 fps
  3. Cue Beat v1 · one pass
  4. whole-track DBN
  5. beats · downbeats · tempo

+1.8

points of downbeat CMLt over the free MIT backbone

75 → 30%

of full-length tracks carrying a bar-grid discontinuity, before and after

$0.008

per audio-minute, one forward pass, CPU only, live in us-east-2

against the tracker we shipped before, the first number reads +21.7. that comparison flatters us, so it isn't the one in large type.

11 — What breaks

The rows we don't hide

The limits, in full

  • Tempo extremes. Under 55 and over 215 BPM, accuracy falls off a cliff. The chart above is the honest map, and the cause is corpus content rather than model capacity. Adding exposure didn't move it.
  • The anime-repertoire result is a training-composition claim, not an accuracy claim. On 99 held-out tracks it reads 77.0 against the stock backbone's 61.0, but only +0.36 points against our own generalist. The corpus matters; the weight-merge on top of it does not. And n=99 can't resolve anything under ~3 points, so neither figure proves a margin.
  • We have never measured our own run-to-run variance. The backbone authors' three published runs span 1.1 points, so we treat anything under ~1.5 from a retrain as unresolved rather than settled. Several of our own closures sit in that band. Our seed repeat is queued, not done.
  • The model doesn't run the video editor yet. The editor still ships madmom, because the request-path image deliberately carries no torch and there's no silent fallback to degrade to. Cue Beat v1 serves the beat-map API. That's the true state of it.
  • Three times now we've found a real per-track signal and no way to route on it. Model agreement, tempo steadiness, structural confidence. Each is worth 1 to 2 points if you could already tell which tracks to apply it to, and nothing we built can tell. Declining a track works, as above; picking a better treatment for it does not. At three for three that's a statement about the task, not our methods.

12 — The arithmetic

None of this is hard to start

beat_track is one line and it will look fine in a demo.

What costs is the rest. We could not learn the bar phase was wrong on 42% of songs until we built a benchmark that measured bar phase. Our accuracy came from 30-second excerpts; the product ran on three-minute songs. Our holdout had been memorised by our own backbone. A tempo ceiling we never questioned was worth 41 points.

Each of those is a few weeks, and a benchmark you have to build before it can tell you anything.