Beat & tempo
Rhythm analysis in Pleco-Xa is tiered, and the tiers are honest about what they
promise. At the top is beat_track — the canonical Ellis dynamic-programming
tracker, with numerically exact tempo and exact beat frames. Below it sits
quickTempo, an explicit fast tier
for live meters, and the bpm namespace, a windowed stability analyzer for
watching tempo drift over a performance. No tier fabricates a result: failure
paths throw, and there are no default BPMs.
beat_track(y, sr) returns { tempo, beats }. The pipeline is
onset_strength (median aggregate) → tempo with the log-normal prior
→ dynamic-programming peak picking consistent with the estimated tempo.
Key functions
Section titled “Key functions”beat_track(y, sr?, opts?)— canonical DP beat tracker; returns{ tempo, beats }. Accepts a scalarbpm, or a per-frame BPM array (fromtempo(..., { aggregate: null })) to track time-varying tempo.tempo(y, opts?)— tempogram-based tempo estimate; scalar by default, per-frame with{ aggregate: null }.quickTempo(y, sr?, opts?)— the explicit quick tier; lands in the same lag bin astempowith a measured (non-fabricated) confidence.beat_sync(data, beats, ...)— beat-synchronous feature aggregation.beatTrack,fastBPMDetect,extractTempo,detectBPM— fast-heuristic helpers.compute_tempogram,tempogram,fourier_tempogram,tempogram_ratio— tempogram family.find_tempo_candidates,detect_tempo_multiples,analyze_groove,findDownbeatPhase,findFirstDownbeat— downbeat / groove helpers.bpm.*— the tempo-drift/stability analyzer (analyzeWithProgress,estimateGlobalTempo,analyzeTempogram, …).
Example
Section titled “Example”import { beat_track, tempo, quickTempo } from 'pleco-xa'
// y is a Float32Array time series (e.g. from audioio.load), sr its sample rateconst { tempo: bpm, beats } = beat_track(y, sr)bpm // scalar BPMbeats // beat frame indices (default units: 'frames')
// Tempo alone (scalar), or per-frame for time-varying materialconst scalar = tempo(y, { sr })const perFrame = tempo(y, { sr, aggregate: null })
// Feed the per-frame curve back in to track a tempo rampconst drifting = beat_track(y, sr, { bpm: perFrame })
// Live/quick tier — same lag bin, honest confidenceconst quick = quickTempo(y, sr)- Tempo is lag-quantized. The tempogram lag bins are
60·sr / (hop·k), so a true 120 BPM click train legitimately reports ~117.45 at hop 512. The honest bound is|err| ≤ 7 BPMat that hop — don’t assert tempo on a knife edge. - Tempo and beats are pinned.
beat_track’s tempo and beat frames are fixture-gated in CI (tempo_beats.json) — and hold even after a 16-bit WAV encode/decode round trip. - The quick tier never impersonates the canonical one.
quickTempois explicitly the fast path; it is only actually faster than the canonical tier at a matched hop length (~2.2× at hop 512). Compare tiers at the same hop. bpmis a drift analyzer, not the primary source. Its windowed pass now tracks tempo changes (a 100→140 BPM ramp reads ~99→144 across windows) — use it to characterize stability, never as your headline BPM.plpandbeat_synchave caveats.plpis a windowed-autocorrelation pulse-strength approximation, not a Fourier-tempogram PLP;beat_syncdrops audio after the last beat boundary (it emitsbeats.length − 1segments).- Use
beat_track, not the legacy detector. The DP tracker (backed byonset_strength+tempo) is the canonical engine; the old standalone BPM detector is superseded.
See the API reference for full signatures and defaults.