Beats and tempo in a module

Find beats, downbeats and onsets in audio: the model-free onset tools you can run anywhere, the host's beat tracker, and how to bring a beat grid into your module.

Knowing where the beats are lets a module slice a loop, sync a delay, quantise a transcription or line up a sample with the song. It is part of AI tasks in your module. For what beat tracking gets right and wrong, read Follow the beat in the Help Center.

You wantUseRuns
Where sounds start (onsets), in your own analysisspectralFlux and pickPeaks from @maruaudio/audio-sdk/analysis/noveltyAnywhere: pure functions over feature frames
Section boundaries in a clipfooteNovelty from the same subpathAnywhere
Beats and downbeats of a songThe host beat tracker, today offered with stem separation (trackRhythm)The host's worker
A beat grid from an app or serveranalyze_audio with analyses: ["rhythm"]Maru's job queue

Onsets without a model

Onset detection does not need a model. Compute log-band levels for each frame (@maruaudio/audio-sdk/analysis/log-bands), take the spectral flux, then pick peaks:

ts
import { spectralFlux, pickPeaks } from "@maruaudio/audio-sdk/analysis/novelty"

const flux = spectralFlux(bandLevelsDb, frames, bands)
const onsetFrames = pickPeaks(flux, { minDistance: 4, threshold: 0.5 })

These run outside the audio callback in a face, a worker or a build script. Inside a Processor, prefer the realtime analysis leaves in @maruaudio/dsp.

Beats from the host

The host's beat tracker is exposed today on the separation surface: a face that declares audio.separation/v1 can call separation.trackRhythm?.("mix") or ("drums") and read separation.state.rhythm:

ts
const rhythm = face.separation?.state.rhythm
// rhythm.beats and rhythm.downbeats are times in seconds
// rhythm.modelLabel names the tracker that produced them

exportRhythm?("mid") gives the person the grid as a MIDI file. Check that trackRhythm exists before offering the action; a host without the tracker omits it.

A dedicated beat-grid task for modules that do not separate stems is planned. Until then, a module that needs beats from the host declares the separation capability, as StemSep does.

Current implementation

The beat tracker is Beat This. It returns beats and downbeats; it does not return a single tempo or a time signature, so derive those from the beat spacing if you need them.

From an app or server

ts
const beats = await submit({
  type: "analyze_audio",
  source,
  analyses: ["rhythm"],
  modelId: "beat-this/small0-onnx",
})

rhythm must be requested on its own, with that model id. Other analyses such as tempo, loudness and key go in a separate analyze_audio job. See Add generative audio to your app.

Ask your agent

text
Read the Maru docs page "Beats and tempo in a module", then read StemSep's
source with `maru module files stemsep`, looking for how its face calls
trackRhythm. Add a "slice on beats" action to my sampler that uses the beat
grid when the host offers one and falls back to onset detection otherwise.