@audio/mir-key 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/key.js +68 -0
- package/package.json +31 -0
package/key.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Krumhansl–Kessler major and minor key profiles.
|
|
5
|
+
* From Krumhansl & Kessler (1982), as used in the Krumhansl–Schmuckler
|
|
6
|
+
* key-finding algorithm. Values are perceived stability ratings per scale degree.
|
|
7
|
+
*/
|
|
8
|
+
export const KK_MAJOR = [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88]
|
|
9
|
+
export const KK_MINOR = [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detect musical key from a chroma vector using the Krumhansl–Schmuckler
|
|
13
|
+
* algorithm: Pearson correlation between the input chroma and each of 24
|
|
14
|
+
* rotated major/minor key profiles. The key with the highest correlation wins.
|
|
15
|
+
*
|
|
16
|
+
* Accepts either a single 12-D chroma vector or an array of frames (in which
|
|
17
|
+
* case the mean across frames is used).
|
|
18
|
+
*
|
|
19
|
+
* @param {(Float64Array | Float32Array | number[]) | (Float64Array | Float32Array | number[])[]} input
|
|
20
|
+
* @param {{profile?: {major: number[], minor: number[]}}} [params]
|
|
21
|
+
* @returns {{tonic: number, mode: 'major'|'minor', label: string, confidence: number, scores: {label: string, score: number}[]}}
|
|
22
|
+
*/
|
|
23
|
+
export default function key(input, params) {
|
|
24
|
+
let profile = params?.profile ?? { major: KK_MAJOR, minor: KK_MINOR }
|
|
25
|
+
|
|
26
|
+
// accept single vector or sequence of frames
|
|
27
|
+
let v = new Float64Array(12)
|
|
28
|
+
if (Array.isArray(input) && input.length && typeof input[0] !== 'number') {
|
|
29
|
+
for (let f of input) for (let i = 0; i < 12; i++) v[i] += f[i]
|
|
30
|
+
for (let i = 0; i < 12; i++) v[i] /= input.length
|
|
31
|
+
} else {
|
|
32
|
+
for (let i = 0; i < 12; i++) v[i] = input[i]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let scores = []
|
|
36
|
+
for (let r = 0; r < 12; r++) {
|
|
37
|
+
scores.push({ label: NOTE_NAMES[r], score: pearson(v, rotate(profile.major, r)) })
|
|
38
|
+
scores.push({ label: NOTE_NAMES[r] + 'm', score: pearson(v, rotate(profile.minor, r)) })
|
|
39
|
+
}
|
|
40
|
+
scores.sort((a, b) => b.score - a.score)
|
|
41
|
+
|
|
42
|
+
let best = scores[0]
|
|
43
|
+
let mode = best.label.endsWith('m') ? 'minor' : 'major'
|
|
44
|
+
let tonic = NOTE_NAMES.indexOf(mode === 'minor' ? best.label.slice(0, -1) : best.label)
|
|
45
|
+
return { tonic, mode, label: best.label, confidence: best.score, scores }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rotate(arr, r) {
|
|
49
|
+
let out = new Array(12)
|
|
50
|
+
for (let i = 0; i < 12; i++) out[i] = arr[(i - r + 12) % 12]
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pearson(a, b) {
|
|
55
|
+
let n = 12
|
|
56
|
+
let ma = 0, mb = 0
|
|
57
|
+
for (let i = 0; i < n; i++) { ma += a[i]; mb += b[i] }
|
|
58
|
+
ma /= n; mb /= n
|
|
59
|
+
let num = 0, da = 0, db = 0
|
|
60
|
+
for (let i = 0; i < n; i++) {
|
|
61
|
+
let ax = a[i] - ma, bx = b[i] - mb
|
|
62
|
+
num += ax * bx
|
|
63
|
+
da += ax * ax
|
|
64
|
+
db += bx * bx
|
|
65
|
+
}
|
|
66
|
+
let denom = Math.sqrt(da * db)
|
|
67
|
+
return denom === 0 ? 0 : num / denom
|
|
68
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/mir-key",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Krumhansl–Kessler major and minor key profiles",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "key.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./key.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"key.js"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"audio",
|
|
17
|
+
"dsp",
|
|
18
|
+
"mir",
|
|
19
|
+
"key",
|
|
20
|
+
"key-detection",
|
|
21
|
+
"krumhansl"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Dmitry Iv. <dfcreative@gmail.com>",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|