Skip to content

Beats and Downbeats ​

Beats are the perceived pulse positions in the music. Downbeats are the first beat of a bar: the stronger "one" that anchors meter. libsonare exposes both beat and downbeat detection as time arrays.

Beat, downbeat, and bar ​

A beat is a single pulse: what you tap your foot to.

A bar groups a fixed number of beats, and the time signature says how many. In 4/4, a bar has four beats.

The downbeat is the first beat of each bar, the accent you feel as the start of the cycle. Beat tracking gives you the grid; downbeat detection tells you where the grid starts repeating. That is what you align loops, lyrics, and sections to.

DETECTOR · BEAT / DOWNBEATIDLE
Downbeat tracking — finding the bar line

Downbeat tracking keeps the first beat of each bar from the full pulse. On this four-bar groove, the detector settles after the opening hit and returns bar lines near 2, 4, and 6 seconds. Switch to Beats to see the pulse it follows.

Detect

Why beats are not just an even 60/BPM grid ​

You might expect beats to be trivial once the tempo is known: divide the timeline evenly. Real music makes that unreliable:

  • tempo can drift;
  • players can push or pull the beat;
  • intros can be rubato (played with free, elastic timing);
  • onset evidence can be noisy.

Beat tracking therefore solves an optimization problem. It tries to find beat times that line up with strong onsets and keep roughly steady spacing near the tempo.

Dynamic programming finds the globally best trade-off instead of greedily snapping each beat to the nearest onset and accumulating error.

Beat tracking ​

Beat tracking starts from the onset-strength envelope and a tempo estimate. The tracker rewards frames with strong onset evidence and uses dynamic programming to choose a sequence whose spacing stays close to the expected beat period.

When adaptive tempo is enabled in the native configuration, the tracker can follow local period changes while still penalizing sudden jumps. This is still a pulse estimate, not a score-level transcription.

Picture a chain of beats rather than a rigid grid. Each gap between two neighboring beats may stretch or shrink a little, but every deviation from the expected beat period costs points (the cost grows with the square of the deviation, scaled by tightness), while every beat that lands on a strong onset earns points. The tracker scores every possible chain across the whole track and keeps the best one, so snapping one beat to a nearby sound is only worth it when the gaps on either side stay plausible.

Downbeat tracking ​

Downbeats are inferred after beats are placed. libsonare estimates meter phase and scores beat positions with beat strength, low-frequency energy, and a phase prior. The downbeat result is therefore more dependent on arrangement cues than raw beat tracking.

Chord-change evidence is a fourth cue, but it only becomes available once chords are known. The whole-track analysis path detects chords and then refines the downbeats a second time with that evidence; the standalone detectDownbeats helper never reaches that stage. Run the full music analysis when downbeat placement matters and the harmony is the clearest cue in the arrangement.

Sparse intros, pickups (notes that lead in before the first downbeat), weak bass, or ambiguous meter can shift the perceived "one." For UI, it is useful to show downbeats as an assistive overlay rather than as an absolute truth.

Beat tracking answers "where is the pulse", and downbeat tracking answers "which beat opens the bar". The first follows a regular grid; the second leans on musical accents such as bass and chord changes, so it is the estimate more likely to miss.

The evidence behind the decision ​

The evidence the downbeat pass scores is published as AnalysisResult.beatObservations, so a caller doing its own meter or accent work scores what the library scores instead of rebuilding a weaker approximation. Each non-empty stream holds one value per beat and indexes in parallel with beats: onsetStrength, lowFrequencyEnergy (the accent evidence a log-spectral difference discards, empty when the analysis ran without audio), and chordChange (empty until chords are analyzed). An empty stream means the analysis could not produce it, which is not the same as every beat having scored zero.

beat.strength and beatObservations.onsetStrength are not the same measurement

A beat's own strength is the onset envelope sampled at that beat's single frame. It is raw and unbounded, its scale depends on the material, and it moves with any jitter in the beat position — so it does not compare across tracks and it is a noisy accent cue.

onsetStrength is the same envelope aggregated over a window around the beat, and it is the value the library's own downbeat pass scores. Use it for accents, and keep strength for the diagnostic it is.

How libsonare computes it

BeatAnalyzer computes mel onset strength, estimates BPM with BpmAnalyzer, and tracks beats with a dynamic program over candidate frames. It then refines downbeats with its internal DownbeatObservations using onset strength, low-frequency energy, and meter phase; the chord-change term is left empty at that point, and MusicAnalyzer calls the refinement again with it once chord detection has run. The public beatObservations carries the same three streams out of the analysis — it is the result surface, not that internal struct. Public helpers such as detectBeats and detectDownbeats return Float32Array / float-array time lists in seconds and stop at the first, chordless refinement.

Related: Onset Detection, Tempo and BPM, Chord Recognition