@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.
- package/detect.js +57 -0
- 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
|
+
}
|