@audio/pitch 2.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 (4) hide show
  1. package/LICENSE +25 -0
  2. package/index.js +12 -0
  3. package/package.json +78 -0
  4. package/readme.md +430 -0
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) Dmitry Iv
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This work is offered under the Krishnized License (https://github.com/krishnized/license).
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // @audio/pitch — pitch detection umbrella re-exporting every @audio/pitch-* atom.
2
+ // For smaller bundles, depend directly on the individual atom.
3
+ // Chroma / chord / key detection moved to @audio/mir.
4
+
5
+ export { default as yin } from '@audio/pitch-yin'
6
+ export { default as mcleod } from '@audio/pitch-mcleod'
7
+ export { default as pyin } from '@audio/pitch-pyin'
8
+ export { default as hps } from '@audio/pitch-hps'
9
+ export { default as cepstrum } from '@audio/pitch-cepstrum'
10
+ export { default as swipe } from '@audio/pitch-swipe'
11
+ export { default as autocorrelation } from '@audio/pitch-autocorrelation'
12
+ export { default as amdf } from '@audio/pitch-amdf'
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@audio/pitch",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "description": "Pitch detection — umbrella for @audio/pitch-* atoms (YIN, pYIN, McLeod, HPS, cepstrum, SWIPE, autocorrelation, AMDF)",
6
+ "exports": {
7
+ ".": "./index.js",
8
+ "./package.json": "./package.json"
9
+ },
10
+ "files": [
11
+ "index.js"
12
+ ],
13
+ "scripts": {
14
+ "test": "node test.js",
15
+ "test:all": "npm test --workspaces --if-present && npm test",
16
+ "publish:all": "npm publish --workspaces && npm publish"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/audiojs/pitch.git"
21
+ },
22
+ "keywords": [
23
+ "pitch",
24
+ "pitch-detection",
25
+ "yin",
26
+ "pyin",
27
+ "mcleod",
28
+ "mpm",
29
+ "autocorrelation",
30
+ "amdf",
31
+ "hps",
32
+ "cepstrum",
33
+ "swipe",
34
+ "chroma",
35
+ "pcp",
36
+ "nnls",
37
+ "chord",
38
+ "chord-detection",
39
+ "key-detection",
40
+ "krumhansl",
41
+ "frequency",
42
+ "f0",
43
+ "fundamental",
44
+ "audio",
45
+ "dsp",
46
+ "signal-processing"
47
+ ],
48
+ "author": "Dmitry Iv. <dfcreative@gmail.com>",
49
+ "license": "MIT",
50
+ "bugs": {
51
+ "url": "https://github.com/audiojs/pitch/issues"
52
+ },
53
+ "homepage": "https://github.com/audiojs/pitch#readme",
54
+ "engines": {
55
+ "node": ">=18"
56
+ },
57
+ "dependencies": {
58
+ "@audio/pitch-yin": "^1.0.0",
59
+ "@audio/pitch-mcleod": "^1.0.0",
60
+ "@audio/pitch-pyin": "^1.0.0",
61
+ "@audio/pitch-hps": "^1.0.0",
62
+ "@audio/pitch-cepstrum": "^1.0.0",
63
+ "@audio/pitch-swipe": "^1.0.0",
64
+ "@audio/pitch-autocorrelation": "^1.0.0",
65
+ "@audio/pitch-amdf": "^1.0.0"
66
+ },
67
+ "devDependencies": {
68
+ "tst": "^9.4.0"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public"
72
+ },
73
+ "sideEffects": false,
74
+ "main": "index.js",
75
+ "workspaces": [
76
+ "packages/*"
77
+ ]
78
+ }
package/readme.md ADDED
@@ -0,0 +1,430 @@
1
+ # @audio/pitch [![test](https://github.com/audiojs/pitch/actions/workflows/test.yml/badge.svg)](https://github.com/audiojs/pitch/actions/workflows/test.yml) [![npm](https://img.shields.io/npm/v/@audio/pitch)](https://www.npmjs.com/package/@audio/pitch) [![MIT](https://img.shields.io/badge/MIT-%E0%A5%90-white)](https://github.com/krishnized/license)
2
+
3
+ Pitch, chroma, chord and key detection. YIN, McLeod, pYIN, HPS, cepstrum, SWIPE, autocorrelation, AMDF, NNLS chroma, chord templates, Krumhansl-Schmuckler.
4
+
5
+ <table><tr><td valign="top">
6
+
7
+ ### Pitch
8
+
9
+ **[YIN](#yin)** — cumulative mean normalized difference<br>
10
+ **[McLeod](#mcleod)** — normalized square difference (MPM)<br>
11
+ **[pYIN](#pyin)** — probabilistic YIN with Beta prior<br>
12
+ **[Autocorrelation](#autocorrelation)** — normalized autocorrelation<br>
13
+ **[AMDF](#amdf)** — average magnitude difference<br>
14
+
15
+ </td><td valign="top">
16
+
17
+ ### Spectral pitch
18
+
19
+ **[HPS](#hps)** — harmonic product spectrum<br>
20
+ **[Cepstrum](#cepstrum)** — real cepstrum peak picking<br>
21
+ **[SWIPE](#swipe)** — sawtooth waveform inspired estimator<br>
22
+
23
+ </td><td valign="top">
24
+
25
+ ### Harmony
26
+
27
+ **[Chroma](#chroma)** — PCP / NNLS pitch-class profiles<br>
28
+ **[Chord](#chord)** — template matching + Viterbi smoothing<br>
29
+ **[Key](#key)** — Krumhansl-Schmuckler key finding<br>
30
+
31
+ </td></tr></table>
32
+
33
+
34
+ ## Install
35
+
36
+ ```
37
+ npm install @audio/pitch
38
+ ```
39
+
40
+
41
+ ## Usage
42
+
43
+ ```js
44
+ import { yin, mcleod, chroma, chord, key } from '@audio/pitch'
45
+
46
+ let fs = 44100
47
+ let frame = new Float32Array(2048) // fill from your audio source
48
+
49
+ // pitch
50
+ let result = yin(frame, { fs })
51
+ // → { freq: 440.1, clarity: 0.97 } or null
52
+
53
+ // chroma → chord → key
54
+ let c = chroma(frame, { fs, method: 'nnls' })
55
+ let ch = chord(c)
56
+ // → { root: 0, quality: 'maj', label: 'C', confidence: 0.92 }
57
+ let k = key(c)
58
+ // → { tonic: 0, mode: 'major', label: 'C', confidence: 0.85, scores: [...] }
59
+ ```
60
+
61
+ > Works in Node.js and browser. No Web Audio API needed — operates on raw `Float32Array` samples.
62
+
63
+ **Sliding windows** — call repeatedly as new samples arrive:
64
+
65
+ ```js
66
+ let hop = 512
67
+ for (let i = 0; i + 2048 <= samples.length; i += hop) {
68
+ let frame = samples.subarray(i, i + 2048)
69
+ let result = yin(frame, { fs })
70
+ if (result) console.log(i / fs, result.freq.toFixed(1))
71
+ }
72
+ ```
73
+
74
+ **Full pipeline** — pitch → chroma → chord → key on a sequence of frames:
75
+
76
+ ```js
77
+ import { chroma, chord, smoothChords, key } from '@audio/pitch'
78
+
79
+ let frames = []
80
+ for (let i = 0; i + 4096 <= samples.length; i += 2048) {
81
+ frames.push(chroma(samples.subarray(i, i + 4096), { fs, method: 'nnls' }))
82
+ }
83
+ let chords = smoothChords(frames, { selfProb: 0.5 })
84
+ // → [{ root: 0, quality: 'maj', label: 'C' }, ...]
85
+ let k = key(frames)
86
+ // → { tonic: 0, mode: 'major', label: 'C', confidence: 0.85, scores: [...] }
87
+ ```
88
+
89
+
90
+ ## API
91
+
92
+ All pitch algorithms return `{ freq, clarity } | null`:
93
+
94
+ - `freq` — fundamental frequency in Hz
95
+ - `clarity` — algorithm-specific confidence in `[0, 1]`
96
+ - `null` — no periodic structure found (silence, noise, polyphony)
97
+
98
+ Time-domain algorithms (YIN, McLeod, pYIN, autocorrelation, AMDF) accept any buffer length. Spectral algorithms (HPS, cepstrum, SWIPE, chroma) require power-of-2 length.
99
+
100
+
101
+ ---
102
+
103
+
104
+ ## YIN
105
+
106
+ **de Cheveigné & Kawahara, 2002.** The reference algorithm for monophonic pitch estimation. Most cited, most tested, most robust.
107
+
108
+ ```js
109
+ import yin from '@audio/pitch/yin.js'
110
+
111
+ let result = yin(samples, { fs: 44100 })
112
+ ```
113
+
114
+ | Param | Default | |
115
+ |---|---|---|
116
+ | `fs` | `44100` | Sample rate (Hz) |
117
+ | `threshold` | `0.15` | CMND threshold — lower = stricter, fewer detections |
118
+
119
+ **Use when:** General-purpose monophonic pitch tracking — speech, singing, solo instruments. The most reliable choice when in doubt.<br>
120
+ **Not for:** Polyphonic audio (returns dominant or null), real-time with hard latency budgets (needs full window).<br>
121
+ **Ref:** de Cheveigné & Kawahara, ["YIN, a fundamental frequency estimator for speech and music"](https://doi.org/10.1121/1.1458024), JASA 2002.<br>
122
+ **Complexity:** $O(N^2/4)$ — two nested passes over half the window.
123
+
124
+
125
+ ## McLeod
126
+
127
+ **McLeod & Wyvill, 2005.** Normalized square difference with smarter peak picking. Handles smaller windows — good for vibrato and fast pitch changes.
128
+
129
+ ```js
130
+ import mcleod from '@audio/pitch/mcleod.js'
131
+
132
+ let result = mcleod(samples, { fs: 44100 })
133
+ ```
134
+
135
+ | Param | Default | |
136
+ |---|---|---|
137
+ | `fs` | `44100` | Sample rate (Hz) |
138
+ | `threshold` | `0.9` | Peak selection threshold as fraction of global max |
139
+
140
+ **Use when:** Vibrato tracking, small hop sizes, singing voice where YIN occasionally double-triggers.<br>
141
+ **Not for:** Highly noisy signals (NSDF is less thresholded than YIN's CMND).<br>
142
+ **Ref:** McLeod & Wyvill, ["A smarter way to find pitch"](https://www.cs.otago.ac.nz/research/publications/oucs-2008-03.pdf), ICMC 2005.<br>
143
+ **Complexity:** $O(N^2/4)$ — same asymptotic cost as YIN.
144
+
145
+
146
+ ## pYIN
147
+
148
+ **Mauch & Dixon, 2014.** Probabilistic YIN — runs YIN at multiple thresholds weighted by a Beta(2, 18) prior, producing a distribution over candidate pitches instead of a single hard pick. More robust than YIN on ambiguous frames.
149
+
150
+ ```js
151
+ import pyin from '@audio/pitch/pyin.js'
152
+
153
+ let result = pyin(samples, { fs: 44100 })
154
+ // → { freq: 440.1, clarity: 0.92, candidates: [{ freq: 440.1, prob: 0.85 }, ...] }
155
+ ```
156
+
157
+ | Param | Default | |
158
+ |---|---|---|
159
+ | `fs` | `44100` | Sample rate (Hz) |
160
+ | `minFreq` | `50` | Minimum detectable frequency (Hz) |
161
+ | `maxFreq` | `2000` | Maximum detectable frequency (Hz) |
162
+
163
+ **Use when:** Ambiguous pitched content — breathy vocals, noisy recordings, or when you need a pitch posterior for downstream HMM tracking.<br>
164
+ **Not for:** Clean signals where YIN already works well (pYIN is ~10× slower due to multi-threshold sweep).<br>
165
+ **Ref:** Mauch & Dixon, ["pYIN: A Fundamental Frequency Estimator Using Probabilistic Threshold Distributions"](https://doi.org/10.1109/ICASSP.2014.6853678), ICASSP 2014.
166
+
167
+
168
+ ## Autocorrelation
169
+
170
+ Normalized autocorrelation — the simplest pitch estimator. Educational baseline.
171
+
172
+ ```js
173
+ import autocorrelation from '@audio/pitch/autocorrelation.js'
174
+
175
+ let result = autocorrelation(samples, { fs: 44100 })
176
+ ```
177
+
178
+ | Param | Default | |
179
+ |---|---|---|
180
+ | `fs` | `44100` | Sample rate (Hz) |
181
+ | `threshold` | `0.5` | Minimum normalized autocorrelation value to accept |
182
+
183
+ **Use when:** Learning, quick prototypes, signals with strong dominant periodicity and low noise.<br>
184
+ **Not for:** Production — octave errors are common without additional heuristics.<br>
185
+ **Ref:** Rabiner, ["Use of autocorrelation analysis for pitch detection"](https://doi.org/10.1109/TASSP.1977.1162905), IEEE TASSP 1977.<br>
186
+ **Complexity:** $O(N^2/4)$.
187
+
188
+
189
+ ## AMDF
190
+
191
+ **Ross et al., 1974.** Average Magnitude Difference Function — the classical predecessor to YIN. Measures average absolute difference between a signal and its delayed copy; minima indicate periodicity.
192
+
193
+ ```js
194
+ import amdf from '@audio/pitch/amdf.js'
195
+
196
+ let result = amdf(samples, { fs: 44100 })
197
+ ```
198
+
199
+ | Param | Default | |
200
+ |---|---|---|
201
+ | `fs` | `44100` | Sample rate (Hz) |
202
+ | `minFreq` | `50` | Minimum detectable frequency (Hz) |
203
+ | `maxFreq` | `2000` | Maximum detectable frequency (Hz) |
204
+ | `threshold` | `0.3` | Normalized AMDF dip threshold |
205
+
206
+ **Use when:** Low-complexity environments, embedded systems. Simpler and cheaper than YIN (no squaring, no cumulative normalization).<br>
207
+ **Not for:** Noisy signals — lacks YIN's cumulative normalization that suppresses octave errors.<br>
208
+ **Ref:** Ross et al., ["Average magnitude difference function pitch extractor"](https://doi.org/10.1109/TASSP.1974.1162598), IEEE TASSP 1974.<br>
209
+ **Complexity:** $O(N^2/4)$.
210
+
211
+
212
+ ---
213
+
214
+
215
+ ## HPS
216
+
217
+ **Schroeder, 1968.** Harmonic Product Spectrum — multiplies the spectrum by its downsampled copies so that harmonic peaks align at the fundamental. Robust to the missing-fundamental problem.
218
+
219
+ ```js
220
+ import hps from '@audio/pitch/hps.js'
221
+
222
+ let result = hps(samples, { fs: 44100 })
223
+ ```
224
+
225
+ | Param | Default | |
226
+ |---|---|---|
227
+ | `fs` | `44100` | Sample rate (Hz) |
228
+ | `harmonics` | `5` | Number of harmonic products |
229
+ | `minFreq` | `50` | Minimum detectable frequency (Hz) |
230
+ | `maxFreq` | `4000` | Maximum detectable frequency (Hz) |
231
+ | `cents` | `10` | Candidate spacing in cents |
232
+ | `threshold` | `0.1` | Minimum clarity to accept |
233
+
234
+ **Use when:** Harmonic-rich signals (guitar, piano, brass). Naturally handles missing fundamentals.<br>
235
+ **Not for:** Pure sinusoids (only one harmonic), very noisy signals.<br>
236
+ **Ref:** Schroeder, ["Period histogram and product spectrum"](https://doi.org/10.1121/1.1910902), JASA 1968.<br>
237
+ **Requires:** Power-of-2 window length.
238
+
239
+
240
+ ## Cepstrum
241
+
242
+ **Noll, 1967.** Real cepstrum — $c(\tau) = \text{IFFT}(\log |\text{FFT}(x)|)$. A peak at quefrency $\tau$ corresponds to period $\tau$ in the time domain.
243
+
244
+ ```js
245
+ import cepstrum from '@audio/pitch/cepstrum.js'
246
+
247
+ let result = cepstrum(samples, { fs: 44100 })
248
+ ```
249
+
250
+ | Param | Default | |
251
+ |---|---|---|
252
+ | `fs` | `44100` | Sample rate (Hz) |
253
+ | `minFreq` | `50` | Minimum detectable frequency (Hz) |
254
+ | `maxFreq` | `2000` | Maximum detectable frequency (Hz) |
255
+ | `threshold` | `0.3` | Minimum clarity to accept |
256
+
257
+ **Use when:** Harmonic signals where you want a clean spectral-domain method. Good pedagogical complement to time-domain algorithms.<br>
258
+ **Not for:** Low-pitched signals (quefrency resolution is limited by window length).<br>
259
+ **Ref:** Noll, ["Cepstrum pitch determination"](https://doi.org/10.1121/1.1911537), JASA 1967.<br>
260
+ **Requires:** Power-of-2 window length.
261
+
262
+
263
+ ## SWIPE
264
+
265
+ **Camacho & Harris, 2008.** SWIPE' (Sawtooth Waveform Inspired Pitch Estimator, prime harmonics). Measures spectral similarity between the window and a sawtooth template whose lobes sit at prime harmonics. More accurate than HPS on clean instrumental signals; robust against octave errors because only prime harmonics contribute.
266
+
267
+ Simplified single-window form: uses one FFT instead of the multi-resolution loudness pyramid of the original paper — sufficient for stationary windows.
268
+
269
+ ```js
270
+ import swipe from '@audio/pitch/swipe.js'
271
+
272
+ let result = swipe(samples, { fs: 44100 })
273
+ ```
274
+
275
+ | Param | Default | |
276
+ |---|---|---|
277
+ | `fs` | `44100` | Sample rate (Hz) |
278
+ | `minFreq` | `60` | Minimum detectable frequency (Hz) |
279
+ | `maxFreq` | `4000` | Maximum detectable frequency (Hz) |
280
+ | `cents` | `10` | Candidate spacing in cents |
281
+ | `threshold` | `0.15` | Minimum clarity to accept |
282
+
283
+ **Use when:** Clean instrumental signals, studio recordings, where sub-Hz accuracy matters.<br>
284
+ **Not for:** Very noisy or reverberant signals (single-window form lacks multi-resolution robustness of the full SWIPE').<br>
285
+ **Ref:** Camacho & Harris, ["A sawtooth waveform inspired pitch estimator for speech and music"](https://doi.org/10.1121/1.2951592), JASA 2008.<br>
286
+ **Requires:** Power-of-2 window length.
287
+
288
+
289
+ ---
290
+
291
+
292
+ ## Chroma
293
+
294
+ **Fujishima, 1999 (PCP) / Mauch & Dixon, 2010 (NNLS).** Chroma feature — a 12-D vector where each bin holds the energy attributed to one pitch class (C, C#, ..., B).
295
+
296
+ ```js
297
+ import chroma from '@audio/pitch/chroma.js'
298
+
299
+ // PCP — classical spectral folding
300
+ let c = chroma(samples, { fs: 44100 })
301
+
302
+ // NNLS — nonnegative least squares (cleaner for polyphonic audio)
303
+ let c2 = chroma(samples, { fs: 44100, method: 'nnls' })
304
+ ```
305
+
306
+ ### PCP (default)
307
+
308
+ Each spectral bin is mapped to its nearest pitch class and squared magnitudes are accumulated. Simple and fast.
309
+
310
+ ### NNLS
311
+
312
+ Fits the observed $\sqrt{\text{spectrum}}$ as a nonnegative combination of synthetic pitch-tone profiles (fundamental plus geometrically decaying overtones, Gaussian lobes in log-frequency with σ = 0.5 semitones). Uses multiplicative NMF updates: $a \leftarrow a \cdot (D^\top s) / (D^\top D a + \varepsilon)$. Suppresses octave and harmonic confusion on polyphonic audio.
313
+
314
+ Pitch dictionary covers MIDI 24–96 (C1–C7) with configurable harmonics per tone.
315
+
316
+ | Param | Default | |
317
+ |---|---|---|
318
+ | `fs` | `44100` | Sample rate (Hz) |
319
+ | `method` | `'pcp'` | `'pcp'` or `'nnls'` |
320
+ | `minFreq` | `65` | Min frequency for PCP mapping (~C2) |
321
+ | `maxFreq` | `2093` | Max frequency for PCP mapping (~C7) |
322
+ | `harmonics` | `8` | Overtones per pitch (NNLS only) |
323
+ | `iterations` | `30` | NMF iterations (NNLS only) |
324
+
325
+ **Returns:** `Float64Array(12)`, L1-normalized.
326
+
327
+ **Use when:** Building chord/key detectors, music information retrieval, audio fingerprinting. NNLS for polyphonic; PCP for speed.<br>
328
+ **Ref (PCP):** Fujishima, ["Realtime chord recognition of musical sound"](https://doi.org/10.1109/ICMC.1999.318003), ICMC 1999.<br>
329
+ **Ref (NNLS):** Mauch & Dixon, ["Approximate Note Transcription for the Improved Identification of Difficult Chords"](https://doi.org/10.5281/zenodo.1416026), ISMIR 2010.<br>
330
+ **Requires:** Power-of-2 window length.
331
+
332
+
333
+ ## Chord
334
+
335
+ **Fujishima, 1999 (templates) / Viterbi smoothing.** Classifies chroma frames as one of 24 major/minor triads via cosine similarity with binary templates.
336
+
337
+ ```js
338
+ import chord, { TEMPLATES, smooth as smoothChords } from '@audio/pitch/chord.js'
339
+
340
+ // single frame
341
+ let c = chord(chromaVec)
342
+ // → { root: 0, quality: 'maj', label: 'C', confidence: 0.92 }
343
+
344
+ // smoothed sequence
345
+ let chords = smoothChords(chromaFrames, { selfProb: 0.5 })
346
+ // → [{ root: 0, quality: 'maj', label: 'C' }, ...]
347
+ ```
348
+
349
+ ### `chord(chromaVec, opts)`
350
+
351
+ Cosine similarity against 24 binary templates (12 major + 12 minor triads). Returns the best match with confidence score.
352
+
353
+ | Param | Default | |
354
+ |---|---|---|
355
+ | `minConfidence` | `0.3` | Below this, returns quality `'N'` (no chord) |
356
+
357
+ **Returns:** `{ root, quality, label, confidence }` where quality is `'maj'`, `'min'`, or `'N'`.
358
+
359
+ ### `smooth(frames, opts)`
360
+
361
+ Viterbi decoding with a sticky self-transition prior. Observation log-likelihood = 8 × cosine similarity (temperature 8 gives reasonably sharp distributions).
362
+
363
+ | Param | Default | |
364
+ |---|---|---|
365
+ | `selfProb` | `0.5` | Self-transition probability (higher = smoother) |
366
+
367
+ **Returns:** `{ root, quality, label }[]` — one chord per frame.
368
+
369
+ ### `TEMPLATES`
370
+
371
+ Exported array of 24 chord templates: `{ root, quality, label, vec }` where `vec` is a `Float64Array(12)` with 1 on chord tones.
372
+
373
+ **Use when:** Quick chord labeling from chroma features. Combine with NNLS chroma for best results.<br>
374
+ **Ref:** Fujishima, ["Realtime chord recognition of musical sound"](https://doi.org/10.1109/ICMC.1999.318003), ICMC 1999.
375
+
376
+
377
+ ## Key
378
+
379
+ **Krumhansl & Schmuckler.** Detects musical key from chroma via Pearson correlation against 24 rotated major/minor key profiles (Krumhansl-Kessler probe-tone ratings).
380
+
381
+ ```js
382
+ import key, { KK_MAJOR, KK_MINOR } from '@audio/pitch/key.js'
383
+
384
+ let k = key(chromaVec)
385
+ // → { tonic: 0, mode: 'major', label: 'C', confidence: 0.85, scores: [...] }
386
+
387
+ // from multiple frames (averages internally)
388
+ let k2 = key(chromaFrames)
389
+ ```
390
+
391
+ | Param | Default | |
392
+ |---|---|---|
393
+ | `profile` | `{ major: KK_MAJOR, minor: KK_MINOR }` | Custom key profiles |
394
+
395
+ **Returns:** `{ tonic, mode, label, confidence, scores }` where `scores` is all 24 keys sorted descending.
396
+
397
+ ### Exported profiles
398
+
399
+ - `KK_MAJOR` — Krumhansl-Kessler major profile: `[6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88]`
400
+ - `KK_MINOR` — Krumhansl-Kessler minor profile: `[6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]`
401
+
402
+ **Use when:** Key detection for music analysis, automatic transposition, music information retrieval.<br>
403
+ **Ref:** Krumhansl, [*Cognitive Foundations of Musical Pitch*](https://doi.org/10.1093/acprof:oso/9780195148367.001.0001), Oxford 1990.<br>
404
+ **Ref:** Temperley, ["What's Key for Key?"](https://doi.org/10.1525/mp.1999.17.1.65), Music Perception 1999.
405
+
406
+
407
+ ---
408
+
409
+
410
+ ## Comparison
411
+
412
+ ### Pitch algorithms
413
+
414
+ | | YIN | McLeod | pYIN | AMDF | HPS | Cepstrum | SWIPE |
415
+ |---|---|---|---|---|---|---|---|
416
+ | **Domain** | time | time | time | time | spectral | spectral | spectral |
417
+ | **Accuracy** | ★★★★★ | ★★★★ | ★★★★★ | ★★★ | ★★★★ | ★★★ | ★★★★★ |
418
+ | **Noise robustness** | ★★★★★ | ★★★★ | ★★★★★ | ★★★ | ★★★ | ★★★ | ★★★★ |
419
+ | **Octave errors** | rare | rare | rare | common | rare | occasional | rare |
420
+ | **Missing fundamental** | no | no | no | no | yes | yes | yes |
421
+ | **Min window** | ~4 periods | ~2 periods | ~4 periods | ~4 periods | power of 2 | power of 2 | power of 2 |
422
+ | **Best for** | general | vibrato | ambiguous | embedded | harmonic-rich | pedagogical | studio |
423
+
424
+ ## See also
425
+
426
+ - [fourier-transform](https://github.com/scijs/fourier-transform) — FFT used by spectral algorithms
427
+ - [beat](https://github.com/audiojs/beat) — onset detection, tempo estimation, beat tracking
428
+ - [digital-filter](https://github.com/audiojs/digital-filter) — filter design and processing
429
+ - [stretch](https://github.com/audiojs/stretch) — time stretching and pitch shifting
430
+ - [shift](https://github.com/audiojs/shift) — pitch shifting algorithms