@audio/mir-tempogram 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/package.json +32 -0
- package/tempogram.js +41 -0
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/mir-tempogram",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tempogram \u2014 local tempo over time via onset-envelope autocorrelation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "tempogram.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./tempogram.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"tempogram.js"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@audio/beat-core": "^1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"audio",
|
|
20
|
+
"dsp",
|
|
21
|
+
"mir",
|
|
22
|
+
"tempogram"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/tempogram.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Tempogram — local tempo over time: onset envelope (spectral flux) autocorrelated
|
|
2
|
+
// over sliding windows. Shorter lags accumulate more terms, biasing toward the
|
|
3
|
+
// notated tempo over its subdivisions (librosa/MIREX tempogram class).
|
|
4
|
+
|
|
5
|
+
import { spectralFlux } from '@audio/beat-core'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Float32Array} data — mono PCM
|
|
9
|
+
* @param {object} opts — { fs=44100, frameSize=2048, hopSize=512,
|
|
10
|
+
* window=6 (s), hop=2 (s), minBpm=40, maxBpm=240 }
|
|
11
|
+
* @returns {{ times: Float32Array, bpm: Float32Array, matrix: Float32Array[],
|
|
12
|
+
* odfRate: number, minLag: number, maxLag: number }}
|
|
13
|
+
*/
|
|
14
|
+
export default function tempogram (data, { fs = 44100, frameSize = 2048, hopSize = 512, window = 6, hop = 2, minBpm = 40, maxBpm = 240 } = {}) {
|
|
15
|
+
let { odf } = spectralFlux(data, { fs, frameSize, hopSize })
|
|
16
|
+
let odfRate = fs / hopSize
|
|
17
|
+
let winN = Math.round(window * odfRate)
|
|
18
|
+
let hopN = Math.max(1, Math.round(hop * odfRate))
|
|
19
|
+
let minLag = Math.max(1, Math.round(odfRate * 60 / maxBpm))
|
|
20
|
+
let maxLag = Math.round(odfRate * 60 / minBpm)
|
|
21
|
+
|
|
22
|
+
let times = [], bpms = [], matrix = []
|
|
23
|
+
for (let s = 0; s + winN <= odf.length; s += hopN) {
|
|
24
|
+
let mean = 0
|
|
25
|
+
for (let i = s; i < s + winN; i++) mean += odf[i]
|
|
26
|
+
mean /= winN
|
|
27
|
+
|
|
28
|
+
let row = new Float32Array(maxLag - minLag + 1)
|
|
29
|
+
let best = -Infinity, bestLag = minLag
|
|
30
|
+
for (let lag = minLag; lag <= maxLag; lag++) {
|
|
31
|
+
let acc = 0
|
|
32
|
+
for (let i = s + lag; i < s + winN; i++) acc += (odf[i] - mean) * (odf[i - lag] - mean)
|
|
33
|
+
row[lag - minLag] = acc
|
|
34
|
+
if (acc > best) { best = acc; bestLag = lag }
|
|
35
|
+
}
|
|
36
|
+
matrix.push(row)
|
|
37
|
+
times.push((s + winN / 2) / odfRate)
|
|
38
|
+
bpms.push(60 * odfRate / bestLag)
|
|
39
|
+
}
|
|
40
|
+
return { times: Float32Array.from(times), bpm: Float32Array.from(bpms), matrix, odfRate, minLag, maxLag }
|
|
41
|
+
}
|