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 want | Use | Runs |
|---|---|---|
| Where sounds start (onsets), in your own analysis | spectralFlux and pickPeaks from @maruaudio/audio-sdk/analysis/novelty | Anywhere: pure functions over feature frames |
| Section boundaries in a clip | footeNovelty from the same subpath | Anywhere |
| Beats and downbeats of a song | The host beat tracker, today offered with stem separation (trackRhythm) | The host's worker |
| A beat grid from an app or server | analyze_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:
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:
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
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
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.