# Finding the Drop: Beat Grids and Cue Points I No Longer Place by Hand

> How my DJ library gets its tempo, beat grid and cue points automatically: a music-structure model on a spare GPU, four disagreeing beat trackers, a bass-energy test for the drop, and an ear for whatever's left.

By Yuwei Liang · 2026-10-30

![Article cover](https://yuweiliang.com/api/blog/assets/de728cec-0d23-48fc-b33f-ae121df6e84c)

Before I can mix a track, it needs three things. A **tempo**. A **beat grid**, which says where every beat and every bar starts. And **cue points** at the moments I'd actually mix in or out.

My library has about 2,500 tracks. Setting all of that by hand takes a few minutes per track, and I'd rather spend those minutes mixing. So over the last couple of months I built a pipeline that does it for me. This is how it works, and where it took measuring rather than guessing.

It's the companion to [Teaching a Traktor Kontrol S5 to Speak Mixxx](/blog/traktor-s5-mixxx). That piece is about the controller; this one is about what the controller plays.

## What the pads should mean

The first step was writing down a convention I already followed without thinking. I measured it across the cues I'd set by hand:

| pad | cue |
|---|---|
| 1 | Mix In -32: 32 bars before the drop, for a long blend |
| 2 | Mix In -16 |
| 3 | Mix In -8 |
| 4 | **Drop**, the moment I mix into |
| 5 | Breakdown |
| 6 | Drop 2, after the breakdown |
| 7 | left empty for loops I add by hand |
| 8 | Outro loop, 8 bars |

Two more markers sit 16 and 8 bars before the outro, so I know when to start bringing the next track in. Everything below exists to put those marks on real bar lines.

## Analysis: a model on a spare GPU

The structure comes from [All-In-One](https://github.com/mir-aidj/all-in-one), a music-structure model. For one audio file it returns beats, downbeats, a BPM, and segments with labels like `intro`, `verse`, `chorus`, `break` and `outro`.

It wants a CUDA GPU. On my M1 Pro's CPU a track took about 304 seconds. Apple's GPU backend only bought about 23%, because one of the model's main components doesn't support it. On an old GTX 1070 in a spare machine, the same track takes about 64 seconds.

So I set up that machine as an extra compute server, and treat it as stateless. Audio goes over, JSON comes back, and the authoritative copy of every analysis lives on my Mac. The machine can be wiped and rebuilt from one setup script.

Two decisions made that work well:

- **Analyses are keyed by a hash of the audio, not the file path.** My files move and get renamed constantly. Keying by path would waste GPU time on every move. Worse, giving one track's filename to a different file would silently reuse the wrong analysis.
- **Analysis and cueing are separate steps.** Analysis costs a minute of GPU per track and never needs redoing, because the audio never changes. Turning an analysis into cue points takes milliseconds. So when I improve a rule, I can re-run it over the whole library for free.

An hourly job picks up new downloads. The first pass over the library queued about 1,700 tracks and took roughly 30 hours. I can also send any selection to the GPU from Mixxx's track menu, with progress showing on the controller's screen.

## Tempo is harder than it looks

Every tool I tried had a different blind spot:

- **Traktor** read 100 of my 107 UK dubstep tracks at about 70 BPM instead of 140.
- **Mixxx's own analyser** reads drum & bass at half tempo (87 for 174), and about a third of the tracks I checked were half a beat off.
- **All-In-One** also hears most drum & bass at half tempo.
- **[Beat This](https://github.com/CPJKU/beat_this)**, a dedicated beat tracker, follows halftime sections. In a dubstep track its beats jump between 70 and 140 BPM.

The fix for octave errors is boring and effective: each folder of my library has a tempo range, and every estimate gets folded into it. Drum & bass lives between 160 and 185. Dubstep stays at its real 130 to 160 and is never halved.

### Every tool has its own clock

Even when two tools agree on the tempo, they don't agree on where a beat is. I measured it:

- Mixxx places the same beat **47.5 ms earlier** than Traktor (31 tracks, the middle half within 2 ms of that).
- All-In-One places it **18.4 ms later** than Mixxx (median of 385 tracks).
- Beat This places it **9.8 ms later** than Mixxx (148 tracks).

Twenty milliseconds doesn't sound like much. But a cue that late lands just after the kick, and two kicks that far apart sound like a flam. So every position gets moved onto Mixxx's timeline before it's written.

### A grid is a straight line

A beat grid in Mixxx is a tempo and a first beat. I fit a straight line through the model's downbeats, numbering each one by how many bars lie between it and its neighbours.

The obvious shortcut, dividing by the model's whole-number BPM, drifts a full bar out within a few minutes. Tracks whose downbeats don't sit on one line, like live drums or edits, get reported and left alone rather than forced.

### Four opinions, one grid

For each track I now have up to four candidate grids: what Traktor had, Beat This, All-In-One, and whatever Mixxx already holds. They count as agreeing when the tempo is within 0.3% and the beats land within 15 ms at three points across the track.

When Beat This and All-In-One agreed with each other, the grid was written automatically: 220 tracks. 326 others went to a list for an ear to settle. Once a grid is locked, nothing automatic ever rewrites it.

Drum & bass needed its own path, because both models hear it at half tempo. There, the tempo comes from the audio itself: an onset envelope searched between 160 and 180 BPM, with the phase measured separately in each third of the track to catch drift. Which beat starts the bar is voted on by the model's segment boundaries.

I checked that vote against my own cues on 56 tracks. The segment boundaries got 55 right, the model's downbeats 54, and the classic "kick on one, snare on two and four" rule only 31 of 36. On 68 tracks whose Traktor grids I'd checked by ear, the estimate agreed on 64.

## When the machines disagree, listen

For the tracks nobody could settle, I built a small review page. It plays an excerpt with a click on every beat of the selected candidate, and a higher click on the first beat of each bar. You hear immediately whether a grid sits on the music or slides off it.

Each candidate is a button. There are ×2 and ÷2, nudges of 5 ms, shifting the bar by one beat, and tap tempo for when every candidate is wrong. The list sorts by confidence, so the ones most likely to need me come first.

The page also lets me say things the algorithms can't: **not dance music**, so leave it ungridded. **Wrong download**, because it isn't the song. **Tempo changes**, because no straight grid fits.



### The grid review page

![Reviewing a breakbeat track the beat tracker couldn't follow. Candidates sit under the timeline; the buttons adjust the grid or mark the track as not dance music, the wrong song, or changing tempo.](https://yuweiliang.com/api/blog/assets/b1857007-674c-4678-bac6-a62bc0ef1ebf)

Reviewing a breakbeat track the beat tracker couldn't follow. Candidates sit under the timeline; the buttons adjust the grid or mark the track as not dance music, the wrong song, or changing tempo.



Of the 311 tracks I went through, I kept a grid for 249. Beat This's was right 122 times, the grid Mixxx already held 64 times, tap tempo 27 times, All-In-One 21 times, and the file's own tag 15 times. I marked 53 as not dance music, and found 7 downloads that weren't the song at all.

## Finding the drop

The model is good at boundaries and bad at names. Its vocabulary comes from pop songs, and it has no word for a drop. Across the first seven tracks I checked, the drop was labelled `inst` five times and `verse` once.

So I use its boundaries as candidates, about a dozen per track instead of several hundred beats, and decide among them with energy. Not full-spectrum loudness, though: **bass energy below 200 Hz, per beat**. In this music a drop is the bass arriving. Vocals, hi-hats and risers barely move it.

**The drop** is the earliest boundary that is loud straight away (over its first 4 bars), stays loud (over 16 bars), and comes after something quieter. Each condition is there because of a track that broke the rule before it:

- A 1989 vinyl rip sat at middling energy all the way through and produced nonsense, so tracks with no real dynamic range get no drop.
- Starting the search 10% into the track skipped one song's 36 bass-less intro bars, which were its one real drop. So the search starts at bar 9 or at 10%, whichever comes first.

**Grooving tracks**, like disco and French house, run at full from bar one and never drop. For those, the cue goes where the track steps up to full: Modjo's first verse after its intro, Basement Jaxx at 90 seconds.

**The breakdown** is the quietest boundary after the drop. **Drop 2** is where the energy genuinely recovers, not simply the next boundary, which can still sit inside the breakdown.

**The mix-in ladder** steps back from the drop through the model's actual downbeats, rather than multiplying an assumed beat length. That matters more than it sounds. One track reported 125.0 BPM while its own downbeats sat at 126.3. An "8-bar" loop computed from the reported tempo came out 32.26 beats long, enough to break sync a few passes in.

**The outro** can't be found by energy at all. In this music, drums and bass carry on through it; one track holds 90% of full energy in its outro. So here the labels decide: the outro is the run of `outro` and `inst` sections that closes the track after the last chorus-like one. Tracks that end on a chorus don't get one.



### The cues on the controller

![On the S5's screen: numbered cues along the overview, the outro range in purple with its -16 and -8 marks, and an OUT counter in the header.](https://yuweiliang.com/api/blog/assets/de728cec-0d23-48fc-b33f-ae121df6e84c)

On the S5's screen: numbered cues along the overview, the outro range in purple with its -16 and -8 marks, and an OUT counter in the header.



## Never overwrite a person

The rule I care about most isn't about music. **A cue I set by hand, especially one set during a set, is never overwritten by an algorithm.**

An early design recomputed everything and protected hand edits with a lock list. It depended on an implementation detail in Traktor, and one silent change there would have meant silently losing my work. So the default is inverted. The pipeline only touches a track if:

- I asked for it, by colouring the track or putting it in a "re-cue" playlist
- I named its folder
- it has no cues at all
- every cue on it came from the pipeline and none has been moved

Everything else is left alone. In Mixxx, generated cues carry known names, and anything else keeps its pad. If I set pad 4 myself, that's the Drop, whatever the energy curve thinks.

## What it adds up to

New tracks arrive, get analysed within the hour, and get grids and cues when I ask. The controller shows where the drop is, how many bars until the next cue, and when the outro starts.

Most of the work wasn't the clever part. It was measuring things I'd assumed: that two programs agree on where a beat is, that a reported BPM matches its own downbeats, that "drop" is something a model can name. Each assumption was wrong by a few milliseconds or a few percent, and each was enough to make a mix sound off.
