Initial Commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# `algorithms/comb-tempo`
|
||||
|
||||
## Purpose
|
||||
|
||||
Comb-filter / autocorrelation tempo estimation. Given an onset-detection
|
||||
function (ODF) and the cadence at which it was sampled, this module finds the
|
||||
dominant periodicity and reports the implied tempo in beats per minute.
|
||||
|
||||
It is the universal back-end for package's three-stage
|
||||
detection pipeline — every algorithm in the package (`energy`,
|
||||
`spectral-flux`, and the explicit `comb` selection) ends up here once it has
|
||||
produced a per-block ODF, because tempo estimation is independent of which
|
||||
ODF you started from.
|
||||
|
||||
## Features
|
||||
|
||||
- `combScore(odf, period)` — single-period autocorrelation score. Cheap
|
||||
building block exposed for callers that want to draw a tempo-confidence
|
||||
curve or implement a custom octave-error policy.
|
||||
- `combTempo(odf, opts)` — full sweep across every integer BPM in
|
||||
`[minBpm, maxBpm]`. Returns the winning BPM and its raw score, with
|
||||
built-in octave-error resolution that prefers tempi near the perceptual
|
||||
centre (120 BPM) when two candidates score within 90% of each other.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { combTempo } from "@suaveplan/audio-analysis-beat/algorithms/comb-tempo";
|
||||
|
||||
// ODF sampled every 1024 samples at 48 kHz → hopSeconds = 1024/48000.
|
||||
const result = combTempo(odf, {
|
||||
minBpm: 60,
|
||||
maxBpm: 200,
|
||||
hopSeconds: 1024 / 48_000,
|
||||
});
|
||||
console.log(result.bpm); // e.g. 120
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
combScore,
|
||||
combTempo,
|
||||
} from "@suaveplan/audio-analysis-beat/algorithms/comb-tempo";
|
||||
|
||||
// Draw a tempo-confidence curve over the search range manually:
|
||||
const hopSeconds = 0.02; // 50 Hz ODF
|
||||
const min = 60;
|
||||
const max = 200;
|
||||
const curve = new Float64Array(max - min + 1);
|
||||
for (let bpm = min; bpm <= max; bpm++) {
|
||||
const period = Math.max(1, Math.round((60 / bpm) / hopSeconds));
|
||||
curve[bpm - min] = combScore(odf, period);
|
||||
}
|
||||
|
||||
// Fall back to combTempo for the canonical winner including octave handling.
|
||||
const winner = combTempo(odf, { minBpm: min, maxBpm: max, hopSeconds });
|
||||
console.log(winner.bpm, winner.score);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface CombTempoOptions { minBpm: number; maxBpm: number; hopSeconds:
|
||||
number }` — input bounds and ODF cadence.
|
||||
- `interface CombTempoResult { bpm: number; score: number }`.
|
||||
- `combScore(odf: Float64Array, periodOdfSamples: number): number` — returns
|
||||
zero for `period <= 0` and otherwise the sum of `odf[i] * odf[i - period]`
|
||||
across the valid index range.
|
||||
- `combTempo(odf: Float64Array, opts: CombTempoOptions): CombTempoResult` —
|
||||
returns `{ bpm: minBpm, score: 0 }` for empty ODF, non-positive
|
||||
`hopSeconds`, or inverted bounds. Otherwise sweeps every integer BPM in
|
||||
the inclusive range.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The score for a candidate BPM is the unnormalised autocorrelation of the ODF
|
||||
at the corresponding lag. The lag is computed as `round((60 / bpm) /
|
||||
hopSeconds)` and clamped at one ODF sample so candidate BPMs near `maxBpm`
|
||||
never collapse to a zero lag.
|
||||
|
||||
Octave-error resolution is intentionally cheap: among all candidates whose
|
||||
score is within 90% of the winner's score, the one closest to 120 BPM
|
||||
wins. This is the median tapping tempo across human listeners (Moelants 2002)
|
||||
and matches perception far more often than picking the raw maximum, which is
|
||||
biased toward half-tempo for slow tracks and double-tempo for fast tracks.
|
||||
|
||||
Integer BPM resolution is sufficient for the universal use case — the
|
||||
caller can run a quadratic interpolation across the three neighbours of the
|
||||
winning bin if sub-BPM precision is needed, but that machinery is not part
|
||||
of this module to keep the public surface small.
|
||||
Reference in New Issue
Block a user