@libraz/libcantus 0.9.1 → 0.9.2

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 (154) hide show
  1. package/README.md +15 -0
  2. package/dist/analyze/index.cjs +1697 -0
  3. package/dist/analyze/index.cjs.map +1 -0
  4. package/dist/analyze/index.d.cts +429 -0
  5. package/dist/analyze/index.d.ts +429 -0
  6. package/dist/analyze/index.js +51 -0
  7. package/dist/analyze/index.js.map +1 -0
  8. package/dist/chunk-5HTNMQW4.js +653 -0
  9. package/dist/chunk-5HTNMQW4.js.map +1 -0
  10. package/dist/chunk-7Y7WF64D.js +410 -0
  11. package/dist/chunk-7Y7WF64D.js.map +1 -0
  12. package/dist/chunk-A4532ZFR.js +259 -0
  13. package/dist/chunk-A4532ZFR.js.map +1 -0
  14. package/dist/chunk-BHUT2S5G.js +22 -0
  15. package/dist/chunk-BHUT2S5G.js.map +1 -0
  16. package/dist/chunk-CCBLVAPK.js +42 -0
  17. package/dist/chunk-CCBLVAPK.js.map +1 -0
  18. package/dist/chunk-COKTX2G5.js +907 -0
  19. package/dist/chunk-COKTX2G5.js.map +1 -0
  20. package/dist/chunk-DYWFNC4A.js +152 -0
  21. package/dist/chunk-DYWFNC4A.js.map +1 -0
  22. package/dist/chunk-GQKN5KDT.js +159 -0
  23. package/dist/chunk-GQKN5KDT.js.map +1 -0
  24. package/dist/chunk-LN7ZZMD2.js +530 -0
  25. package/dist/chunk-LN7ZZMD2.js.map +1 -0
  26. package/dist/chunk-MSWFZF7J.js +118 -0
  27. package/dist/chunk-MSWFZF7J.js.map +1 -0
  28. package/dist/chunk-R3S2LUBH.js +2722 -0
  29. package/dist/chunk-R3S2LUBH.js.map +1 -0
  30. package/dist/chunk-SDM3VJJ5.js +41 -0
  31. package/dist/chunk-SDM3VJJ5.js.map +1 -0
  32. package/dist/chunk-XTC3X6MS.js +816 -0
  33. package/dist/chunk-XTC3X6MS.js.map +1 -0
  34. package/dist/chunk-ZXW5YIWK.js +55 -0
  35. package/dist/chunk-ZXW5YIWK.js.map +1 -0
  36. package/dist/core/index.cjs +432 -0
  37. package/dist/core/index.cjs.map +1 -0
  38. package/dist/core/index.d.cts +210 -0
  39. package/dist/core/index.d.ts +210 -0
  40. package/dist/core/index.js +73 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/function-2bJW1Zp_.d.ts +187 -0
  43. package/dist/function-r2sh-jOO.d.cts +187 -0
  44. package/dist/generate/index.cjs +3787 -0
  45. package/dist/generate/index.cjs.map +1 -0
  46. package/dist/generate/index.d.cts +980 -0
  47. package/dist/generate/index.d.ts +980 -0
  48. package/dist/generate/index.js +52 -0
  49. package/dist/generate/index.js.map +1 -0
  50. package/dist/index-BMPxwjwV.d.ts +151 -0
  51. package/dist/index-BOBXBNwP.d.cts +157 -0
  52. package/dist/index-BdeQ1r_p.d.cts +56 -0
  53. package/dist/index-BreoJRw0.d.ts +127 -0
  54. package/dist/index-CGMZNCx6.d.cts +140 -0
  55. package/dist/index-CGMZNCx6.d.ts +140 -0
  56. package/dist/index-CM5W4rcm.d.ts +157 -0
  57. package/dist/index-CP3wAfEH.d.ts +56 -0
  58. package/dist/index-Cl7mhOFM.d.cts +164 -0
  59. package/dist/index-Cl7mhOFM.d.ts +164 -0
  60. package/dist/index-DZ1M21do.d.cts +127 -0
  61. package/dist/index-D_ZTUNKp.d.cts +151 -0
  62. package/dist/index.cjs +3016 -2984
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +14 -3982
  65. package/dist/index.d.ts +14 -3982
  66. package/dist/index.js +170 -6504
  67. package/dist/index.js.map +1 -1
  68. package/dist/model/index.cjs +2435 -0
  69. package/dist/model/index.cjs.map +1 -0
  70. package/dist/model/index.d.cts +630 -0
  71. package/dist/model/index.d.ts +630 -0
  72. package/dist/model/index.js +21 -0
  73. package/dist/model/index.js.map +1 -0
  74. package/dist/styled-BjXtOFNk.d.cts +282 -0
  75. package/dist/styled-D7BAx3S3.d.ts +282 -0
  76. package/dist/theory/index.cjs +1677 -0
  77. package/dist/theory/index.cjs.map +1 -0
  78. package/dist/theory/index.d.cts +535 -0
  79. package/dist/theory/index.d.ts +535 -0
  80. package/dist/theory/index.js +149 -0
  81. package/dist/theory/index.js.map +1 -0
  82. package/dist/types-MGP29r3t.d.cts +30 -0
  83. package/dist/types-MGP29r3t.d.ts +30 -0
  84. package/package.json +27 -2
  85. package/src/analyze/arrange/index.ts +21 -0
  86. package/src/analyze/arrange/internal.ts +125 -0
  87. package/src/analyze/arrange/tension.ts +187 -0
  88. package/src/analyze/arrange/tracks.ts +279 -0
  89. package/src/{detect → analyze/detect}/index.ts +4 -4
  90. package/src/analyze/functional/borrowed.ts +84 -0
  91. package/src/analyze/functional/cadence.ts +66 -0
  92. package/src/analyze/functional/function.ts +184 -0
  93. package/src/analyze/functional/index.ts +23 -0
  94. package/src/analyze/functional/internal.ts +29 -0
  95. package/src/{functional/index.ts → analyze/functional/roman.ts} +9 -327
  96. package/src/analyze/index.ts +49 -0
  97. package/src/{timeline → analyze/timeline}/index.ts +7 -11
  98. package/src/{analysis → analyze/voice}/index.ts +8 -5
  99. package/src/core/index.ts +47 -0
  100. package/src/{bass → generate/bass}/index.ts +5 -5
  101. package/src/{countermelody → generate/countermelody}/index.ts +11 -11
  102. package/src/{drums → generate/drums}/index.ts +2 -2
  103. package/src/{drums → generate/drums}/rng.ts +2 -2
  104. package/src/{groove → generate/groove}/index.ts +3 -3
  105. package/src/{harmonize → generate/harmonize}/index.ts +13 -13
  106. package/src/generate/index.ts +59 -0
  107. package/src/{motif → generate/motif}/index.ts +7 -7
  108. package/src/{progression → generate/progression}/index.ts +11 -25
  109. package/src/{reharmony → generate/reharmony}/index.ts +11 -11
  110. package/src/{rhythm → generate/rhythm}/index.ts +2 -2
  111. package/src/index.ts +130 -117
  112. package/src/model/chord.ts +423 -0
  113. package/src/model/index.ts +5 -1096
  114. package/src/model/interval.ts +82 -0
  115. package/src/model/key.ts +266 -0
  116. package/src/model/note.ts +176 -0
  117. package/src/model/progression.ts +161 -0
  118. package/src/model/shared.ts +12 -0
  119. package/src/{chord → theory/chord}/index.ts +19 -1
  120. package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
  121. package/src/theory/index.ts +107 -0
  122. package/src/{safety → theory/safety}/index.ts +1 -1
  123. package/src/theory/scale/degrees.ts +123 -0
  124. package/src/theory/scale/index.ts +28 -0
  125. package/src/theory/scale/key.ts +56 -0
  126. package/src/theory/scale/masks.ts +153 -0
  127. package/src/{spelling → theory/spelling}/index.ts +3 -3
  128. package/src/{symbol → theory/symbol}/index.ts +8 -1
  129. package/src/theory/voicing/index.ts +5 -0
  130. package/src/theory/voicing/internal.ts +168 -0
  131. package/src/theory/voicing/leading.ts +112 -0
  132. package/src/theory/voicing/satb.ts +195 -0
  133. package/src/theory/voicing/styled.ts +176 -0
  134. package/src/arrange/index.ts +0 -571
  135. package/src/scale/index.ts +0 -331
  136. package/src/voicing/index.ts +0 -623
  137. /package/src/{interval → core/interval}/index.ts +0 -0
  138. /package/src/{meter → core/meter}/index.ts +0 -0
  139. /package/src/{pitch → core/pitch}/index.ts +0 -0
  140. /package/src/{random → core/random}/index.ts +0 -0
  141. /package/src/{tuning → core/tuning}/index.ts +0 -0
  142. /package/src/{types.ts → core/types.ts} +0 -0
  143. /package/src/{drums → generate/drums}/beat.ts +0 -0
  144. /package/src/{drums → generate/drums}/euclid.ts +0 -0
  145. /package/src/{drums → generate/drums}/fills.ts +0 -0
  146. /package/src/{drums → generate/drums}/ghost.ts +0 -0
  147. /package/src/{drums → generate/drums}/hihat.ts +0 -0
  148. /package/src/{drums → generate/drums}/hit.ts +0 -0
  149. /package/src/{drums → generate/drums}/internal.ts +0 -0
  150. /package/src/{drums → generate/drums}/kick.ts +0 -0
  151. /package/src/{drums → generate/drums}/percussion.ts +0 -0
  152. /package/src/{drums → generate/drums}/swing.ts +0 -0
  153. /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
  154. /package/src/{harmony → theory/harmony}/index.ts +0 -0
@@ -0,0 +1,530 @@
1
+ import {
2
+ HARMONIC_MINOR_MASK,
3
+ MAJOR_MASK,
4
+ MELODIC_MINOR_MASK,
5
+ NATURAL_MINOR_MASK,
6
+ chordPitchClasses,
7
+ chordQualities,
8
+ isScaleTone,
9
+ majorKey,
10
+ makeChord,
11
+ minorKey,
12
+ scaleTonesInDegreeOrder
13
+ } from "./chunk-7Y7WF64D.js";
14
+
15
+ // src/analyze/functional/internal.ts
16
+ function mod12(n) {
17
+ return (n % 12 + 12) % 12;
18
+ }
19
+ function degreeRootPc(degreeNumber, key) {
20
+ const tones = scaleTonesInDegreeOrder(key);
21
+ if (tones.length === 0) {
22
+ return mod12(key.rootPc);
23
+ }
24
+ return tones[(degreeNumber - 1) % tones.length] ?? mod12(key.rootPc);
25
+ }
26
+ function isNeapolitan(chord, key) {
27
+ return mod12(chord.rootPc - key.rootPc) === 1 && chord.quality === "maj";
28
+ }
29
+
30
+ // src/analyze/functional/roman.ts
31
+ var ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII"];
32
+ var ROMAN_TO_DEGREE = {
33
+ I: 1,
34
+ II: 2,
35
+ III: 3,
36
+ IV: 4,
37
+ V: 5,
38
+ VI: 6,
39
+ VII: 7
40
+ };
41
+ var OFFSET_SPELLING = [
42
+ [1, ""],
43
+ [2, "b"],
44
+ [2, ""],
45
+ [3, "b"],
46
+ [3, ""],
47
+ [4, ""],
48
+ [4, "#"],
49
+ [5, ""],
50
+ [6, "b"],
51
+ [6, ""],
52
+ [7, "b"],
53
+ [7, ""]
54
+ ];
55
+ function seventhQuality(base, halfDim) {
56
+ if (halfDim) {
57
+ return "m7b5";
58
+ }
59
+ if (base === "dim") {
60
+ return "dim7";
61
+ }
62
+ if (base === "aug") {
63
+ return "aug7";
64
+ }
65
+ return base === "maj" ? "dom7" : "min7";
66
+ }
67
+ var INVERSION_FIGURES = /* @__PURE__ */ new Set(["6", "64", "65", "43", "42", "2", "7"]);
68
+ var ROMAN_STYLE = {
69
+ maj: { lower: false, suffix: "" },
70
+ min: { lower: true, suffix: "" },
71
+ dim: { lower: true, suffix: "o" },
72
+ aug: { lower: false, suffix: "+" },
73
+ maj7: { lower: false, suffix: "maj7" },
74
+ min7: { lower: true, suffix: "7" },
75
+ dom7: { lower: false, suffix: "7" },
76
+ dim7: { lower: true, suffix: "o7" },
77
+ m7b5: { lower: true, suffix: "\xF87" },
78
+ minMaj7: { lower: true, suffix: "maj7" },
79
+ aug7: { lower: false, suffix: "+7" },
80
+ augMaj7: { lower: false, suffix: "+maj7" },
81
+ majb5: { lower: false, suffix: "b5" },
82
+ "6": { lower: false, suffix: "add6" },
83
+ min6: { lower: true, suffix: "add6" },
84
+ "6/9": { lower: false, suffix: "69" },
85
+ sus2: { lower: false, suffix: "sus2" },
86
+ sus4: { lower: false, suffix: "sus4" },
87
+ add9: { lower: false, suffix: "add9" },
88
+ add11: { lower: false, suffix: "add11" },
89
+ maj9: { lower: false, suffix: "maj9" },
90
+ min9: { lower: true, suffix: "9" },
91
+ dom9: { lower: false, suffix: "9" },
92
+ "7b9": { lower: false, suffix: "7b9" },
93
+ "7#9": { lower: false, suffix: "7#9" },
94
+ "7#11": { lower: false, suffix: "7#11" },
95
+ "7b13": { lower: false, suffix: "7b13" },
96
+ "11": { lower: false, suffix: "11" },
97
+ "13": { lower: false, suffix: "13" },
98
+ "5": { lower: false, suffix: "5" }
99
+ };
100
+ var SUFFIX_QUALITY = new Map(
101
+ Object.entries(ROMAN_STYLE).map(
102
+ ([quality, style]) => [`${style.lower ? "l" : "u"}:${style.suffix}`, quality]
103
+ )
104
+ );
105
+ function parseInversion(figures) {
106
+ if (figures === "65") {
107
+ return { inversion: 1, seventh: true };
108
+ }
109
+ if (figures === "43") {
110
+ return { inversion: 2, seventh: true };
111
+ }
112
+ if (figures === "42" || figures === "2") {
113
+ return { inversion: 3, seventh: true };
114
+ }
115
+ if (figures === "7") {
116
+ return { inversion: 0, seventh: true };
117
+ }
118
+ if (figures === "64") {
119
+ return { inversion: 2, seventh: false };
120
+ }
121
+ if (figures === "6") {
122
+ return { inversion: 1, seventh: false };
123
+ }
124
+ return { inversion: 0, seventh: false };
125
+ }
126
+ function parseSimpleRoman(text, key) {
127
+ const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());
128
+ if (!match) {
129
+ throw new Error(`Invalid Roman numeral: ${text}`);
130
+ }
131
+ const accidental = match[1] === "b" ? -1 : match[1] === "#" ? 1 : 0;
132
+ const glyph = (match[2] ?? "").toUpperCase();
133
+ const degreeNumber = ROMAN_TO_DEGREE[glyph];
134
+ if (degreeNumber === void 0) {
135
+ throw new Error(`Invalid Roman numeral: ${text}`);
136
+ }
137
+ const isUpper = (match[2] ?? "")[0] === (match[2] ?? "")[0]?.toUpperCase();
138
+ const suffix = match[3] ?? "";
139
+ const rootPc = mod12(degreeRootPc(degreeNumber, key) + accidental);
140
+ const canonical = SUFFIX_QUALITY.get(`${isUpper ? "u" : "l"}:${suffix}`);
141
+ if (canonical !== void 0) {
142
+ return { rootPc, quality: canonical, inversion: 0 };
143
+ }
144
+ const isDim = /o|°|dim/.test(suffix);
145
+ const isAug = /\+|aug/.test(suffix);
146
+ const isHalfDim = /ø/.test(suffix);
147
+ const explicitMaj7 = /maj7|M7/.test(suffix);
148
+ const figures = suffix.replace(/maj7|M7/g, "").replace(/[^0-9]/g, "");
149
+ if (figures !== "" && !INVERSION_FIGURES.has(figures)) {
150
+ throw new Error(`Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`);
151
+ }
152
+ const { inversion, seventh } = parseInversion(figures);
153
+ const hasSeventh = explicitMaj7 || seventh || isHalfDim;
154
+ let base;
155
+ if (isDim || isHalfDim) {
156
+ base = "dim";
157
+ } else if (isAug) {
158
+ base = "aug";
159
+ } else {
160
+ base = isUpper ? "maj" : "min";
161
+ }
162
+ let quality;
163
+ if (explicitMaj7) {
164
+ quality = base === "min" ? "minMaj7" : base === "aug" ? "augMaj7" : "maj7";
165
+ } else if (hasSeventh) {
166
+ quality = seventhQuality(base, isHalfDim);
167
+ } else if (base === "dim") {
168
+ quality = "dim";
169
+ } else if (base === "aug") {
170
+ quality = "aug";
171
+ } else {
172
+ quality = base;
173
+ }
174
+ return { rootPc, quality, inversion };
175
+ }
176
+ function chordFromParsed(parsed) {
177
+ const chord = makeChord(parsed.rootPc, parsed.quality);
178
+ if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {
179
+ chord.bassPc = mod12(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));
180
+ }
181
+ return chord;
182
+ }
183
+ function romanToChord(text, key) {
184
+ const trimmed = text.trim();
185
+ const slash = trimmed.indexOf("/");
186
+ if (slash >= 0) {
187
+ const applied = trimmed.slice(0, slash);
188
+ const target = trimmed.slice(slash + 1);
189
+ const targetRoot = parseSimpleRoman(target, key).rootPc;
190
+ const localKey = majorKey(targetRoot);
191
+ return chordFromParsed(parseSimpleRoman(applied, localKey));
192
+ }
193
+ return chordFromParsed(parseSimpleRoman(trimmed, key));
194
+ }
195
+ function romanStyle(quality) {
196
+ return ROMAN_STYLE[quality];
197
+ }
198
+ function romanSpelling(rootPc, key) {
199
+ const tones = scaleTonesInDegreeOrder(key);
200
+ const diatonic = tones.indexOf(mod12(rootPc));
201
+ if (diatonic >= 0) {
202
+ return { degreeNumber: diatonic + 1, accidental: "" };
203
+ }
204
+ for (let i = 0; i < tones.length; i += 1) {
205
+ const degreeNumber2 = i + 1;
206
+ if (degreeNumber2 === 1 || degreeNumber2 === 5) {
207
+ continue;
208
+ }
209
+ if (mod12((tones[i] ?? 0) - 1) === mod12(rootPc)) {
210
+ return { degreeNumber: degreeNumber2, accidental: "b" };
211
+ }
212
+ }
213
+ for (let i = 0; i < tones.length; i += 1) {
214
+ if (mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {
215
+ return { degreeNumber: i + 1, accidental: "#" };
216
+ }
217
+ }
218
+ const [degreeNumber, accidental] = OFFSET_SPELLING[mod12(rootPc - key.rootPc)] ?? [1, ""];
219
+ return { degreeNumber, accidental };
220
+ }
221
+ function baseMarker(quality) {
222
+ if (quality === "dim" || quality === "dim7") {
223
+ return "o";
224
+ }
225
+ if (quality === "m7b5") {
226
+ return "\xF8";
227
+ }
228
+ if (quality === "aug" || quality === "aug7") {
229
+ return "+";
230
+ }
231
+ if (quality === "augMaj7") {
232
+ return "+maj7";
233
+ }
234
+ if (quality === "maj7" || quality === "minMaj7") {
235
+ return "maj7";
236
+ }
237
+ return "";
238
+ }
239
+ var TRIAD_FIGURES = { 1: "6", 2: "64" };
240
+ var SEVENTH_FIGURES = { 1: "65", 2: "43", 3: "42" };
241
+ var TRIAD_FIGURE_QUALITIES = /* @__PURE__ */ new Set(["maj", "min", "dim", "aug"]);
242
+ var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
243
+ "maj7",
244
+ "min7",
245
+ "dom7",
246
+ "dim7",
247
+ "m7b5",
248
+ "minMaj7",
249
+ "aug7",
250
+ "augMaj7"
251
+ ]);
252
+ function chordToRoman(chord, key) {
253
+ const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);
254
+ const { lower, suffix } = romanStyle(chord.quality);
255
+ const numeral = ROMAN[degreeNumber - 1] ?? "I";
256
+ const cased = lower ? numeral.toLowerCase() : numeral;
257
+ let inversion = 0;
258
+ if (chord.bassPc !== void 0) {
259
+ const idx = chord.intervals.findIndex((iv) => mod12(chord.rootPc + iv) === chord.bassPc);
260
+ if (idx > 0) {
261
+ inversion = idx;
262
+ }
263
+ }
264
+ if (inversion > 0) {
265
+ const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality) ? SEVENTH_FIGURES[inversion] : TRIAD_FIGURE_QUALITIES.has(chord.quality) ? TRIAD_FIGURES[inversion] : void 0;
266
+ if (figure !== void 0) {
267
+ return `${accidental}${cased}${baseMarker(chord.quality)}${figure}`;
268
+ }
269
+ }
270
+ return `${accidental}${cased}${suffix}`;
271
+ }
272
+
273
+ // src/analyze/functional/borrowed.ts
274
+ function isHarmonicMinorAlteration(chord, key) {
275
+ return isMinorKey(key) && isDiatonic(chord, { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK });
276
+ }
277
+ function isBorrowedChord(chord, key) {
278
+ if (isDiatonic(chord, key)) {
279
+ return false;
280
+ }
281
+ if (isHarmonicMinorAlteration(chord, key)) {
282
+ return false;
283
+ }
284
+ return isDiatonic(chord, parallelKey(key));
285
+ }
286
+ function borrowedSource(chord, key) {
287
+ if (isDiatonic(chord, key)) {
288
+ return null;
289
+ }
290
+ if (isNeapolitan(chord, key)) {
291
+ return "neapolitan";
292
+ }
293
+ if (isBorrowedChord(chord, key)) {
294
+ return isMinorKey(key) ? "parallel-major" : "parallel-minor";
295
+ }
296
+ return null;
297
+ }
298
+
299
+ // src/analyze/functional/function.ts
300
+ var FUNCTION_BY_OFFSET = [
301
+ "tonic",
302
+ // 0 I
303
+ "subdominant",
304
+ // 1 bII (Neapolitan)
305
+ "subdominant",
306
+ // 2 ii
307
+ "tonic",
308
+ // 3 bIII
309
+ "tonic",
310
+ // 4 iii
311
+ "subdominant",
312
+ // 5 IV
313
+ "dominant",
314
+ // 6 #IV / bV
315
+ "dominant",
316
+ // 7 V
317
+ "subdominant",
318
+ // 8 bVI
319
+ "tonic",
320
+ // 9 vi
321
+ "subdominant",
322
+ // 10 bVII
323
+ "dominant"
324
+ // 11 vii
325
+ ];
326
+ function isMinorKey(key) {
327
+ const hasMinorThird = key.modeMask12 >> 3 & 1;
328
+ const hasMajorThird2 = key.modeMask12 >> 4 & 1;
329
+ return Boolean(hasMinorThird) && !hasMajorThird2;
330
+ }
331
+ function functionOf(chord, key) {
332
+ return FUNCTION_BY_OFFSET[mod12(chord.rootPc - key.rootPc)] ?? "tonic";
333
+ }
334
+ function isDiatonic(chord, key) {
335
+ return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
336
+ }
337
+ function parallelKey(key) {
338
+ return {
339
+ rootPc: mod12(key.rootPc),
340
+ modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK
341
+ };
342
+ }
343
+ function isDiminishedQuality(quality) {
344
+ return quality === "dim" || quality === "dim7" || quality === "m7b5";
345
+ }
346
+ function hasMajorThird(chord) {
347
+ return chord.intervals.some((interval) => mod12(interval) === 4);
348
+ }
349
+ function qualityAwareFunction(chord, key) {
350
+ const offset = mod12(chord.rootPc - key.rootPc);
351
+ if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {
352
+ return "dominant";
353
+ }
354
+ if (isNeapolitan(chord, key)) {
355
+ return "subdominant";
356
+ }
357
+ if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
358
+ return "subdominant";
359
+ }
360
+ return functionOf(chord, key);
361
+ }
362
+ function analyzeChord(chord, key) {
363
+ const source = borrowedSource(chord, key);
364
+ return {
365
+ function: qualityAwareFunction(chord, key),
366
+ borrowed: source !== null,
367
+ source,
368
+ roman: chordToRoman(chord, key)
369
+ };
370
+ }
371
+ function secondaryDominant(targetDegree, key) {
372
+ const targetRoot = degreeRootPc(targetDegree + 1, key);
373
+ return makeChord(mod12(targetRoot + 7), "dom7");
374
+ }
375
+
376
+ // src/analyze/functional/cadence.ts
377
+ function detectCadence(from, to, key) {
378
+ const tonic = mod12(key.rootPc);
379
+ const fromOffset = mod12(from.rootPc - tonic);
380
+ const toOffset = mod12(to.rootPc - tonic);
381
+ const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];
382
+ if (fromOffset === 7 && toOffset === 0) {
383
+ return "authentic";
384
+ }
385
+ if (fromOffset === 5 && toOffset === 0) {
386
+ return "plagal";
387
+ }
388
+ if (fromOffset === 7 && deceptiveTargets.includes(toOffset)) {
389
+ return "deceptive";
390
+ }
391
+ if (toOffset === 7 && fromOffset !== 7) {
392
+ return "half";
393
+ }
394
+ return null;
395
+ }
396
+
397
+ // src/analyze/detect/index.ts
398
+ function pitchClass(value) {
399
+ return (Math.trunc(value) % 12 + 12) % 12;
400
+ }
401
+ function uniquePitchClasses(pitches) {
402
+ return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);
403
+ }
404
+ function detectChord(pitches) {
405
+ const input = uniquePitchClasses(pitches);
406
+ if (input.length === 0) {
407
+ return [];
408
+ }
409
+ const bassPc = pitchClass(Math.min(...pitches));
410
+ const inputSet = new Set(input);
411
+ const matches = [];
412
+ const qualities = chordQualities();
413
+ for (const rootPc of input) {
414
+ for (const quality of qualities) {
415
+ const chord = makeChord(rootPc, quality);
416
+ const tones = chordPitchClasses(chord);
417
+ const toneSet = new Set(tones);
418
+ const missingPcs = tones.filter((pc) => !inputSet.has(pc));
419
+ const fifthPc = pitchClass(rootPc + 7);
420
+ if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
421
+ continue;
422
+ }
423
+ const extraPcs = input.filter((pc) => !toneSet.has(pc));
424
+ const bassIndex = chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);
425
+ const inversion = bassIndex > 0 ? bassIndex : 0;
426
+ const match = {
427
+ rootPc,
428
+ quality,
429
+ missingPcs,
430
+ extraPcs,
431
+ exact: extraPcs.length === 0 && missingPcs.length === 0,
432
+ inversion
433
+ };
434
+ if (inversion > 0) {
435
+ match.bassPc = bassPc;
436
+ }
437
+ matches.push(match);
438
+ }
439
+ }
440
+ matches.sort((a, b) => {
441
+ if (a.extraPcs.length !== b.extraPcs.length) {
442
+ return a.extraPcs.length - b.extraPcs.length;
443
+ }
444
+ if (a.missingPcs.length !== b.missingPcs.length) {
445
+ return a.missingPcs.length - b.missingPcs.length;
446
+ }
447
+ if (a.inversion === 0 !== (b.inversion === 0)) {
448
+ return a.inversion === 0 ? -1 : 1;
449
+ }
450
+ const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;
451
+ const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;
452
+ return bSize - aSize;
453
+ });
454
+ return matches;
455
+ }
456
+ function detectChordBest(pitches) {
457
+ const best = detectChord(pitches)[0];
458
+ if (!best) {
459
+ return null;
460
+ }
461
+ return makeChord(best.rootPc, best.quality, best.bassPc);
462
+ }
463
+ var MINOR_MASK_VARIANTS = [NATURAL_MINOR_MASK, HARMONIC_MINOR_MASK, MELODIC_MINOR_MASK];
464
+ function detectKey(pitches) {
465
+ const input = uniquePitchClasses(pitches);
466
+ if (input.length === 0) {
467
+ return [];
468
+ }
469
+ const counts = /* @__PURE__ */ new Map();
470
+ for (const pc of pitches.map(pitchClass)) {
471
+ counts.set(pc, (counts.get(pc) ?? 0) + 1);
472
+ }
473
+ const total = pitches.length;
474
+ const results = [];
475
+ for (let tonic = 0; tonic < 12; tonic += 1) {
476
+ for (const mode of ["major", "minor"]) {
477
+ const key = mode === "major" ? majorKey(tonic) : minorKey(tonic);
478
+ const masks = mode === "major" ? [MAJOR_MASK] : MINOR_MASK_VARIANTS;
479
+ let inScale = 0;
480
+ let weighted = Number.NEGATIVE_INFINITY;
481
+ for (const mask of masks) {
482
+ let variantInScale = 0;
483
+ let variantWeighted = 0;
484
+ for (const pc of input) {
485
+ const offset = (pc - tonic + 12) % 12;
486
+ if (mask >> offset & 1) {
487
+ variantInScale += 1;
488
+ }
489
+ }
490
+ for (const [pc, count] of counts) {
491
+ const offset = (pc - tonic + 12) % 12;
492
+ if (mask >> offset & 1) {
493
+ variantWeighted += count;
494
+ }
495
+ }
496
+ if (variantWeighted > weighted) {
497
+ weighted = variantWeighted;
498
+ inScale = variantInScale;
499
+ }
500
+ }
501
+ weighted += (counts.get(tonic) ?? 0) * 0.5;
502
+ results.push({
503
+ key,
504
+ mode,
505
+ fit: inScale / input.length,
506
+ score: weighted / total
507
+ });
508
+ }
509
+ }
510
+ results.sort((a, b) => b.score - a.score);
511
+ return results.map(({ key, mode, fit }) => ({ key, mode, fit }));
512
+ }
513
+
514
+ export {
515
+ romanToChord,
516
+ chordToRoman,
517
+ isMinorKey,
518
+ functionOf,
519
+ isDiatonic,
520
+ parallelKey,
521
+ analyzeChord,
522
+ secondaryDominant,
523
+ isBorrowedChord,
524
+ borrowedSource,
525
+ detectCadence,
526
+ detectChord,
527
+ detectChordBest,
528
+ detectKey
529
+ };
530
+ //# sourceMappingURL=chunk-LN7ZZMD2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/analyze/functional/internal.ts","../src/analyze/functional/roman.ts","../src/analyze/functional/borrowed.ts","../src/analyze/functional/function.ts","../src/analyze/functional/cadence.ts","../src/analyze/detect/index.ts"],"sourcesContent":["/**\n * Shared helpers for the functional-harmony modules.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { scaleTonesInDegreeOrder } from '../../theory/scale/index.js';\n\nexport function mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\n/** Diatonic pitch class of a 1-based scale degree in a key. */\nexport function degreeRootPc(degreeNumber: number, key: KeyScale): number {\n const tones = scaleTonesInDegreeOrder(key);\n if (tones.length === 0) {\n return mod12(key.rootPc);\n }\n return tones[(degreeNumber - 1) % tones.length] ?? mod12(key.rootPc);\n}\n\n/** Whether a chord is the Neapolitan: a major triad on the flat second degree. */\nexport function isNeapolitan(chord: Chord, key: KeyScale): boolean {\n return mod12(chord.rootPc - key.rootPc) === 1 && chord.quality === 'maj';\n}\n","/**\n * Roman-numeral parsing and formatting.\n *\n * The {@link ROMAN_STYLE} table is the single source of truth for both\n * directions, so {@link chordToRoman} and {@link romanToChord} stay mutual\n * inverses by construction.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { makeChord } from '../../theory/chord/index.js';\nimport { majorKey, scaleTonesInDegreeOrder } from '../../theory/scale/index.js';\nimport { degreeRootPc, mod12 } from './internal.js';\n\n/** Roman numeral glyphs indexed by degree number - 1. */\nconst ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] as const;\n\n/** Roman glyph -> degree number (1..7). */\nconst ROMAN_TO_DEGREE: Record<string, number> = {\n I: 1,\n II: 2,\n III: 3,\n IV: 4,\n V: 5,\n VI: 6,\n VII: 7,\n};\n\n/** Major-key Roman spelling for each semitone offset above the tonic. */\nconst OFFSET_SPELLING: readonly [number, string][] = [\n [1, ''],\n [2, 'b'],\n [2, ''],\n [3, 'b'],\n [3, ''],\n [4, ''],\n [4, '#'],\n [5, ''],\n [6, 'b'],\n [6, ''],\n [7, 'b'],\n [7, ''],\n];\n\n/** Choose a seventh-chord quality from a triad label and the numeral case. */\nfunction seventhQuality(base: 'maj' | 'min' | 'dim' | 'aug', halfDim: boolean): ChordQuality {\n if (halfDim) {\n return 'm7b5';\n }\n if (base === 'dim') {\n return 'dim7';\n }\n if (base === 'aug') {\n return 'aug7';\n }\n return base === 'maj' ? 'dom7' : 'min7';\n}\n\n/** Figure strings that denote a recognized figured-bass inversion. */\nconst INVERSION_FIGURES = new Set(['6', '64', '65', '43', '42', '2', '7']);\n\n/**\n * Canonical Roman-numeral rendering (numeral case and quality suffix) for every\n * supported chord quality. The table is the single source of truth for both\n * directions: {@link chordToRoman} renders from it and {@link romanToChord}\n * recognizes its exact case-sensitive suffixes, so the two stay mutual\n * inverses by construction. Suffixes are chosen to never collide with\n * figured-bass inversion digits (`6`, `64`, `65`, `43`, `42`, `2`) — the added\n * sixth is `add6`, the six-nine chord is `69` — and each (case, suffix) pair is\n * unique.\n */\nconst ROMAN_STYLE: Record<ChordQuality, { lower: boolean; suffix: string }> = {\n maj: { lower: false, suffix: '' },\n min: { lower: true, suffix: '' },\n dim: { lower: true, suffix: 'o' },\n aug: { lower: false, suffix: '+' },\n maj7: { lower: false, suffix: 'maj7' },\n min7: { lower: true, suffix: '7' },\n dom7: { lower: false, suffix: '7' },\n dim7: { lower: true, suffix: 'o7' },\n m7b5: { lower: true, suffix: 'ø7' },\n minMaj7: { lower: true, suffix: 'maj7' },\n aug7: { lower: false, suffix: '+7' },\n augMaj7: { lower: false, suffix: '+maj7' },\n majb5: { lower: false, suffix: 'b5' },\n '6': { lower: false, suffix: 'add6' },\n min6: { lower: true, suffix: 'add6' },\n '6/9': { lower: false, suffix: '69' },\n sus2: { lower: false, suffix: 'sus2' },\n sus4: { lower: false, suffix: 'sus4' },\n add9: { lower: false, suffix: 'add9' },\n add11: { lower: false, suffix: 'add11' },\n maj9: { lower: false, suffix: 'maj9' },\n min9: { lower: true, suffix: '9' },\n dom9: { lower: false, suffix: '9' },\n '7b9': { lower: false, suffix: '7b9' },\n '7#9': { lower: false, suffix: '7#9' },\n '7#11': { lower: false, suffix: '7#11' },\n '7b13': { lower: false, suffix: '7b13' },\n '11': { lower: false, suffix: '11' },\n '13': { lower: false, suffix: '13' },\n '5': { lower: false, suffix: '5' },\n};\n\n/** Reverse lookup of {@link ROMAN_STYLE}: `(case, exact suffix) -> quality`. */\nconst SUFFIX_QUALITY: ReadonlyMap<string, ChordQuality> = new Map(\n (Object.entries(ROMAN_STYLE) as [ChordQuality, { lower: boolean; suffix: string }][]).map(\n ([quality, style]) => [`${style.lower ? 'l' : 'u'}:${style.suffix}`, quality],\n ),\n);\n\n/** Read figured-bass digits into a chord inversion and whether a seventh is implied. */\nfunction parseInversion(figures: string): { inversion: number; seventh: boolean } {\n if (figures === '65') {\n return { inversion: 1, seventh: true };\n }\n if (figures === '43') {\n return { inversion: 2, seventh: true };\n }\n if (figures === '42' || figures === '2') {\n return { inversion: 3, seventh: true };\n }\n if (figures === '7') {\n return { inversion: 0, seventh: true };\n }\n if (figures === '64') {\n return { inversion: 2, seventh: false };\n }\n if (figures === '6') {\n return { inversion: 1, seventh: false };\n }\n return { inversion: 0, seventh: false };\n}\n\n/** Parse a Roman numeral (no secondary '/') into a root, quality, and inversion. */\nfunction parseSimpleRoman(\n text: string,\n key: KeyScale,\n): { rootPc: number; quality: ChordQuality; inversion: number } {\n const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());\n if (!match) {\n throw new Error(`Invalid Roman numeral: ${text}`);\n }\n const accidental = match[1] === 'b' ? -1 : match[1] === '#' ? 1 : 0;\n const glyph = (match[2] ?? '').toUpperCase();\n const degreeNumber = ROMAN_TO_DEGREE[glyph];\n if (degreeNumber === undefined) {\n throw new Error(`Invalid Roman numeral: ${text}`);\n }\n const isUpper = (match[2] ?? '')[0] === (match[2] ?? '')[0]?.toUpperCase();\n const suffix = match[3] ?? '';\n const rootPc = mod12(degreeRootPc(degreeNumber, key) + accidental);\n\n // Canonical quality suffixes (exact match, case-sensitive on the numeral)\n // come first so every chordToRoman rendering re-parses to the same quality:\n // `V9` -> dom9 but `ii9` -> min9 and `Imaj9` -> maj9, and added-tone suffixes\n // whose digits would otherwise be misread as figured bass (`Iadd6`, `I69`,\n // `Isus2`, `Iadd11`) resolve to their qualities in root position.\n const canonical = SUFFIX_QUALITY.get(`${isUpper ? 'u' : 'l'}:${suffix}`);\n if (canonical !== undefined) {\n return { rootPc, quality: canonical, inversion: 0 };\n }\n\n const isDim = /o|°|dim/.test(suffix);\n const isAug = /\\+|aug/.test(suffix);\n const isHalfDim = /ø/.test(suffix);\n const explicitMaj7 = /maj7|M7/.test(suffix);\n const figures = suffix.replace(/maj7|M7/g, '').replace(/[^0-9]/g, '');\n\n // Reject figure strings that are neither a known figured-bass inversion nor a\n // canonical quality suffix rather than silently degrading to a root triad.\n if (figures !== '' && !INVERSION_FIGURES.has(figures)) {\n throw new Error(`Unsupported figured-bass or extension \"${figures}\" in Roman numeral: ${text}`);\n }\n\n const { inversion, seventh } = parseInversion(figures);\n // The ø glyph is the half-diminished seventh by definition (there is no\n // half-diminished triad), so it always implies a seventh.\n const hasSeventh = explicitMaj7 || seventh || isHalfDim;\n\n let base: 'maj' | 'min' | 'dim' | 'aug';\n if (isDim || isHalfDim) {\n base = 'dim';\n } else if (isAug) {\n base = 'aug';\n } else {\n base = isUpper ? 'maj' : 'min';\n }\n\n let quality: ChordQuality;\n if (explicitMaj7) {\n quality = base === 'min' ? 'minMaj7' : base === 'aug' ? 'augMaj7' : 'maj7';\n } else if (hasSeventh) {\n quality = seventhQuality(base, isHalfDim);\n } else if (base === 'dim') {\n quality = 'dim';\n } else if (base === 'aug') {\n quality = 'aug';\n } else {\n quality = base;\n }\n return { rootPc, quality, inversion };\n}\n\n/** Build a chord from a parsed Roman numeral, attaching a bass for inversions. */\nfunction chordFromParsed(parsed: {\n rootPc: number;\n quality: ChordQuality;\n inversion: number;\n}): Chord {\n const chord = makeChord(parsed.rootPc, parsed.quality);\n if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {\n chord.bassPc = mod12(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));\n }\n return chord;\n}\n\n/**\n * Build the chord denoted by a Roman numeral in a key.\n *\n * Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`\n * suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions\n * (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash\n * (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree\n * whose root becomes a local major tonic for the applied chord. Inverted chords\n * carry a `bassPc`.\n *\n * The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==\n * `m7b5`), since no half-diminished triad exists. Extension and added-tone\n * suffixes are the canonical case-sensitive forms emitted by\n * {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->\n * `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->\n * `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->\n * `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.\n * A figure string that is neither a recognized inversion nor a supported\n * quality suffix throws rather than silently degrading to a triad.\n *\n * @param text The Roman numeral.\n * @param key The prevailing key.\n * @returns The chord.\n * @example\n * ```ts\n * import { romanToChord, majorKey } from '@libraz/libcantus';\n * romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }\n * ```\n * @category Functional Harmony\n */\nexport function romanToChord(text: string, key: KeyScale): Chord {\n const trimmed = text.trim();\n const slash = trimmed.indexOf('/');\n if (slash >= 0) {\n const applied = trimmed.slice(0, slash);\n const target = trimmed.slice(slash + 1);\n const targetRoot = parseSimpleRoman(target, key).rootPc;\n const localKey = majorKey(targetRoot);\n return chordFromParsed(parseSimpleRoman(applied, localKey));\n }\n return chordFromParsed(parseSimpleRoman(trimmed, key));\n}\n\n/** Case and suffix for rendering a chord quality as a Roman numeral. */\nfunction romanStyle(quality: ChordQuality): { lower: boolean; suffix: string } {\n return ROMAN_STYLE[quality];\n}\n\n/** Choose the degree number and accidental to spell a root as a Roman numeral. */\nfunction romanSpelling(\n rootPc: number,\n key: KeyScale,\n): { degreeNumber: number; accidental: string } {\n const tones = scaleTonesInDegreeOrder(key);\n const diatonic = tones.indexOf(mod12(rootPc));\n if (diatonic >= 0) {\n return { degreeNumber: diatonic + 1, accidental: '' };\n }\n // Chromatic root: prefer a flat of the diatonic degree a semitone above it,\n // then a sharp of the degree a semitone below. Degree 1 (the tonic) and\n // degree 5 (the dominant) are skipped in the flat pass, so a raised leading\n // tone spells as `#vii` rather than a flat tonic `bI`, and the tritone above\n // the tonic spells as `#iv` rather than a flat fifth `bV` (matching the\n // conventional `OFFSET_SPELLING` for offset 6).\n for (let i = 0; i < tones.length; i += 1) {\n const degreeNumber = i + 1;\n if (degreeNumber === 1 || degreeNumber === 5) {\n continue;\n }\n if (mod12((tones[i] ?? 0) - 1) === mod12(rootPc)) {\n return { degreeNumber, accidental: 'b' };\n }\n }\n for (let i = 0; i < tones.length; i += 1) {\n if (mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {\n return { degreeNumber: i + 1, accidental: '#' };\n }\n }\n const [degreeNumber, accidental] = OFFSET_SPELLING[mod12(rootPc - key.rootPc)] ?? [1, ''];\n return { degreeNumber, accidental };\n}\n\n/** Quality marker (without the seventh digit) used when a figure carries the 7. */\nfunction baseMarker(quality: ChordQuality): string {\n if (quality === 'dim' || quality === 'dim7') {\n return 'o';\n }\n if (quality === 'm7b5') {\n return 'ø';\n }\n if (quality === 'aug' || quality === 'aug7') {\n return '+';\n }\n if (quality === 'augMaj7') {\n return '+maj7';\n }\n if (quality === 'maj7' || quality === 'minMaj7') {\n return 'maj7';\n }\n return '';\n}\n\nconst TRIAD_FIGURES: Record<number, string> = { 1: '6', 2: '64' };\nconst SEVENTH_FIGURES: Record<number, string> = { 1: '65', 2: '43', 3: '42' };\n\n/** Qualities whose inversions render as lossless triad figures (6, 64). */\nconst TRIAD_FIGURE_QUALITIES: ReadonlySet<ChordQuality> = new Set(['maj', 'min', 'dim', 'aug']);\n\n/**\n * Qualities carrying a chordal seventh, whose inversions render as seventh\n * figures (65, 43, 42). Membership is decided by the quality itself — a 7th\n * above the root (10 or 11 semitones, or the diminished 7th of `dim7`) — never\n * by interval count, which would turn added-tone chords into false sevenths.\n */\nconst SEVENTH_FIGURE_QUALITIES: ReadonlySet<ChordQuality> = new Set([\n 'maj7',\n 'min7',\n 'dom7',\n 'dim7',\n 'm7b5',\n 'minMaj7',\n 'aug7',\n 'augMaj7',\n]);\n\n/**\n * Render a chord as a Roman numeral relative to a key.\n *\n * Diatonic roots take their scale-degree numeral directly, so numerals are\n * correct in both major and minor keys (and any custom scale). The quality\n * selects the case and suffix; chromatic roots receive a flat/sharp spelling by\n * convention. When the chord carries a `bassPc` on a chord tone, a figured-bass\n * inversion (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true\n * seventh chords; added-tone and extended qualities have no lossless figure and\n * render in root position instead (the bass is dropped, pitch classes are\n * preserved).\n *\n * @param chord The chord to name.\n * @param key The prevailing key.\n * @returns The Roman numeral string.\n * @example\n * ```ts\n * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';\n * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)\n * ```\n * @category Functional Harmony\n */\nexport function chordToRoman(chord: Chord, key: KeyScale): string {\n const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);\n const { lower, suffix } = romanStyle(chord.quality);\n const numeral: string = ROMAN[degreeNumber - 1] ?? 'I';\n const cased = lower ? numeral.toLowerCase() : numeral;\n\n let inversion = 0;\n if (chord.bassPc !== undefined) {\n const idx = chord.intervals.findIndex((iv) => mod12(chord.rootPc + iv) === chord.bassPc);\n if (idx > 0) {\n inversion = idx;\n }\n }\n if (inversion > 0) {\n const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality)\n ? SEVENTH_FIGURES[inversion]\n : TRIAD_FIGURE_QUALITIES.has(chord.quality)\n ? TRIAD_FIGURES[inversion]\n : undefined;\n if (figure !== undefined) {\n return `${accidental}${cased}${baseMarker(chord.quality)}${figure}`;\n }\n // No lossless figured-bass symbol exists — the quality is an added-tone or\n // extended chord, or the bass falls on a tension beyond the seventh — so\n // fall back to root-position rendering with the quality suffix rather than\n // emitting a figure that would re-parse as a different chord.\n }\n return `${accidental}${cased}${suffix}`;\n}\n","/**\n * Borrowed chords (modal interchange) and the Neapolitan.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { HARMONIC_MINOR_MASK } from '../../theory/scale/index.js';\nimport { isDiatonic, isMinorKey, parallelKey } from './function.js';\nimport { isNeapolitan, mod12 } from './internal.js';\n\n/**\n * The origin of a recognized non-diatonic chord, or null when none applies.\n *\n * @category Functional Harmony\n */\nexport type BorrowedSource = 'parallel-minor' | 'parallel-major' | 'neapolitan' | null;\n\n/**\n * In a minor key, whether a non-diatonic chord is explained by the harmonic\n * minor scale on the same tonic (major V, V7, the raised-leading-tone viio).\n * Such chords are in-key chromatic alterations, not modal interchange.\n */\nfunction isHarmonicMinorAlteration(chord: Chord, key: KeyScale): boolean {\n return (\n isMinorKey(key) &&\n isDiatonic(chord, { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK })\n );\n}\n\n/**\n * Whether a chord is borrowed from the parallel mode (modal interchange).\n *\n * True when the chord is not diatonic to `key` but is diatonic to its\n * {@link parallelKey} — e.g. iv, bVI, or bVII in a major key, or the Picardy\n * tonic and major IV in a minor key. Two non-diatonic families are excluded:\n * chords diatonic to neither mode (they are chromatic, not borrowed), and, in\n * a minor key, chords explained by the harmonic minor scale (the major\n * dominant and raised-leading-tone chords), which are in-key alterations\n * rather than interchange even though they happen to fit the parallel major.\n *\n * @param chord The chord to test.\n * @param key The prevailing key.\n * @returns True if the chord is borrowed from the parallel mode.\n * @category Functional Harmony\n */\nexport function isBorrowedChord(chord: Chord, key: KeyScale): boolean {\n if (isDiatonic(chord, key)) {\n return false;\n }\n if (isHarmonicMinorAlteration(chord, key)) {\n return false;\n }\n return isDiatonic(chord, parallelKey(key));\n}\n\n/**\n * Identify where a non-diatonic chord comes from.\n *\n * The Neapolitan (major triad on b2) is recognized first, since it sits\n * outside both parallel modes; parallel-mode borrowing follows the\n * {@link isBorrowedChord} rules. Diatonic chords, harmonic-minor alterations,\n * and unrecognized chromatic chords all yield null.\n *\n * @param chord The chord to classify.\n * @param key The prevailing key.\n * @returns The borrowing source, or null.\n * @category Functional Harmony\n */\nexport function borrowedSource(chord: Chord, key: KeyScale): BorrowedSource {\n if (isDiatonic(chord, key)) {\n return null;\n }\n if (isNeapolitan(chord, key)) {\n return 'neapolitan';\n }\n if (isBorrowedChord(chord, key)) {\n return isMinorKey(key) ? 'parallel-major' : 'parallel-minor';\n }\n return null;\n}\n","/**\n * Harmonic function (tonic / subdominant / dominant), diatonicity, parallel-key\n * mirroring, and full chord analysis.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport { isScaleTone, MAJOR_MASK, NATURAL_MINOR_MASK } from '../../theory/scale/index.js';\nimport { type BorrowedSource, borrowedSource } from './borrowed.js';\nimport { degreeRootPc, isNeapolitan, mod12 } from './internal.js';\nimport { chordToRoman } from './roman.js';\n\n/**\n * The three broad harmonic functions of tonal music.\n *\n * @category Functional Harmony\n */\nexport type HarmonicFunction = 'tonic' | 'subdominant' | 'dominant';\n\n/** Harmonic function of each semitone offset above the tonic (major context). */\nconst FUNCTION_BY_OFFSET: readonly HarmonicFunction[] = [\n 'tonic', // 0 I\n 'subdominant', // 1 bII (Neapolitan)\n 'subdominant', // 2 ii\n 'tonic', // 3 bIII\n 'tonic', // 4 iii\n 'subdominant', // 5 IV\n 'dominant', // 6 #IV / bV\n 'dominant', // 7 V\n 'subdominant', // 8 bVI\n 'tonic', // 9 vi\n 'subdominant', // 10 bVII\n 'dominant', // 11 vii\n];\n\n/**\n * Whether a key's scale has a minor third and no major third (a minor key).\n *\n * @category Functional Harmony\n */\nexport function isMinorKey(key: KeyScale): boolean {\n const hasMinorThird = (key.modeMask12 >> 3) & 1;\n const hasMajorThird = (key.modeMask12 >> 4) & 1;\n return Boolean(hasMinorThird) && !hasMajorThird;\n}\n\n/**\n * The harmonic function of a chord in a key, from its root's offset above the\n * tonic. The mapping follows common-practice major-key function and is a useful\n * approximation in minor and for borrowed chords.\n *\n * @param chord The chord.\n * @param key The prevailing key.\n * @returns The harmonic function.\n * @category Functional Harmony\n */\nexport function functionOf(chord: Chord, key: KeyScale): HarmonicFunction {\n return FUNCTION_BY_OFFSET[mod12(chord.rootPc - key.rootPc)] ?? 'tonic';\n}\n\n/**\n * The result of {@link analyzeChord}: function, borrowing, and Roman numeral.\n *\n * @category Functional Harmony\n */\nexport type ChordAnalysis = {\n function: HarmonicFunction;\n borrowed: boolean;\n source: BorrowedSource;\n roman: string;\n};\n\n/**\n * Whether every pitch class of a chord belongs to the key's scale.\n *\n * The test is strict against the key's own mode mask: in a natural-minor key\n * the harmonic-minor dominant (major V) is *not* diatonic, since the raised\n * leading tone lies outside the mask. Borrowing predicates treat that case as\n * an in-key alteration separately (see {@link isBorrowedChord}).\n *\n * @param chord The chord to test.\n * @param key The prevailing key.\n * @returns True if all chord pitch classes are scale tones.\n * @category Functional Harmony\n */\nexport function isDiatonic(chord: Chord, key: KeyScale): boolean {\n return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));\n}\n\n/**\n * The parallel key: same tonic, opposite mode.\n *\n * A key with a minor third (natural/harmonic/melodic minor, dorian, phrygian)\n * maps to the parallel major; any other key maps to the parallel natural minor.\n *\n * @param key The key to mirror.\n * @returns The parallel major or natural-minor key on the same tonic.\n * @category Functional Harmony\n */\nexport function parallelKey(key: KeyScale): KeyScale {\n return {\n rootPc: mod12(key.rootPc),\n modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK,\n };\n}\n\n/** Diminished-family qualities: diminished triad, dim7, half-diminished. */\nfunction isDiminishedQuality(quality: ChordQuality): boolean {\n return quality === 'dim' || quality === 'dim7' || quality === 'm7b5';\n}\n\n/** Whether the chord's interval template carries a major third above the root. */\nfunction hasMajorThird(chord: Chord): boolean {\n return chord.intervals.some((interval) => mod12(interval) === 4);\n}\n\n/**\n * Quality-aware harmonic function, refining the offset table where chord\n * quality disambiguates: diminished chords on the leading tone or the raised\n * subdominant resolve by semitone and act as dominants; the Neapolitan and the\n * major-third chords on bVI/bVII borrowed from the parallel minor act as\n * subdominant (predominant) harmony. All other chords keep {@link functionOf}.\n */\nfunction qualityAwareFunction(chord: Chord, key: KeyScale): HarmonicFunction {\n const offset = mod12(chord.rootPc - key.rootPc);\n if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {\n return 'dominant';\n }\n if (isNeapolitan(chord, key)) {\n return 'subdominant';\n }\n if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {\n return 'subdominant';\n }\n return functionOf(chord, key);\n}\n\n/**\n * Analyze a chord in a key: harmonic function, borrowing, and Roman numeral.\n *\n * The function is quality-aware (see the predicates behind it), the source\n * follows {@link borrowedSource}, and the numeral comes from\n * {@link chordToRoman}. `borrowed` is true whenever a source is identified —\n * including the Neapolitan, which the stricter parallel-mode predicate\n * {@link isBorrowedChord} does not count.\n *\n * @param chord The chord to analyze.\n * @param key The prevailing key.\n * @returns The chord analysis.\n * @example\n * ```ts\n * import { analyzeChord, makeChord, majorKey } from '@libraz/libcantus';\n * analyzeChord(makeChord(7, 'dom7'), majorKey(0));\n * // { function: 'dominant', borrowed: false, source: null, roman: 'V7' }\n * ```\n * @category Functional Harmony\n */\nexport function analyzeChord(chord: Chord, key: KeyScale): ChordAnalysis {\n const source = borrowedSource(chord, key);\n return {\n function: qualityAwareFunction(chord, key),\n borrowed: source !== null,\n source,\n roman: chordToRoman(chord, key),\n };\n}\n\n/**\n * The secondary dominant (V7) that tonicizes a scale degree.\n *\n * @param targetDegree 0-based scale degree to tonicize.\n * @param key The prevailing key.\n * @returns A dominant-seventh chord a fifth above the target's root.\n * @category Functional Harmony\n */\nexport function secondaryDominant(targetDegree: number, key: KeyScale): Chord {\n const targetRoot = degreeRootPc(targetDegree + 1, key);\n return makeChord(mod12(targetRoot + 7), 'dom7');\n}\n","/**\n * Cadence detection between two successive chords.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { isMinorKey } from './function.js';\nimport { mod12 } from './internal.js';\n\n/**\n * A recognized cadence type, or null when a chord pair forms none.\n *\n * @category Functional Harmony\n */\nexport type Cadence = 'authentic' | 'plagal' | 'half' | 'deceptive' | null;\n\n/**\n * Classify the cadence formed by moving from one chord to the next.\n *\n * - authentic: V (dominant a fifth above the tonic) to I\n * - plagal: IV (a fourth above the tonic) to I\n * - deceptive: V to the submediant (diatonic vi or borrowed bVI in major, VI in\n * minor)\n * - half: any chord other than the dominant itself to V\n *\n * A static V-to-V repeat with no root motion is not a cadence and yields null.\n *\n * @param from The penultimate chord.\n * @param to The final chord.\n * @param key The prevailing key.\n * @returns The cadence type, or null.\n * @example\n * ```ts\n * import { detectCadence, makeChord, majorKey } from '@libraz/libcantus';\n * detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0)); // => 'authentic'\n * ```\n * @category Functional Harmony\n */\nexport function detectCadence(from: Chord, to: Chord, key: KeyScale): Cadence {\n const tonic = mod12(key.rootPc);\n const fromOffset = mod12(from.rootPc - tonic);\n const toOffset = mod12(to.rootPc - tonic);\n // Deceptive targets: the diatonic submediant (vi at offset 9 in major, VI at\n // offset 8 in minor) plus, in a major key, the borrowed flat-submediant bVI\n // at offset 8.\n const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];\n if (fromOffset === 7 && toOffset === 0) {\n return 'authentic';\n }\n if (fromOffset === 5 && toOffset === 0) {\n return 'plagal';\n }\n if (fromOffset === 7 && deceptiveTargets.includes(toOffset)) {\n return 'deceptive';\n }\n // A move to the dominant is a half cadence, but a no-root-motion V-to-V repeat\n // is not a cadence at all.\n if (toOffset === 7 && fromOffset !== 7) {\n return 'half';\n }\n return null;\n}\n","/**\n * Recognition: infer a chord name from a set of pitches, or a key from a set of\n * pitch classes. This is the inverse direction of the chord/scale builders,\n * which only go name -> notes.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordPitchClasses, chordQualities, makeChord } from '../../theory/chord/index.js';\nimport {\n HARMONIC_MINOR_MASK,\n MAJOR_MASK,\n MELODIC_MINOR_MASK,\n majorKey,\n minorKey,\n NATURAL_MINOR_MASK,\n} from '../../theory/scale/index.js';\n\n/**\n * A candidate chord interpretation of a pitch set.\n *\n * @category Recognition\n */\nexport type ChordMatch = {\n rootPc: number;\n quality: ChordQuality;\n /** Chord tones absent from the input (an incomplete voicing). */\n missingPcs: number[];\n /** Input pitch classes not belonging to the chord. */\n extraPcs: number[];\n /** True when the input pitch-class set equals the chord exactly. */\n exact: boolean;\n /** Inversion implied by the lowest note: 0 root position, 1 first, ... */\n inversion: number;\n /** Bass pitch class when inverted (the lowest note is not the root). */\n bassPc?: number;\n};\n\n/**\n * A candidate key interpretation of a pitch-class set.\n *\n * @category Recognition\n */\nexport type KeyMatch = {\n key: KeyScale;\n mode: 'major' | 'minor';\n /** Fraction of input pitch classes that are in the scale, in [0, 1]. */\n fit: number;\n};\n\n/** Reduce any integer to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\n\n/** Unique pitch classes of the input, sorted ascending. */\nfunction uniquePitchClasses(pitches: number[]): number[] {\n return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);\n}\n\n/**\n * Identify chords matching a set of pitches.\n *\n * Every input pitch class is tried as a root against every known chord quality.\n * A match is reported when all of the chord's tones are present in the input, or\n * when the only absent tone is the perfect fifth (a common omission in shell\n * voicings); matches are ranked best-first by fewest extra notes, then fewest\n * missing notes, then most specific (largest) chord. An exact match (no extras,\n * no missing) is flagged and ranked first.\n *\n * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).\n * @returns Ranked chord interpretations (may be empty).\n * @example\n * ```ts\n * import { detectChord } from '@libraz/libcantus';\n * const matches = detectChord([60, 64, 67]); // C E G\n * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }\n * ```\n * @category Recognition\n */\nexport function detectChord(pitches: number[]): ChordMatch[] {\n const input = uniquePitchClasses(pitches);\n if (input.length === 0) {\n return [];\n }\n const bassPc = pitchClass(Math.min(...pitches));\n const inputSet = new Set(input);\n const matches: ChordMatch[] = [];\n const qualities = chordQualities();\n for (const rootPc of input) {\n for (const quality of qualities) {\n const chord = makeChord(rootPc, quality);\n const tones = chordPitchClasses(chord);\n const toneSet = new Set(tones);\n const missingPcs = tones.filter((pc) => !inputSet.has(pc));\n // Accept an exact-tone match, or one whose only absent tone is the perfect\n // fifth; any other missing tone (third, sus tone, seventh, ...) rejects.\n const fifthPc = pitchClass(rootPc + 7);\n if (missingPcs.length > 1 || (missingPcs.length === 1 && missingPcs[0] !== fifthPc)) {\n continue;\n }\n const extraPcs = input.filter((pc) => !toneSet.has(pc));\n const bassIndex = chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);\n const inversion = bassIndex > 0 ? bassIndex : 0;\n const match: ChordMatch = {\n rootPc,\n quality,\n missingPcs,\n extraPcs,\n exact: extraPcs.length === 0 && missingPcs.length === 0,\n inversion,\n };\n if (inversion > 0) {\n match.bassPc = bassPc;\n }\n matches.push(match);\n }\n }\n matches.sort((a, b) => {\n if (a.extraPcs.length !== b.extraPcs.length) {\n return a.extraPcs.length - b.extraPcs.length;\n }\n if (a.missingPcs.length !== b.missingPcs.length) {\n return a.missingPcs.length - b.missingPcs.length;\n }\n // Prefer root position (the bass is the chord root) on a tie.\n if ((a.inversion === 0) !== (b.inversion === 0)) {\n return a.inversion === 0 ? -1 : 1;\n }\n const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;\n const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;\n return bSize - aSize;\n });\n return matches;\n}\n\n/**\n * The single best chord interpretation of a pitch set, or null if none.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns The top-ranked chord, or null when nothing matches.\n * @example\n * ```ts\n * import { detectChordBest } from '@libraz/libcantus';\n * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }\n * ```\n * @category Recognition\n */\nexport function detectChordBest(pitches: number[]): Chord | null {\n const best = detectChord(pitches)[0];\n if (!best) {\n return null;\n }\n return makeChord(best.rootPc, best.quality, best.bassPc);\n}\n\n/**\n * Minor-scale variants scored for each minor-key candidate. Scoring against all\n * three lets the raised sixth and seventh (e.g. the leading tone G# in A minor)\n * count toward their own tonic instead of only penalizing it.\n */\nconst MINOR_MASK_VARIANTS = [NATURAL_MINOR_MASK, HARMONIC_MINOR_MASK, MELODIC_MINOR_MASK];\n\n/**\n * Rank major and minor keys by how well they contain a set of pitch classes.\n *\n * The tonic is weighted so that, among equally-fitting keys, the one whose root\n * appears in the input is preferred. Minor candidates are scored against the\n * natural, harmonic, and melodic minor variants and keep the best of the three,\n * so a minor cadence containing the leading tone still resolves to its own\n * tonic; the returned key is always the natural-minor scale. Returns all 24\n * keys ranked best-first, or an empty array for an empty input (mirroring\n * {@link detectChord}).\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns Ranked key interpretations (empty for an empty input).\n * @example\n * ```ts\n * import { detectKey } from '@libraz/libcantus';\n * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale\n * keys[0].mode; // 'major', with keys[0].key.rootPc === 0\n * ```\n * @category Recognition\n */\nexport function detectKey(pitches: number[]): KeyMatch[] {\n const input = uniquePitchClasses(pitches);\n if (input.length === 0) {\n return [];\n }\n const counts = new Map<number, number>();\n for (const pc of pitches.map(pitchClass)) {\n counts.set(pc, (counts.get(pc) ?? 0) + 1);\n }\n const total = pitches.length;\n const results: (KeyMatch & { score: number })[] = [];\n for (let tonic = 0; tonic < 12; tonic += 1) {\n for (const mode of ['major', 'minor'] as const) {\n const key = mode === 'major' ? majorKey(tonic) : minorKey(tonic);\n const masks = mode === 'major' ? [MAJOR_MASK] : MINOR_MASK_VARIANTS;\n // Score each scale variant and keep the best; ties keep the earlier\n // (more diatonic) variant.\n let inScale = 0;\n let weighted = Number.NEGATIVE_INFINITY;\n for (const mask of masks) {\n let variantInScale = 0;\n let variantWeighted = 0;\n for (const pc of input) {\n const offset = (pc - tonic + 12) % 12;\n if ((mask >> offset) & 1) {\n variantInScale += 1;\n }\n }\n for (const [pc, count] of counts) {\n const offset = (pc - tonic + 12) % 12;\n if ((mask >> offset) & 1) {\n variantWeighted += count;\n }\n }\n if (variantWeighted > weighted) {\n weighted = variantWeighted;\n inScale = variantInScale;\n }\n }\n weighted += (counts.get(tonic) ?? 0) * 0.5;\n results.push({\n key,\n mode,\n fit: inScale / input.length,\n score: weighted / total,\n });\n }\n }\n results.sort((a, b) => b.score - a.score);\n return results.map(({ key, mode, fit }) => ({ key, mode, fit }));\n}\n"],"mappings":"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","names":["degreeNumber","hasMajorThird"]}