Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -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.