@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.
Files changed (2) hide show
  1. package/package.json +32 -0
  2. 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
+ }