@audio/pitch-amdf 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/amdf.js +49 -0
  2. package/package.json +40 -0
package/amdf.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * AMDF (Average Magnitude Difference Function) pitch detection.
3
+ * Ross et al., 1974 — the classical predecessor to YIN.
4
+ *
5
+ * @param {Float32Array | Float64Array} data
6
+ * @param {{fs?: number, minFreq?: number, maxFreq?: number, threshold?: number}} [params]
7
+ * @returns {{freq: number, clarity: number} | null}
8
+ */
9
+ export default function amdf(data, params) {
10
+ let fs = params?.fs || 44100
11
+ let minFreq = params?.minFreq ?? 50
12
+ let maxFreq = params?.maxFreq ?? 2000
13
+ let threshold = params?.threshold ?? 0.3
14
+ let len = data.length
15
+
16
+ let tauMin = Math.max(2, Math.floor(fs / maxFreq))
17
+ let tauMax = Math.min(len >> 1, Math.ceil(fs / minFreq))
18
+ if (tauMax <= tauMin + 1) return null
19
+
20
+ // d(τ) = (1/(N-τ)) Σ |x[i] - x[i+τ]|
21
+ let d = new Float64Array(tauMax + 1)
22
+ for (let tau = tauMin; tau <= tauMax; tau++) {
23
+ let sum = 0
24
+ let count = len - tau
25
+ for (let i = 0; i < count; i++) sum += Math.abs(data[i] - data[i + tau])
26
+ d[tau] = sum / count
27
+ }
28
+
29
+ // normalize by max so threshold is scale-invariant
30
+ let dMax = 0
31
+ for (let tau = tauMin; tau <= tauMax; tau++) if (d[tau] > dMax) dMax = d[tau]
32
+ if (dMax === 0) return null
33
+ for (let tau = tauMin; tau <= tauMax; tau++) d[tau] /= dMax
34
+
35
+ // find first local minimum below threshold
36
+ let tau = tauMin + 1
37
+ while (tau < tauMax) {
38
+ if (d[tau] < threshold && d[tau] <= d[tau - 1] && d[tau] <= d[tau + 1]) break
39
+ tau++
40
+ }
41
+ if (tau >= tauMax) return null
42
+
43
+ // parabolic interpolation around the minimum
44
+ let s0 = d[tau - 1], s1 = d[tau], s2 = d[tau + 1]
45
+ let denom = s0 - 2 * s1 + s2
46
+ let period = denom !== 0 ? tau + (s0 - s2) / (2 * denom) : tau
47
+
48
+ return { freq: fs / period, clarity: 1 - s1 }
49
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@audio/pitch-amdf",
3
+ "version": "1.0.0",
4
+ "description": "AMDF (Average Magnitude Difference Function) pitch detection",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "amdf.js",
8
+ "exports": {
9
+ ".": "./amdf.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "amdf.js"
14
+ ],
15
+ "keywords": [
16
+ "audio",
17
+ "dsp",
18
+ "pitch",
19
+ "pitch-detection",
20
+ "f0",
21
+ "amdf"
22
+ ],
23
+ "license": "MIT",
24
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/audiojs/pitch.git",
28
+ "directory": "packages/pitch-amdf"
29
+ },
30
+ "homepage": "https://github.com/audiojs/pitch/tree/main/packages/pitch-amdf",
31
+ "bugs": {
32
+ "url": "https://github.com/audiojs/pitch/issues"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ }
40
+ }