@audio/beat-detect 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/detect.js +57 -0
  2. package/package.json +43 -0
package/detect.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Full beat detection pipeline: onset detection → tempo estimation → beat grid.
3
+ * Detects onsets via spectral flux, estimates tempo via comb-filter resonance,
4
+ * then builds a phase-aligned beat grid. Shares a single STFT pass across both stages.
5
+ * @param {Float32Array|Float64Array} data - Audio samples (mono)
6
+ * @param {Object} [opts]
7
+ * @param {number} [opts.fs=44100] - Sample rate
8
+ * @param {number} [opts.frameSize=2048] - STFT frame size
9
+ * @param {number} [opts.hopSize=512] - STFT hop size
10
+ * @param {number} [opts.delta=1.4] - Onset peak-pick threshold multiplier
11
+ * @param {number} [opts.minBpm=60] - Minimum BPM to consider
12
+ * @param {number} [opts.maxBpm=200] - Maximum BPM to consider
13
+ * @returns {{ bpm: number, confidence: number, beats: Float64Array, onsets: Float64Array }}
14
+ * @see Scheirer, "Tempo and Beat Analysis of Acoustic Musical Signals" (JASA 1998)
15
+ * @see Dixon, "Onset Detection Revisited" (DAFx 2006)
16
+ */
17
+
18
+ import { spectralFlux, peakPick, ODF, validate } from '@audio/beat-core'
19
+ import combTempo from '@audio/beat-tempo/comb'
20
+
21
+ export default function detect(data, opts) {
22
+ validate(data, opts)
23
+ let fs = opts?.fs || 44100
24
+ let sf = spectralFlux(data, opts)
25
+ if (!sf.odf.length) return { bpm: 0, confidence: 0, beats: new Float64Array(0), onsets: new Float64Array(0) }
26
+
27
+ let ons = peakPick(sf.odf, { hopSize: sf.hopSize, fs: sf.fs, ...opts })
28
+ let { bpm, confidence } = combTempo(data, { ...opts, [ODF]: sf })
29
+
30
+ if (bpm <= 0 || !ons.length) return { bpm, confidence, beats: new Float64Array(0), onsets: ons }
31
+
32
+ // build beat grid: find best phase by alignment with detected onsets
33
+ let beatInterval = 60 / bpm
34
+ let duration = data.length / fs
35
+
36
+ let bestPhase = 0, bestScore = -Infinity
37
+ let nTest = Math.min(20, Math.ceil(beatInterval * fs / sf.hopSize))
38
+ for (let p = 0; p < nTest; p++) {
39
+ let phase = (p / nTest) * beatInterval
40
+ let score = 0
41
+ for (let o of ons) {
42
+ let dist = ((o - phase) % beatInterval + beatInterval) % beatInterval
43
+ if (dist > beatInterval / 2) dist = beatInterval - dist
44
+ score -= dist
45
+ }
46
+ if (score > bestScore) { bestScore = score; bestPhase = phase }
47
+ }
48
+
49
+ let beats = []
50
+ for (let t = bestPhase; t < duration; t += beatInterval) beats.push(t)
51
+
52
+ // if the first beat leaves a gap at t=0, snap one beat back to cover the start
53
+ if (beats.length > 0 && beats[0] > beatInterval * 0.25)
54
+ beats.unshift(Math.max(0, beats[0] - beatInterval))
55
+
56
+ return { bpm, confidence, beats: new Float64Array(beats), onsets: ons }
57
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@audio/beat-detect",
3
+ "version": "1.0.0",
4
+ "description": "Full beat detection pipeline: onset detection → tempo estimation → beat grid",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "detect.js",
8
+ "exports": {
9
+ ".": "./detect.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "detect.js"
14
+ ],
15
+ "dependencies": {
16
+ "@audio/beat-core": "^1.0.0",
17
+ "@audio/beat-tempo": "^1.0.0"
18
+ },
19
+ "keywords": [
20
+ "audio",
21
+ "dsp",
22
+ "beat",
23
+ "beat-detection",
24
+ "bpm"
25
+ ],
26
+ "license": "MIT",
27
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/audiojs/beat.git",
31
+ "directory": "packages/beat-detect"
32
+ },
33
+ "homepage": "https://github.com/audiojs/beat/tree/main/packages/beat-detect",
34
+ "bugs": {
35
+ "url": "https://github.com/audiojs/beat/issues"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }