first commit
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# `algorithms/beat-grid`
|
||||
|
||||
## Purpose
|
||||
|
||||
Beat-grid construction: given a known beat period and a list of detected
|
||||
onsets, find the phase that aligns a regular `period`-spaced grid to the
|
||||
onsets and emit the grid itself. This is package's three-stage
|
||||
detection pipeline — what runs after tempo estimation has produced a BPM and
|
||||
the caller is ready to convert raw onset times into a downbeat-stable beat
|
||||
sequence.
|
||||
|
||||
The same phase sweep also yields a confidence score in `[0, 1]` that
|
||||
quantifies how tightly the detected onsets cluster around the chosen grid —
|
||||
a sharp cluster (every onset within a tenth of a period of a grid line)
|
||||
gives a confidence near 1, scattered onsets give one near 0.
|
||||
|
||||
## Features
|
||||
|
||||
- `buildBeatGrid(opts)` — sweep `phaseTrials` equally-spaced candidate phases
|
||||
across one period, pick the phase that minimises total
|
||||
onset-to-nearest-beat distance, and return the resulting beat grid.
|
||||
- Configurable sweep resolution (`phaseTrials`, default 24) — finer sweeps
|
||||
cost linearly more time but tighten the grid alignment in proportion.
|
||||
- Defensive against zero-period, zero-duration, and empty-onsets inputs —
|
||||
returns an empty grid with `confidence = 0` rather than throwing.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { buildBeatGrid } from "@suaveplan/audio-analysis-beat/algorithms/beat-grid";
|
||||
|
||||
const result = buildBeatGrid({
|
||||
period: 60 / 120, // 120 BPM
|
||||
duration: 10,
|
||||
onsets: [0.5, 1.0, 1.5, 2.0],
|
||||
});
|
||||
console.log(result.beats.length, result.phase, result.confidence);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { buildBeatGrid } from "@suaveplan/audio-analysis-beat/algorithms/beat-grid";
|
||||
|
||||
// Compare two BPM candidates and prefer the one whose grid the onsets fit
|
||||
// more tightly — this is how the BeatDetector breaks ties between candidate
|
||||
// tempi when an autocorrelation sweep returns two near-equal peaks.
|
||||
const onsets = new Float64Array([
|
||||
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0,
|
||||
]);
|
||||
|
||||
const a = buildBeatGrid({ period: 60 / 120, duration: 4.2, onsets, phaseTrials: 48 });
|
||||
const b = buildBeatGrid({ period: 60 / 60, duration: 4.2, onsets, phaseTrials: 48 });
|
||||
|
||||
const winner = a.confidence >= b.confidence ? a : b;
|
||||
console.log("chosen phase", winner.phase, "beats", Array.from(winner.beats));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface BeatGridOptions { period: number; duration: number; onsets:
|
||||
readonly number[] | Float64Array; phaseTrials?: number }`.
|
||||
- `interface BeatGridResult { beats: Float64Array; phase: number; confidence:
|
||||
number }`.
|
||||
- `buildBeatGrid(opts: BeatGridOptions): BeatGridResult` — `beats` is
|
||||
empty when `period <= 0` or `duration <= 0`; `confidence` is `0` for empty
|
||||
onsets, otherwise clamped to `[0, 1]`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The cost function is L1 (sum of absolute errors) rather than L2. L1 is more
|
||||
robust against the occasional outlier onset that does not actually mark a
|
||||
beat — a snare on the upbeat, a hi-hat trip, a transient false positive —
|
||||
whereas L2 would let that outlier dominate the phase sweep.
|
||||
|
||||
`phaseTrials` defaults to 24, which is one trial every 2.5° of the beat
|
||||
cycle at 120 BPM (≈ 21 ms). Halving the trial count roughly halves the cost
|
||||
function evaluation time at the price of a coarser phase grid; doubling it
|
||||
sharpens alignment until the cost surface becomes flat at the noise floor of
|
||||
the onset times themselves.
|
||||
|
||||
The confidence formula `1 - cost / (n * period/4)` treats a uniformly
|
||||
distributed onset list (mean absolute error = quarter-period) as confidence 0
|
||||
and a perfectly aligned list (zero error) as confidence 1. Inputs that are
|
||||
*better* than uniform — onsets concentrated on the beat — saturate at 1
|
||||
rather than overshooting, so downstream code can treat the field as a
|
||||
probability without further clamping.
|
||||
Reference in New Issue
Block a user