@audio/spectral-harmonics 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/harmonics.js +43 -0
- package/package.json +32 -0
package/harmonics.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Harmonic descriptors from f0 — partial amplitudes at k·f0 give inharmonicity,
|
|
2
|
+
// tristimulus (T1/T2/T3) and odd/even energy ratio (essentia parity).
|
|
3
|
+
|
|
4
|
+
import { fft } from 'fourier-transform'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {Float32Array} data — mono PCM frame (power of 2)
|
|
8
|
+
* @param {object} opts — { fs=44100, f0 (required, Hz), nHarmonics=10 }
|
|
9
|
+
* @returns {{ amps: Float32Array, inharmonicity: number, tristimulus: [n,n,n], oddEven: number }}
|
|
10
|
+
*/
|
|
11
|
+
export default function harmonics (data, { fs = 44100, f0, nHarmonics = 10 } = {}) {
|
|
12
|
+
if (!f0) throw new RangeError('harmonics: opts.f0 required')
|
|
13
|
+
let n = data.length, half = n / 2
|
|
14
|
+
let buf = new Float64Array(n)
|
|
15
|
+
for (let i = 0; i < n; i++) buf[i] = data[i] * (0.5 - 0.5 * Math.cos(2 * Math.PI * i / n))
|
|
16
|
+
let [re, im] = fft(buf)
|
|
17
|
+
let mag = k => Math.hypot(re[k], im[k])
|
|
18
|
+
|
|
19
|
+
let amps = new Float32Array(nHarmonics)
|
|
20
|
+
let inh = 0, total = 0
|
|
21
|
+
for (let h = 1; h <= nHarmonics; h++) {
|
|
22
|
+
let kc = h * f0 * n / fs
|
|
23
|
+
if (kc >= half - 2) break
|
|
24
|
+
let b = Math.round(kc), best = b, m = 0
|
|
25
|
+
for (let j = Math.max(1, b - 2); j <= Math.min(half, b + 2); j++) if (mag(j) > m) { m = mag(j); best = j }
|
|
26
|
+
amps[h - 1] = m
|
|
27
|
+
inh += Math.abs(best - kc) / kc * m
|
|
28
|
+
total += m
|
|
29
|
+
}
|
|
30
|
+
let e = amps.map(a => a * a)
|
|
31
|
+
let sum = e.reduce((a, b) => a + b, 0) + 1e-20
|
|
32
|
+
let odd = 0, even = 0
|
|
33
|
+
for (let h = 2; h <= nHarmonics; h++) {
|
|
34
|
+
if (h % 2) odd += e[h - 1] || 0
|
|
35
|
+
else even += e[h - 1] || 0
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
amps,
|
|
39
|
+
inharmonicity: total > 0 ? inh / total : 0,
|
|
40
|
+
tristimulus: [e[0] / sum, (e[1] + e[2] + e[3]) / sum, e.slice(4).reduce((a, b) => a + b, 0) / sum],
|
|
41
|
+
oddEven: odd / (even + 1e-20),
|
|
42
|
+
}
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/spectral-harmonics",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Harmonic descriptors — inharmonicity, tristimulus, odd/even ratio from f0 (essentia parity)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "harmonics.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./harmonics.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"harmonics.js"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"fourier-transform": "^2.2.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"audio",
|
|
20
|
+
"dsp",
|
|
21
|
+
"spectral",
|
|
22
|
+
"harmonics"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|