@libraz/libcantus 1.0.1 → 1.2.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 (142) hide show
  1. package/README.md +27 -65
  2. package/README_ja.md +28 -66
  3. package/dist/analyze/index.cjs +11 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +79 -144
  6. package/dist/analyze/index.d.ts +79 -144
  7. package/dist/analyze/index.js +20 -16
  8. package/dist/chunk-35NBV2FT.cjs +145 -0
  9. package/dist/chunk-35NBV2FT.cjs.map +1 -0
  10. package/dist/{chunk-UADYAYAT.cjs → chunk-3R3QVKY5.cjs} +1353 -580
  11. package/dist/chunk-3R3QVKY5.cjs.map +1 -0
  12. package/dist/{chunk-WWURE4XT.cjs → chunk-76ZBTRG2.cjs} +624 -729
  13. package/dist/chunk-76ZBTRG2.cjs.map +1 -0
  14. package/dist/{chunk-FVSI36UV.js → chunk-7KG3RTYS.js} +197 -110
  15. package/dist/chunk-7KG3RTYS.js.map +1 -0
  16. package/dist/{chunk-VTI6FOLB.js → chunk-AGOVW7U6.js} +709 -133
  17. package/dist/chunk-AGOVW7U6.js.map +1 -0
  18. package/dist/{chunk-E3F4AHAH.cjs → chunk-CGBADIO5.cjs} +221 -134
  19. package/dist/chunk-CGBADIO5.cjs.map +1 -0
  20. package/dist/{chunk-6XH6QZYQ.cjs → chunk-DSMJRXCZ.cjs} +1455 -849
  21. package/dist/chunk-DSMJRXCZ.cjs.map +1 -0
  22. package/dist/{chunk-PKFNUHDY.js → chunk-FBH75DAY.js} +1218 -612
  23. package/dist/chunk-FBH75DAY.js.map +1 -0
  24. package/dist/chunk-H2KJHBW4.js +3329 -0
  25. package/dist/chunk-H2KJHBW4.js.map +1 -0
  26. package/dist/chunk-HPVSH7SF.cjs +2294 -0
  27. package/dist/chunk-HPVSH7SF.cjs.map +1 -0
  28. package/dist/{chunk-MXO72PLY.cjs → chunk-HWHGOVO6.cjs} +21 -19
  29. package/dist/chunk-HWHGOVO6.cjs.map +1 -0
  30. package/dist/chunk-HYJJ2V2I.js +2294 -0
  31. package/dist/chunk-HYJJ2V2I.js.map +1 -0
  32. package/dist/{chunk-AFM2ULJH.cjs → chunk-J7FHVZII.cjs} +1567 -1067
  33. package/dist/chunk-J7FHVZII.cjs.map +1 -0
  34. package/dist/chunk-K6OZXIR6.js +1 -0
  35. package/dist/{chunk-H3VORETI.js → chunk-M5RYMAFI.js} +487 -592
  36. package/dist/chunk-M5RYMAFI.js.map +1 -0
  37. package/dist/chunk-NB25P3EH.js +363 -0
  38. package/dist/chunk-NB25P3EH.js.map +1 -0
  39. package/dist/{chunk-TTCLJX6D.js → chunk-NFJ4P6ZZ.js} +10 -8
  40. package/dist/chunk-NFJ4P6ZZ.js.map +1 -0
  41. package/dist/chunk-NM7UAMC3.cjs +457 -0
  42. package/dist/chunk-NM7UAMC3.cjs.map +1 -0
  43. package/dist/chunk-PM4XJE2Y.cjs +363 -0
  44. package/dist/chunk-PM4XJE2Y.cjs.map +1 -0
  45. package/dist/chunk-PUKJP2Z5.js +145 -0
  46. package/dist/chunk-PUKJP2Z5.js.map +1 -0
  47. package/dist/{chunk-VOHZB45S.js → chunk-Q6LFSAKV.js} +1321 -821
  48. package/dist/chunk-Q6LFSAKV.js.map +1 -0
  49. package/dist/{chunk-INHVXP6Z.js → chunk-RFIBF2RR.js} +1203 -430
  50. package/dist/chunk-RFIBF2RR.js.map +1 -0
  51. package/dist/{chunk-Q2WGKJNN.cjs → chunk-S5BKZZGM.cjs} +699 -123
  52. package/dist/chunk-S5BKZZGM.cjs.map +1 -0
  53. package/dist/chunk-SGLEQVJH.cjs +3329 -0
  54. package/dist/chunk-SGLEQVJH.cjs.map +1 -0
  55. package/dist/chunk-UGLFDG7L.cjs +1 -0
  56. package/dist/chunk-UGLFDG7L.cjs.map +1 -0
  57. package/dist/{chunk-YH3BIKHH.js → chunk-UMWZ4ZET.js} +146 -49
  58. package/dist/chunk-UMWZ4ZET.js.map +1 -0
  59. package/dist/{index-szBv3496.d.ts → coerce-DP0vYu-x.d.ts} +286 -199
  60. package/dist/{index-C_8o--VE.d.cts → coerce-DRERzdve.d.cts} +286 -199
  61. package/dist/core/index.cjs +9 -5
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +122 -108
  64. package/dist/core/index.d.ts +122 -108
  65. package/dist/core/index.js +32 -28
  66. package/dist/generate/index.cjs +8 -8
  67. package/dist/generate/index.d.cts +168 -84
  68. package/dist/generate/index.d.ts +168 -84
  69. package/dist/generate/index.js +7 -7
  70. package/dist/{index-CGpRv5sl.d.cts → index-9eoQs2C1.d.ts} +34 -19
  71. package/dist/{index-CCNJRG-P.d.cts → index-B9QLziDe.d.cts} +293 -56
  72. package/dist/{index-Dzr13q3d.d.cts → index-BBWNZS0f.d.cts} +135 -58
  73. package/dist/{index-9wcMhG73.d.ts → index-BHIUyjhZ.d.ts} +293 -56
  74. package/dist/{index-BGFW-8P2.d.cts → index-BJgP97N1.d.ts} +196 -114
  75. package/dist/{index-CunYKLiu.d.ts → index-BL5N2Oyz.d.cts} +1 -1
  76. package/dist/{index-Cj49F892.d.ts → index-BMweUlm5.d.cts} +63 -23
  77. package/dist/{index-CgHqxdps.d.ts → index-BNSM-DSk.d.cts} +196 -114
  78. package/dist/{index-D57QRbVn.d.ts → index-BRMbPkHE.d.ts} +480 -254
  79. package/dist/{index-Chbst3Jx.d.ts → index-CJBBiAmU.d.ts} +135 -58
  80. package/dist/{index-BkGmlyI3.d.cts → index-CL4b4gUQ.d.ts} +63 -23
  81. package/dist/{index-B9MNZboQ.d.ts → index-Cy15oTvh.d.cts} +52 -22
  82. package/dist/{index-B9MNZboQ.d.cts → index-Cy15oTvh.d.ts} +52 -22
  83. package/dist/{index-BO6xE9YU.d.cts → index-DGCNKAUr.d.cts} +480 -254
  84. package/dist/{index-B4o55NDr.d.cts → index-Dw2DP7vm.d.ts} +1 -1
  85. package/dist/{index-G1uwGGWQ.d.ts → index-cHvbYgJI.d.cts} +34 -19
  86. package/dist/index.cjs +42 -14
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.d.cts +14 -14
  89. package/dist/index.d.ts +14 -14
  90. package/dist/index.js +82 -54
  91. package/dist/{masks-DYbE485J.d.cts → masks-Cc7-MzzY.d.ts} +49 -2
  92. package/dist/{masks-DYbE485J.d.ts → masks-pADfTOiD.d.cts} +49 -2
  93. package/dist/model/index.cjs +19 -12
  94. package/dist/model/index.cjs.map +1 -1
  95. package/dist/model/index.d.cts +504 -182
  96. package/dist/model/index.d.ts +504 -182
  97. package/dist/model/index.js +18 -11
  98. package/dist/{positional-Cptlj-xs.d.ts → positional-DJgs6ltD.d.ts} +85 -8
  99. package/dist/{positional-DvH_sd8u.d.cts → positional-DMZer-FM.d.cts} +85 -8
  100. package/dist/theory/index.cjs +35 -6
  101. package/dist/theory/index.cjs.map +1 -1
  102. package/dist/theory/index.d.cts +257 -62
  103. package/dist/theory/index.d.ts +257 -62
  104. package/dist/theory/index.js +41 -12
  105. package/package.json +7 -5
  106. package/dist/chunk-2LCPM3EG.js +0 -3224
  107. package/dist/chunk-2LCPM3EG.js.map +0 -1
  108. package/dist/chunk-6XH6QZYQ.cjs.map +0 -1
  109. package/dist/chunk-6XPTBE7Y.cjs +0 -49
  110. package/dist/chunk-6XPTBE7Y.cjs.map +0 -1
  111. package/dist/chunk-AFM2ULJH.cjs.map +0 -1
  112. package/dist/chunk-B6XNKWZV.cjs +0 -3224
  113. package/dist/chunk-B6XNKWZV.cjs.map +0 -1
  114. package/dist/chunk-E3F4AHAH.cjs.map +0 -1
  115. package/dist/chunk-ELCTZUJJ.js +0 -379
  116. package/dist/chunk-ELCTZUJJ.js.map +0 -1
  117. package/dist/chunk-FVSI36UV.js.map +0 -1
  118. package/dist/chunk-H3VORETI.js.map +0 -1
  119. package/dist/chunk-H42UHPYW.js +0 -1577
  120. package/dist/chunk-H42UHPYW.js.map +0 -1
  121. package/dist/chunk-HFPS7JFD.js +0 -1
  122. package/dist/chunk-INHVXP6Z.js.map +0 -1
  123. package/dist/chunk-K4ZJUGF4.cjs +0 -379
  124. package/dist/chunk-K4ZJUGF4.cjs.map +0 -1
  125. package/dist/chunk-KNJEKLKV.js +0 -49
  126. package/dist/chunk-KNJEKLKV.js.map +0 -1
  127. package/dist/chunk-MXO72PLY.cjs.map +0 -1
  128. package/dist/chunk-NLEU4QSE.cjs +0 -360
  129. package/dist/chunk-NLEU4QSE.cjs.map +0 -1
  130. package/dist/chunk-OE6WK2Y2.cjs +0 -1577
  131. package/dist/chunk-OE6WK2Y2.cjs.map +0 -1
  132. package/dist/chunk-PKFNUHDY.js.map +0 -1
  133. package/dist/chunk-Q2WGKJNN.cjs.map +0 -1
  134. package/dist/chunk-RHIJ3S4I.cjs +0 -1
  135. package/dist/chunk-RHIJ3S4I.cjs.map +0 -1
  136. package/dist/chunk-TTCLJX6D.js.map +0 -1
  137. package/dist/chunk-UADYAYAT.cjs.map +0 -1
  138. package/dist/chunk-VOHZB45S.js.map +0 -1
  139. package/dist/chunk-VTI6FOLB.js.map +0 -1
  140. package/dist/chunk-WWURE4XT.cjs.map +0 -1
  141. package/dist/chunk-YH3BIKHH.js.map +0 -1
  142. /package/dist/{chunk-HFPS7JFD.js.map → chunk-K6OZXIR6.js.map} +0 -0
@@ -1,1577 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
- var _chunkUADYAYATcjs = require('./chunk-UADYAYAT.cjs');
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
- var _chunkQ2WGKJNNcjs = require('./chunk-Q2WGKJNN.cjs');
40
-
41
- // src/analyze/detect/modes.ts
42
- var MODAL_CANDIDATES = Object.freeze([
43
- { scaleName: "dorian", mode: "minor", mask: _chunkUADYAYATcjs.DORIAN_MASK, substitutions: [[8, 9]] },
44
- { scaleName: "phrygian", mode: "minor", mask: _chunkUADYAYATcjs.PHRYGIAN_MASK, substitutions: [[2, 1]] },
45
- { scaleName: "lydian", mode: "major", mask: _chunkUADYAYATcjs.LYDIAN_MASK, substitutions: [[5, 6]] },
46
- { scaleName: "mixolydian", mode: "major", mask: _chunkUADYAYATcjs.MIXOLYDIAN_MASK, substitutions: [[11, 10]] },
47
- {
48
- scaleName: "locrian",
49
- mode: "minor",
50
- mask: _chunkUADYAYATcjs.LOCRIAN_MASK,
51
- substitutions: [
52
- [2, 1],
53
- [7, 6]
54
- ]
55
- }
56
- ]);
57
- var MODAL_SCALE_NAMES = Object.freeze(
58
- MODAL_CANDIDATES.map((candidate) => candidate.scaleName)
59
- );
60
- function modalProfileVector(profile, candidate) {
61
- const parent = candidate.mode === "major" ? profile.major : profile.minor;
62
- const vector = [...parent];
63
- for (const [replaced, replacing] of candidate.substitutions) {
64
- vector[replacing] = _nullishCoalesce(parent[replaced], () => ( 0));
65
- vector[replaced] = _nullishCoalesce(parent[replacing], () => ( 0));
66
- }
67
- return vector;
68
- }
69
- function resolveModalCandidates(modes) {
70
- if (modes === void 0 || modes === false) {
71
- return [];
72
- }
73
- if (modes === true) {
74
- return MODAL_CANDIDATES;
75
- }
76
- if (!Array.isArray(modes)) {
77
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("modes must be a boolean or an array of mode names");
78
- }
79
- const requested = new Set(modes);
80
- for (const name of requested) {
81
- if (!MODAL_SCALE_NAMES.some((known) => known === name)) {
82
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
83
- `unknown mode '${String(name)}'; expected one of ${MODAL_SCALE_NAMES.join(", ")}`
84
- );
85
- }
86
- }
87
- return MODAL_CANDIDATES.filter((candidate) => requested.has(candidate.scaleName));
88
- }
89
-
90
- // src/analyze/detect/profiles.ts
91
- var KRUMHANSL_KESSLER_PROFILE = {
92
- 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],
93
- minor: [6.33, 2.68, 3.52, 5.38, 2.6, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17]
94
- };
95
- var TEMPERLEY_KOSTKA_PAYNE_PROFILE = {
96
- major: [0.748, 0.06, 0.488, 0.082, 0.67, 0.46, 0.096, 0.715, 0.104, 0.366, 0.057, 0.4],
97
- minor: [0.712, 0.084, 0.474, 0.618, 0.049, 0.46, 0.105, 0.747, 0.404, 0.067, 0.133, 0.33]
98
- };
99
- var FLAT_SCALE_PROFILE = {
100
- major: [1.5, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1],
101
- minor: [1.5, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0]
102
- };
103
- var PROFILE_LENGTH = 12;
104
- var SCORE_QUANTUM = 1e12;
105
- var NAMED_PROFILES = {
106
- krumhansl: KRUMHANSL_KESSLER_PROFILE,
107
- temperley: TEMPERLEY_KOSTKA_PAYNE_PROFILE,
108
- flat: FLAT_SCALE_PROFILE
109
- };
110
- function assertProfileVector(vector, name) {
111
- if (vector.length !== PROFILE_LENGTH) {
112
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must have ${PROFILE_LENGTH} entries`);
113
- }
114
- for (let index = 0; index < vector.length; index += 1) {
115
- const value = vector[index];
116
- if (typeof value !== "number" || !Number.isFinite(value)) {
117
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name}[${index}] must be a finite number`);
118
- }
119
- }
120
- }
121
- function resolveKeyProfile(profile) {
122
- if (profile === void 0) {
123
- return KRUMHANSL_KESSLER_PROFILE;
124
- }
125
- if (typeof profile === "string") {
126
- const named = NAMED_PROFILES[profile];
127
- if (named === void 0) {
128
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
129
- `unknown key profile '${profile}'; expected one of ${Object.keys(NAMED_PROFILES).join(", ")}`
130
- );
131
- }
132
- return named;
133
- }
134
- if (typeof profile !== "object" || profile === null) {
135
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("profile must be a profile name or a { major, minor } pair");
136
- }
137
- const { major, minor } = profile;
138
- if (!Array.isArray(major) || !Array.isArray(minor)) {
139
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("profile must carry a major and a minor vector");
140
- }
141
- assertProfileVector(major, "profile.major");
142
- assertProfileVector(minor, "profile.minor");
143
- return { major, minor };
144
- }
145
- function normalizedDot(dist, vector, tonic) {
146
- let dot = 0;
147
- let distSquares = 0;
148
- let profileSquares = 0;
149
- for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {
150
- const d = _nullishCoalesce(dist[pc], () => ( 0));
151
- const p = _nullishCoalesce(vector[(pc - tonic + PROFILE_LENGTH) % PROFILE_LENGTH], () => ( 0));
152
- dot += d * p;
153
- distSquares += d * d;
154
- profileSquares += p * p;
155
- }
156
- const norm = Math.sqrt(distSquares * profileSquares);
157
- return norm > 0 ? dot / norm : 0;
158
- }
159
- function profileScore(dist, vector, tonic) {
160
- let distSum = 0;
161
- let profileSum = 0;
162
- for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {
163
- distSum += _nullishCoalesce(dist[pc], () => ( 0));
164
- profileSum += _nullishCoalesce(vector[pc], () => ( 0));
165
- }
166
- const distMean = distSum / PROFILE_LENGTH;
167
- const profileMean = profileSum / PROFILE_LENGTH;
168
- let covariance = 0;
169
- let distVariance = 0;
170
- let profileVariance = 0;
171
- for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {
172
- const d = (_nullishCoalesce(dist[pc], () => ( 0))) - distMean;
173
- const p = (_nullishCoalesce(vector[(pc - tonic + PROFILE_LENGTH) % PROFILE_LENGTH], () => ( 0))) - profileMean;
174
- covariance += d * p;
175
- distVariance += d * d;
176
- profileVariance += p * p;
177
- }
178
- const denominator = Math.sqrt(distVariance * profileVariance);
179
- const raw = denominator > 0 ? covariance / denominator : normalizedDot(dist, vector, tonic);
180
- if (!Number.isFinite(raw)) {
181
- return 0;
182
- }
183
- const clamped = Math.min(1, Math.max(-1, raw));
184
- return Math.round(clamped * SCORE_QUANTUM) / SCORE_QUANTUM;
185
- }
186
-
187
- // src/analyze/detect/index.ts
188
- function uniquePitchClasses(pitches) {
189
- return [...new Set(pitches.map(_chunkQ2WGKJNNcjs.pitchClassOf))].sort((a, b) => a - b);
190
- }
191
- function assertPitches(pitches, name, budget = void 0) {
192
- _chunkQ2WGKJNNcjs.assertGenerationBudget.call(void 0, pitches.length, name, budget);
193
- for (let index = 0; index < pitches.length; index += 1) {
194
- _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, _nullishCoalesce(pitches[index], () => ( Number.NaN)), `pitches[${index}]`);
195
- }
196
- }
197
- function detectChord(pitches, opts = {}) {
198
- assertPitches(pitches, "chord detection pitches");
199
- if (opts.bassPc !== void 0) _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, opts.bassPc, "bassPc");
200
- const input = uniquePitchClasses(pitches);
201
- if (input.length === 0) {
202
- return [];
203
- }
204
- const inputKind = opts.input === void 0 || opts.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : opts.input;
205
- const bassPc = opts.bassPc !== void 0 ? _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, opts.bassPc) : inputKind === "midi" ? _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
206
- const inputSet = new Set(input);
207
- const matches = [];
208
- const qualities = _chunkUADYAYATcjs.chordQualities.call(void 0, );
209
- const toneCounts = new Map(
210
- qualities.map((quality) => [quality, _chunkUADYAYATcjs.chordPitchClasses.call(void 0, _chunkUADYAYATcjs.makeChord.call(void 0, 0, quality)).length])
211
- );
212
- for (const rootPc of input) {
213
- for (const quality of qualities) {
214
- const chord = _chunkUADYAYATcjs.makeChord.call(void 0, rootPc, quality);
215
- const tones = _chunkUADYAYATcjs.chordPitchClasses.call(void 0, chord);
216
- const toneSet = new Set(tones);
217
- const missingPcs = tones.filter((pc) => !inputSet.has(pc));
218
- const fifthPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc + 7);
219
- if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
220
- continue;
221
- }
222
- const extraPcs = input.filter((pc) => !toneSet.has(pc));
223
- const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc + iv) === bassPc);
224
- const inversion = bassPc === void 0 || bassIndex < 0 ? null : bassIndex;
225
- const match = {
226
- rootPc,
227
- quality,
228
- missingPcs,
229
- extraPcs,
230
- exact: extraPcs.length === 0 && missingPcs.length === 0,
231
- inversion
232
- };
233
- if (bassPc !== void 0 && bassPc !== rootPc) {
234
- match.bassPc = bassPc;
235
- }
236
- matches.push(match);
237
- }
238
- }
239
- matches.sort((a, b) => {
240
- if (a.extraPcs.length !== b.extraPcs.length) {
241
- return a.extraPcs.length - b.extraPcs.length;
242
- }
243
- if (a.missingPcs.length !== b.missingPcs.length) {
244
- return a.missingPcs.length - b.missingPcs.length;
245
- }
246
- if (a.inversion === 0 !== (b.inversion === 0)) {
247
- return a.inversion === 0 ? -1 : 1;
248
- }
249
- const aSize = _nullishCoalesce(toneCounts.get(a.quality), () => ( 0));
250
- const bSize = _nullishCoalesce(toneCounts.get(b.quality), () => ( 0));
251
- return bSize - aSize;
252
- });
253
- return matches;
254
- }
255
- function detectChordBest(pitches, opts = {}) {
256
- const best = detectChord(pitches, opts)[0];
257
- if (!best) {
258
- return null;
259
- }
260
- return _chunkUADYAYATcjs.makeChord.call(void 0, best.rootPc, best.quality, best.bassPc);
261
- }
262
- var MINOR_VARIANTS = [
263
- { variant: "natural", mask: _chunkUADYAYATcjs.NATURAL_MINOR_MASK, scaleName: "naturalMinor" },
264
- { variant: "harmonic", mask: _chunkUADYAYATcjs.HARMONIC_MINOR_MASK, scaleName: "harmonicMinor" },
265
- { variant: "melodic", mask: _chunkUADYAYATcjs.MELODIC_MINOR_MASK, scaleName: "melodicMinor" }
266
- ];
267
- var MAJOR_VARIANTS = [
268
- { variant: "major", mask: _chunkUADYAYATcjs.MAJOR_MASK, scaleName: "major" }
269
- ];
270
- var SCALE_NAME_ORDER = [
271
- "major",
272
- "naturalMinor",
273
- "harmonicMinor",
274
- "melodicMinor",
275
- "dorian",
276
- "phrygian",
277
- "lydian",
278
- "mixolydian",
279
- "locrian"
280
- ];
281
- function scaleNameRank(name) {
282
- return SCALE_NAME_ORDER.indexOf(name);
283
- }
284
- var EXPLAINED_RIVALS = 3;
285
- var SCORE_DIGITS = 2;
286
- var MAX_SCORE_DIGITS = 6;
287
- function quoteScores(rival, winner) {
288
- let digits = SCORE_DIGITS;
289
- while (digits < MAX_SCORE_DIGITS && rival.toFixed(digits) === winner.toFixed(digits)) {
290
- digits += 1;
291
- }
292
- return `${rival.toFixed(digits)} against ${winner.toFixed(digits)}`;
293
- }
294
- function keyLabel(match) {
295
- const tonic = _chunkQ2WGKJNNcjs.formatNote.call(void 0, _chunkUADYAYATcjs.spelledKeyOf.call(void 0, match.key).tonic);
296
- const scale = match.scaleName.replace(/([A-Z])/g, " $1").toLowerCase();
297
- return `${tonic} ${scale}`;
298
- }
299
- function describeKeyMatch(match, inputSize) {
300
- const inScale = Math.round(match.fit * inputSize);
301
- return `${keyLabel(match)}: profile correlation ${match.score.toFixed(SCORE_DIGITS)}, with ${inScale} of ${inputSize} input pitch classes in the scale`;
302
- }
303
- function keyRivalReason(winner, rival) {
304
- if (rival.score !== winner.score) {
305
- return `Ranked below on profile correlation (${quoteScores(rival.score, winner.score)})`;
306
- }
307
- if (rival.fit !== winner.fit) {
308
- return "Tied on correlation, ranked below on the share of the input its scale covers";
309
- }
310
- if (rival.key.rootPc !== winner.key.rootPc) {
311
- return "Tied on correlation and coverage, ranked below by tonic pitch class";
312
- }
313
- return "Tied on correlation and coverage on the same tonic, ranked below by scale, which takes major before the minor forms and the modes last";
314
- }
315
- function explainMatches(results, inputSize) {
316
- for (const match of results) {
317
- match.rationale = describeKeyMatch(match, inputSize);
318
- }
319
- const winner = results[0];
320
- if (winner === void 0) {
321
- return;
322
- }
323
- winner.alternatives = results.slice(1, EXPLAINED_RIVALS + 1).map((rival) => ({ label: keyLabel(rival), reason: keyRivalReason(winner, rival) }));
324
- }
325
- function detectKey(pitches, opts = {}) {
326
- assertPitches(pitches, "key detection pitches", opts.budget);
327
- const weights = opts.weights;
328
- if (weights !== void 0 && weights.length !== pitches.length) {
329
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("weights must have one entry per pitch");
330
- }
331
- const profile = resolveKeyProfile(opts.profile);
332
- const modalCandidates = resolveModalCandidates(opts.modes).map((candidate) => ({
333
- candidate,
334
- vector: modalProfileVector(profile, candidate)
335
- }));
336
- const counts = /* @__PURE__ */ new Map();
337
- let total = 0;
338
- for (let index = 0; index < pitches.length; index += 1) {
339
- const weight = weights === void 0 ? 1 : _nullishCoalesce(weights[index], () => ( 0));
340
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, weight, `weights[${index}]`);
341
- if (weight <= 0) {
342
- continue;
343
- }
344
- const pc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _nullishCoalesce(pitches[index], () => ( 0)));
345
- counts.set(pc, (_nullishCoalesce(counts.get(pc), () => ( 0))) + weight);
346
- total += weight;
347
- }
348
- if (total === 0) {
349
- return [];
350
- }
351
- const input = [...counts.keys()];
352
- const dist = new Array(12).fill(0);
353
- for (const [pc, count] of counts) {
354
- dist[pc] = count;
355
- }
356
- const results = [];
357
- for (let tonic = 0; tonic < 12; tonic += 1) {
358
- for (const mode of ["major", "minor"]) {
359
- const vector = mode === "major" ? profile.major : profile.minor;
360
- const score = profileScore(dist, vector, tonic);
361
- const variants = mode === "major" ? MAJOR_VARIANTS : MINOR_VARIANTS;
362
- let covered = Number.NEGATIVE_INFINITY;
363
- let inScale = 0;
364
- let best = variants[0];
365
- for (const candidate of variants) {
366
- let candidateCovered = 0;
367
- let candidateInScale = 0;
368
- for (const [pc, count] of counts) {
369
- const offset = (pc - tonic + 12) % 12;
370
- if (candidate.mask >> offset & 1) {
371
- candidateCovered += count;
372
- candidateInScale += 1;
373
- }
374
- }
375
- if (candidateCovered > covered) {
376
- covered = candidateCovered;
377
- inScale = candidateInScale;
378
- best = candidate;
379
- }
380
- }
381
- results.push({
382
- key: { rootPc: tonic, modeMask12: best.mask },
383
- mode,
384
- variant: best.variant,
385
- scaleName: best.scaleName,
386
- fit: inScale / input.length,
387
- score
388
- });
389
- }
390
- for (const { candidate, vector } of modalCandidates) {
391
- let inScale = 0;
392
- for (const pc of counts.keys()) {
393
- if (candidate.mask >> (pc - tonic + 12) % 12 & 1) {
394
- inScale += 1;
395
- }
396
- }
397
- results.push({
398
- key: { rootPc: tonic, modeMask12: candidate.mask },
399
- mode: candidate.mode,
400
- variant: "modal",
401
- scaleName: candidate.scaleName,
402
- fit: inScale / input.length,
403
- score: profileScore(dist, vector, tonic)
404
- });
405
- }
406
- }
407
- results.sort((a, b) => {
408
- if (a.score !== b.score) {
409
- return b.score - a.score;
410
- }
411
- if (a.fit !== b.fit) {
412
- return b.fit - a.fit;
413
- }
414
- if (a.key.rootPc !== b.key.rootPc) {
415
- return a.key.rootPc - b.key.rootPc;
416
- }
417
- return scaleNameRank(a.scaleName) - scaleNameRank(b.scaleName);
418
- });
419
- if (opts.explain === true) {
420
- explainMatches(results, input.length);
421
- }
422
- return results;
423
- }
424
- var DEFAULT_VELOCITY = 100;
425
- function detectKeyFromNotes(notes, opts = {}) {
426
- _chunkQ2WGKJNNcjs.assertNoteEvents.call(void 0, notes, "key detection notes", {
427
- allowNonPositiveDuration: true,
428
- budget: opts.budget
429
- });
430
- const sounding = notes.filter((note) => note.durationBeat > 0);
431
- return detectKey(
432
- sounding.map((note) => note.pitch),
433
- {
434
- weights: sounding.map((note) => note.durationBeat * (_nullishCoalesce(note.velocity, () => ( DEFAULT_VELOCITY)))),
435
- profile: opts.profile,
436
- modes: opts.modes,
437
- explain: opts.explain,
438
- budget: opts.budget
439
- }
440
- );
441
- }
442
- function detectKeyBest(pitches, opts = {}) {
443
- return _nullishCoalesce(detectKey(pitches, opts)[0], () => ( null));
444
- }
445
-
446
- // src/analyze/functional/internal.ts
447
- function isMinorScale(key) {
448
- const minorThirdBit = key.modeMask12 >> 3 & 1;
449
- const majorThirdBit = key.modeMask12 >> 4 & 1;
450
- return Boolean(minorThirdBit) && !majorThirdBit;
451
- }
452
- function parallelScale(key) {
453
- return {
454
- rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc),
455
- modeMask12: isMinorScale(key) ? _chunkUADYAYATcjs.MAJOR_MASK : _chunkUADYAYATcjs.NATURAL_MINOR_MASK
456
- };
457
- }
458
- function degreeRootPc(degreeNumber, key) {
459
- const tones = _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, key);
460
- if (tones.length === 0) {
461
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
462
- }
463
- const index = ((degreeNumber - 1) % tones.length + tones.length) % tones.length;
464
- return _nullishCoalesce(tones[index], () => ( _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc)));
465
- }
466
- function loweredDegrees(key) {
467
- const tones = _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, key);
468
- const out = /* @__PURE__ */ new Set();
469
- if (tones.length !== 7) {
470
- return out;
471
- }
472
- const major = _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc), modeMask12: _chunkUADYAYATcjs.MAJOR_MASK });
473
- for (let index = 0; index < 7; index += 1) {
474
- const tone = tones[index];
475
- const majorTone = major[index];
476
- if (tone !== void 0 && majorTone !== void 0 && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, majorTone - tone) === 1) {
477
- out.add(index + 1);
478
- }
479
- }
480
- return out;
481
- }
482
- function romanReference(key) {
483
- return _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, key).length === 7 ? key : _chunkUADYAYATcjs.majorKey.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc));
484
- }
485
- function isDiatonicChord(chord, key) {
486
- return _chunkUADYAYATcjs.chordPitchClasses.call(void 0, chord).every((pc) => _chunkUADYAYATcjs.isScaleTone.call(void 0, pc, key));
487
- }
488
- function hasMajorThird(chord) {
489
- return chord.intervals.some((interval) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, interval) === 4);
490
- }
491
- function isNeapolitan(chord, key) {
492
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 1 && chord.quality === "maj" && !isDiatonicChord(chord, key);
493
- }
494
- function isAppliedDominantSonority(chord) {
495
- const has = (semitones) => chord.intervals.some((i) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, i) === semitones);
496
- return chord.quality === "maj" || has(4) && has(10);
497
- }
498
-
499
- // src/analyze/functional/augmented-sixth.ts
500
- var KINDS = ["italian", "french", "german"];
501
- var BASS_ABOVE_TONIC = 8;
502
- var TONES_ABOVE_BASS = {
503
- italian: [0, 4, 10],
504
- french: [0, 4, 6, 10],
505
- german: [0, 4, 7, 10]
506
- };
507
- var SPELLED_ABOVE_BASS = {
508
- italian: ["P1", "M3", "A6"].map((name) => _chunkQ2WGKJNNcjs.parseInterval.call(void 0, name)),
509
- french: ["P1", "M3", "A4", "A6"].map((name) => _chunkQ2WGKJNNcjs.parseInterval.call(void 0, name)),
510
- german: ["P1", "M3", "P5", "A6"].map((name) => _chunkQ2WGKJNNcjs.parseInterval.call(void 0, name))
511
- };
512
- var BASS_INTERVAL = _chunkQ2WGKJNNcjs.parseInterval.call(void 0, "m6");
513
- var CHORD_SHAPE = {
514
- italian: { rootAboveBass: 0, quality: "dom7", intervals: [0, 4, 10] },
515
- french: { rootAboveBass: 6, quality: "7b5", intervals: [0, 4, 6, 10] },
516
- german: { rootAboveBass: 0, quality: "dom7", intervals: [0, 4, 7, 10] }
517
- };
518
- var KIND_BY_SYMBOL = /* @__PURE__ */ new Map([
519
- ["It6", "italian"],
520
- ["Fr6", "french"],
521
- ["Ger6", "german"],
522
- // The German sixth's figures over its bass are 6/5; both spellings are in use.
523
- ["Ger65", "german"]
524
- ]);
525
- var SYMBOL_BY_KIND = {
526
- italian: "It6",
527
- french: "Fr6",
528
- german: "Ger6"
529
- };
530
- function bassPcOf(key) {
531
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc + BASS_ABOVE_TONIC);
532
- }
533
- function bareSpelling(note) {
534
- return { letter: note.letter, alter: note.alter };
535
- }
536
- function spelledInChordOrder(kind, tonic) {
537
- const tones = spelledFromTonic(kind, tonic);
538
- const rootIndex = TONES_ABOVE_BASS[kind].indexOf(CHORD_SHAPE[kind].rootAboveBass);
539
- return [...tones.slice(rootIndex), ...tones.slice(0, rootIndex)];
540
- }
541
- function augmentedSixthChord(kind, key) {
542
- return augmentedSixthOn(kind, _chunkUADYAYATcjs.toKeyScale.call(void 0, key));
543
- }
544
- function augmentedSixthOn(kind, key) {
545
- const checked = _chunkQ2WGKJNNcjs.assertOneOf.call(void 0, kind, KINDS, "augmented sixth kind");
546
- const shape = CHORD_SHAPE[checked];
547
- const bassPc = bassPcOf(key);
548
- const chord = _chunkUADYAYATcjs.makeChord.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, bassPc + shape.rootAboveBass), shape.quality, bassPc);
549
- chord.intervals = [...shape.intervals];
550
- const tonic = _chunkUADYAYATcjs.spelledKeyOf.call(void 0, key).tonic;
551
- const bass = _chunkQ2WGKJNNcjs.transposeByInterval.call(void 0, tonic, BASS_INTERVAL);
552
- const tones = spelledInChordOrder(checked, tonic);
553
- const [root = bass] = tones;
554
- chord.rootSpelling = bareSpelling(root);
555
- chord.bassSpelling = bareSpelling(bass);
556
- chord.toneSpellings = tones.map(bareSpelling);
557
- return chord;
558
- }
559
- function kindOfPitchClasses(pcs, bass) {
560
- for (const kind of KINDS) {
561
- const tones = TONES_ABOVE_BASS[kind];
562
- if (pcs.size === tones.length && tones.every((tone) => pcs.has(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, bass + tone)))) {
563
- return kind;
564
- }
565
- }
566
- return null;
567
- }
568
- function spellingId(note) {
569
- return `${_chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, note.letter)}:${note.alter}`;
570
- }
571
- function spellsAugmentedSixth(chord, kind, key) {
572
- const tonic = _chunkUADYAYATcjs.spelledKeyOf.call(void 0, key).tonic;
573
- const written = new Set(_chunkUADYAYATcjs.spellChord.call(void 0, chord, tonic, key).map(spellingId));
574
- const tones = spelledFromTonic(kind, tonic).map(spellingId);
575
- return written.size === tones.length && tones.every((tone) => written.has(tone));
576
- }
577
- function augmentedSixthKind(chord, key) {
578
- return augmentedSixthKindOf(_chunkUADYAYATcjs.toChordData.call(void 0, chord), _chunkUADYAYATcjs.toKeyScale.call(void 0, key));
579
- }
580
- function augmentedSixthKindOf(chord, key) {
581
- const bass = bassPcOf(key);
582
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _nullishCoalesce(chord.bassPc, () => ( chord.rootPc))) !== bass) {
583
- return null;
584
- }
585
- const kind = kindOfPitchClasses(new Set(_chunkUADYAYATcjs.chordPitchClasses.call(void 0, chord)), bass);
586
- if (kind === null || !spellsAugmentedSixth(chord, kind, key)) {
587
- return null;
588
- }
589
- return kind;
590
- }
591
- function augmentedSixthFromPitchClasses(pcs, bassPc, key) {
592
- return augmentedSixthOverBass(pcs, bassPc, _chunkUADYAYATcjs.toKeyScale.call(void 0, key));
593
- }
594
- function augmentedSixthOverBass(pcs, bassPc, key) {
595
- const bass = bassPcOf(key);
596
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, bassPc) !== bass) {
597
- return null;
598
- }
599
- const kind = kindOfPitchClasses(new Set(pcs.map(_chunkQ2WGKJNNcjs.pitchClassOf)), bass);
600
- return kind === null ? null : augmentedSixthOn(kind, key);
601
- }
602
- function spellAugmentedSixth(kind, tonic) {
603
- return spelledFromTonic(kind, _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic));
604
- }
605
- function spelledFromTonic(kind, tonic) {
606
- const checked = _chunkQ2WGKJNNcjs.assertOneOf.call(void 0, kind, KINDS, "augmented sixth kind");
607
- const bass = _chunkQ2WGKJNNcjs.transposeByInterval.call(void 0, tonic, BASS_INTERVAL);
608
- return SPELLED_ABOVE_BASS[checked].map((interval) => _chunkQ2WGKJNNcjs.transposeByInterval.call(void 0, bass, interval));
609
- }
610
- function augmentedSixthFromSymbol(symbol, key) {
611
- const kind = KIND_BY_SYMBOL.get(symbol);
612
- return kind === void 0 ? null : augmentedSixthOn(kind, key);
613
- }
614
- function augmentedSixthSymbol(chord, key) {
615
- const kind = augmentedSixthKindOf(chord, key);
616
- return kind === null ? null : SYMBOL_BY_KIND[kind];
617
- }
618
-
619
- // src/analyze/functional/borrowed.ts
620
- function isMinorScaleAlteration(chord, key) {
621
- if (!isMinorScale(key)) return false;
622
- const alteredKey = { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc), modeMask12: _chunkUADYAYATcjs.HARMONIC_MINOR_MASK };
623
- if (isDiatonicChord(chord, alteredKey)) return true;
624
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 11 && isDiatonicChord(chord, { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc), modeMask12: _chunkUADYAYATcjs.MELODIC_MINOR_MASK });
625
- }
626
- function isBorrowed(chord, key) {
627
- if (isDiatonicChord(chord, key)) {
628
- return false;
629
- }
630
- if (isNeapolitan(chord, key)) {
631
- return true;
632
- }
633
- if (isMinorScaleAlteration(chord, key)) {
634
- return false;
635
- }
636
- return isDiatonicChord(chord, parallelScale(key));
637
- }
638
- function borrowedSourceOf(chord, key) {
639
- if (isDiatonicChord(chord, key)) {
640
- return null;
641
- }
642
- if (isNeapolitan(chord, key)) return "neapolitan";
643
- if (isBorrowed(chord, key)) {
644
- return isMinorScale(key) ? "parallelMajor" : "parallelMinor";
645
- }
646
- return null;
647
- }
648
- function isBorrowedChord(chord, key) {
649
- return isBorrowed(_chunkUADYAYATcjs.toChordData.call(void 0, chord), _chunkUADYAYATcjs.toKeyScale.call(void 0, key));
650
- }
651
- function borrowedSource(chord, key) {
652
- return borrowedSourceOf(_chunkUADYAYATcjs.toChordData.call(void 0, chord), _chunkUADYAYATcjs.toKeyScale.call(void 0, key));
653
- }
654
-
655
- // src/analyze/functional/cadence.ts
656
- var DOMINANT_DEGREE = 5;
657
- var SUBMEDIANT_DEGREE = 6;
658
- function degreeOffset(degreeNumber, key) {
659
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, degreeRootPc(degreeNumber, romanReference(key)) - key.rootPc);
660
- }
661
- function thirdBelongsToKey(chord, key) {
662
- return chord.intervals.some(
663
- (interval) => (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, interval) === 3 || _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, interval) === 4) && _chunkUADYAYATcjs.isScaleTone.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + interval), key)
664
- );
665
- }
666
- function deceptiveTargets(key) {
667
- const submediant = degreeOffset(SUBMEDIANT_DEGREE, key);
668
- if (isMinorScale(key)) {
669
- return [submediant];
670
- }
671
- const borrowed = degreeOffset(SUBMEDIANT_DEGREE, parallelScale(key));
672
- return borrowed === submediant ? [submediant] : [submediant, borrowed];
673
- }
674
- function restsOnDominant(to, key) {
675
- if (hasMajorThird(to)) {
676
- return true;
677
- }
678
- const dominantPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc + degreeOffset(DOMINANT_DEGREE, key));
679
- const leadingToneDominant = _chunkUADYAYATcjs.isScaleTone.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, dominantPc + 4), key);
680
- return !leadingToneDominant && thirdBelongsToKey(to, key);
681
- }
682
- function isLeadingToneDiminished(chord) {
683
- return chord.quality === "dim" || chord.quality === "dim7" || chord.quality === "m7b5";
684
- }
685
- function isDominantOf(chord, key) {
686
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 7 && hasMajorThird(chord);
687
- }
688
- function isLeadingToneOf(chord, key) {
689
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 11 && isLeadingToneDiminished(chord);
690
- }
691
- function isTonicSixFour(chord, key) {
692
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) !== 0 || chord.bassPc === void 0) {
693
- return false;
694
- }
695
- const isTriad = chord.intervals.length === 3 && chord.intervals.some((interval) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, interval) === 7);
696
- return isTriad && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.bassPc - chord.rootPc) === 7;
697
- }
698
- function isCadentialSixFour(chord, next, key) {
699
- return isTonicSixFour(chord, key) && isDominantOf(next, key) && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _nullishCoalesce(chord.bassPc, () => ( chord.rootPc))) === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _nullishCoalesce(next.bassPc, () => ( next.rootPc)));
700
- }
701
- function outerPitches(voicing) {
702
- let bass;
703
- let soprano;
704
- for (const pitch of voicing) {
705
- if (!Number.isFinite(pitch)) {
706
- continue;
707
- }
708
- if (bass === void 0 || pitch < bass) {
709
- bass = pitch;
710
- }
711
- if (soprano === void 0 || pitch > soprano) {
712
- soprano = pitch;
713
- }
714
- }
715
- return { bass, soprano };
716
- }
717
- function bassPcOf2(chord, voicedBass) {
718
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _nullishCoalesce(_nullishCoalesce(voicedBass, () => ( chord.bassPc)), () => ( chord.rootPc)));
719
- }
720
- function cadenceType(from, to, key, fromBassPc, toBassPc) {
721
- const tonic = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
722
- const fromOffset = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, from.rootPc - tonic);
723
- const toOffset = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, to.rootPc - tonic);
724
- if (isCadentialSixFour(from, to, key)) {
725
- return null;
726
- }
727
- const dominant = isDominantOf(from, key);
728
- if ((dominant || isLeadingToneOf(from, key)) && toOffset === 0) {
729
- return "authentic";
730
- }
731
- if (fromOffset === 5 && toOffset === 0) {
732
- return "plagal";
733
- }
734
- if (fromOffset === 10 && toOffset === 0 && hasMajorThird(from)) {
735
- return "modal";
736
- }
737
- if (dominant && deceptiveTargets(key).includes(toOffset)) {
738
- return "deceptive";
739
- }
740
- const dominantOffset = degreeOffset(DOMINANT_DEGREE, key);
741
- const half = toOffset === dominantOffset && restsOnDominant(to, key) && fromOffset !== dominantOffset;
742
- if (half && isMinorScale(key) && fromOffset === 5 && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, fromBassPc - tonic) === 8 && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, toBassPc - tonic) === 7) {
743
- return "phrygian";
744
- }
745
- return half ? "half" : null;
746
- }
747
- function authenticStrength(leadingTone, rootPosition, voiced, soprano) {
748
- if (leadingTone || !rootPosition) {
749
- return "imperfect";
750
- }
751
- if (!voiced) {
752
- return null;
753
- }
754
- return soprano === "root" ? "perfect" : "imperfect";
755
- }
756
- function imperfectReason(facts) {
757
- if (facts.leadingTone) {
758
- return "the dominant root never sounds under the leading-tone chord";
759
- }
760
- if (!facts.rootPosition) {
761
- return "a chord stands on something other than its own root";
762
- }
763
- return "the soprano does not land on the tonic";
764
- }
765
- function describeNonCadence(facts) {
766
- if (facts.withinSixFour) {
767
- return "No cadence: the tonic six-four stands on the dominant already, so its resolution is one harmony moving inside itself rather than a motion between two";
768
- }
769
- if (facts.fromOffset === facts.toOffset) {
770
- return "No cadence: the harmony repeats, so there is no root motion to cadence with";
771
- }
772
- if (facts.toOffset === 0) {
773
- return "No cadence: the tonic is approached by none of the chords that cadence onto it";
774
- }
775
- if (facts.onFifthDegree) {
776
- return "No cadence: the arrival stands on the fifth degree but sounds no third the key rests on, so it is not the dominant a half cadence comes to";
777
- }
778
- return "No cadence: the motion arrives on neither the tonic nor the dominant";
779
- }
780
- function describeCadence(facts) {
781
- let text;
782
- switch (facts.type) {
783
- case "authentic": {
784
- const approach = facts.leadingTone ? "leading-tone chord" : "dominant";
785
- const head = `Authentic cadence: the ${approach} resolves onto the tonic`;
786
- text = facts.strength === "perfect" ? `${head}, perfect with both chords on their roots and the tonic in the soprano` : facts.strength === "imperfect" ? `${head}, imperfect because ${imperfectReason(facts)}` : `${head}; no voicing names the soprano, so it is graded neither perfect nor imperfect`;
787
- break;
788
- }
789
- case "plagal":
790
- text = "Plagal cadence: the subdominant falls onto the tonic";
791
- break;
792
- case "modal":
793
- text = "Modal cadence: the major triad on the subtonic rises onto the tonic with no leading tone";
794
- break;
795
- case "deceptive":
796
- text = "Deceptive cadence: the dominant resolves onto the submediant in place of the tonic";
797
- break;
798
- case "phrygian":
799
- text = "Phrygian cadence: the bass falls a semitone from the lowered submediant onto the dominant, and the motion rests there";
800
- break;
801
- case "half":
802
- text = "Half cadence: the motion comes to rest on the dominant";
803
- break;
804
- case null:
805
- text = describeNonCadence(facts);
806
- break;
807
- }
808
- if (facts.evaded) {
809
- text = `${text}; the tonic arrives inverted, so the arrival the ear was promised is withheld`;
810
- }
811
- return facts.sixFour ? `${text}; the dominant was reached as a cadential six-four, a double appoggiatura over the same bass, so the cadence begins there rather than at the dominant's own arrival` : text;
812
- }
813
- function cadenceAlternatives(facts) {
814
- const out = [];
815
- if (facts.type === "deceptive") {
816
- out.push({
817
- label: "authentic",
818
- reason: "The dominant was prepared, but the arrival is the submediant rather than the tonic"
819
- });
820
- } else if (facts.type !== "authentic" && facts.toOffset === 0) {
821
- out.push({
822
- label: "authentic",
823
- reason: "The arrival is the tonic, but the chord before it is neither the dominant nor a leading-tone chord"
824
- });
825
- }
826
- if (facts.type === "authentic" && facts.strength !== "perfect") {
827
- out.push({
828
- label: "perfect authentic",
829
- reason: facts.strength === null ? "The chords meet every condition a voicing can be judged without, but no voicing names the soprano" : `The cadence is authentic, but ${imperfectReason(facts)}`
830
- });
831
- }
832
- if (facts.type === "half" && facts.minor && facts.fromOffset === 5) {
833
- out.push({
834
- label: "phrygian",
835
- reason: "The dominant is approached from the subdominant, but the bass does not fall a semitone from the lowered submediant onto it"
836
- });
837
- }
838
- if (facts.type === null && facts.onFifthDegree && facts.fromOffset !== facts.toOffset) {
839
- out.push({
840
- label: "half",
841
- reason: "The arrival stands on the fifth degree, but a half cadence rests on the dominant the key has: the major third, or the mode's own third where that dominant carries no leading tone"
842
- });
843
- }
844
- return out;
845
- }
846
- function detectCadence(from, to, key, opts = {}) {
847
- const { approach, ...rest } = opts;
848
- return cadenceBetween(
849
- _chunkUADYAYATcjs.toChordData.call(void 0, from),
850
- _chunkUADYAYATcjs.toChordData.call(void 0, to),
851
- _chunkUADYAYATcjs.toKeyScale.call(void 0, key),
852
- approach === void 0 ? rest : { ...rest, approach: _chunkUADYAYATcjs.toChordData.call(void 0, approach) }
853
- );
854
- }
855
- function cadenceBetween(from, to, key, opts = {}) {
856
- const [fromVoicing, toVoicing] = _nullishCoalesce(opts.voicing, () => ( []));
857
- const fromOuter = outerPitches(_nullishCoalesce(fromVoicing, () => ( [])));
858
- const toOuter = outerPitches(_nullishCoalesce(toVoicing, () => ( [])));
859
- const fromBassPc = bassPcOf2(from, fromOuter.bass);
860
- const toBassPc = bassPcOf2(to, toOuter.bass);
861
- const toRootPosition = toBassPc === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, to.rootPc);
862
- const rootPosition = fromBassPc === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, from.rootPc) && toRootPosition;
863
- const soprano = toOuter.soprano === void 0 ? null : _chunkUADYAYATcjs.chordToneRole.call(void 0, toOuter.soprano, to);
864
- const type = cadenceType(from, to, key, fromBassPc, toBassPc);
865
- const leadingTone = isLeadingToneOf(from, key);
866
- const facts = {
867
- type,
868
- strength: type === "authentic" ? authenticStrength(leadingTone, rootPosition, toOuter.soprano !== void 0, soprano) : null,
869
- rootPosition,
870
- // The dominant resolved, but onto an inverted tonic: the arrival the ear was
871
- // promised is the one it does not get.
872
- evaded: type === "authentic" && !toRootPosition,
873
- leadingTone,
874
- fromOffset: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, from.rootPc - key.rootPc),
875
- toOffset: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, to.rootPc - key.rootPc),
876
- minor: isMinorScale(key),
877
- onFifthDegree: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, to.rootPc - key.rootPc) === degreeOffset(DOMINANT_DEGREE, key),
878
- sixFour: opts.approach !== void 0 && isCadentialSixFour(opts.approach, from, key),
879
- withinSixFour: isCadentialSixFour(from, to, key)
880
- };
881
- const result = {
882
- type: facts.type,
883
- strength: facts.strength,
884
- rootPosition: facts.rootPosition,
885
- evaded: facts.evaded,
886
- rationale: describeCadence(facts),
887
- alternatives: opts.alternatives === true ? cadenceAlternatives(facts) : []
888
- };
889
- if (soprano !== null) {
890
- result.soprano = soprano;
891
- }
892
- return result;
893
- }
894
-
895
- // src/analyze/functional/roman.ts
896
- var ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII"];
897
- var ROMAN_TO_DEGREE = {
898
- I: 1,
899
- II: 2,
900
- III: 3,
901
- IV: 4,
902
- V: 5,
903
- VI: 6,
904
- VII: 7
905
- };
906
- var OFFSET_SPELLING = [
907
- [1, ""],
908
- [2, "b"],
909
- [2, ""],
910
- [3, "b"],
911
- [3, ""],
912
- [4, ""],
913
- [4, "#"],
914
- [5, ""],
915
- [6, "b"],
916
- [6, ""],
917
- [7, "b"],
918
- [7, ""]
919
- ];
920
- function seventhQuality(base, halfDim) {
921
- if (halfDim) {
922
- return "m7b5";
923
- }
924
- if (base === "dim") {
925
- return "dim7";
926
- }
927
- if (base === "aug") {
928
- return "aug7";
929
- }
930
- return base === "maj" ? "dom7" : "min7";
931
- }
932
- var INVERSION_FIGURES = /* @__PURE__ */ new Set(["6", "64", "65", "43", "42", "2", "7"]);
933
- var ROMAN_STYLE = {
934
- maj: { lower: false, suffix: "" },
935
- min: { lower: true, suffix: "" },
936
- dim: { lower: true, suffix: "o" },
937
- aug: { lower: false, suffix: "+" },
938
- maj7: { lower: false, suffix: "maj7" },
939
- min7: { lower: true, suffix: "7" },
940
- dom7: { lower: false, suffix: "7" },
941
- dim7: { lower: true, suffix: "o7" },
942
- m7b5: { lower: true, suffix: "\xF87" },
943
- minMaj7: { lower: true, suffix: "maj7" },
944
- minMaj9: { lower: true, suffix: "maj9" },
945
- minMaj11: { lower: true, suffix: "maj11" },
946
- minMaj13: { lower: true, suffix: "maj13" },
947
- aug7: { lower: false, suffix: "+7" },
948
- augMaj7: { lower: false, suffix: "+maj7" },
949
- majb5: { lower: false, suffix: "b5" },
950
- "6": { lower: false, suffix: "add6" },
951
- min6: { lower: true, suffix: "add6" },
952
- "6/9": { lower: false, suffix: "69" },
953
- sus2: { lower: false, suffix: "sus2" },
954
- sus4: { lower: false, suffix: "sus4" },
955
- add9: { lower: false, suffix: "add9" },
956
- add11: { lower: false, suffix: "add11" },
957
- maj9: { lower: false, suffix: "maj9" },
958
- min9: { lower: true, suffix: "9" },
959
- dom9: { lower: false, suffix: "9" },
960
- "7b9": { lower: false, suffix: "7b9" },
961
- "7#9": { lower: false, suffix: "7#9" },
962
- "7#11": { lower: false, suffix: "7#11" },
963
- "7b13": { lower: false, suffix: "7b13" },
964
- "11": { lower: false, suffix: "11" },
965
- "13": { lower: false, suffix: "13" },
966
- "5": { lower: false, suffix: "5" },
967
- "7sus4": { lower: false, suffix: "7sus4" },
968
- "7b5": { lower: false, suffix: "7b5" },
969
- "7alt": { lower: false, suffix: "7alt" },
970
- "13b9": { lower: false, suffix: "13b9" },
971
- maj13: { lower: false, suffix: "maj13" },
972
- "maj7#11": { lower: false, suffix: "maj7#11" },
973
- min11: { lower: true, suffix: "11" },
974
- min13: { lower: true, suffix: "13" },
975
- minAdd9: { lower: true, suffix: "add9" },
976
- "min6/9": { lower: true, suffix: "69" }
977
- };
978
- var SUFFIX_QUALITY = new Map(
979
- Object.entries(ROMAN_STYLE).map(
980
- ([quality, style]) => [`${style.lower ? "l" : "u"}:${style.suffix}`, quality]
981
- )
982
- );
983
- function parseInversion(figures) {
984
- if (figures === "65") {
985
- return { inversion: 1, seventh: true };
986
- }
987
- if (figures === "43") {
988
- return { inversion: 2, seventh: true };
989
- }
990
- if (figures === "42" || figures === "2") {
991
- return { inversion: 3, seventh: true };
992
- }
993
- if (figures === "7") {
994
- return { inversion: 0, seventh: true };
995
- }
996
- if (figures === "64") {
997
- return { inversion: 2, seventh: false };
998
- }
999
- if (figures === "6") {
1000
- return { inversion: 1, seventh: false };
1001
- }
1002
- return { inversion: 0, seventh: false };
1003
- }
1004
- function parseSimpleRoman(text, key) {
1005
- const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());
1006
- if (!match) {
1007
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Invalid Roman numeral: ${text}`);
1008
- }
1009
- const glyphText = _nullishCoalesce(match[2], () => ( ""));
1010
- if (glyphText !== glyphText.toUpperCase() && glyphText !== glyphText.toLowerCase()) {
1011
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Roman numeral must use one case: ${text}`);
1012
- }
1013
- const accidental = match[1] === "b" ? -1 : match[1] === "#" ? 1 : 0;
1014
- const glyph = glyphText.toUpperCase();
1015
- const degreeNumber = ROMAN_TO_DEGREE[glyph];
1016
- if (degreeNumber === void 0) {
1017
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Invalid Roman numeral: ${text}`);
1018
- }
1019
- const isUpper = glyphText[0] === _optionalChain([glyphText, 'access', _ => _[0], 'optionalAccess', _2 => _2.toUpperCase, 'call', _3 => _3()]);
1020
- const suffix = _nullishCoalesce(match[3], () => ( ""));
1021
- const frame = romanReference(key);
1022
- const redundantFlat = accidental === -1 && loweredDegrees(frame).has(degreeNumber);
1023
- const diatonicRootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0,
1024
- degreeRootPc(degreeNumber, frame) + (redundantFlat ? 0 : accidental)
1025
- );
1026
- const rootForQuality = (quality2) => accidental === 0 && degreeNumber === 7 && loweredDegrees(frame).has(7) && (quality2 === "dim" || quality2 === "dim7") ? _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, diatonicRootPc + 1) : diatonicRootPc;
1027
- const canonical = SUFFIX_QUALITY.get(`${isUpper ? "u" : "l"}:${suffix}`);
1028
- if (canonical !== void 0) {
1029
- return { rootPc: rootForQuality(canonical), quality: canonical, inversion: 0 };
1030
- }
1031
- const parsedSuffix = /^(o|°|dim|ø|\+|aug)?(maj7|M7)?(65|64|43|42|7|6|2)?$/.exec(suffix);
1032
- if (!parsedSuffix) {
1033
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unsupported suffix "${suffix}" in Roman numeral: ${text}`);
1034
- }
1035
- const qualityMarker = _nullishCoalesce(parsedSuffix[1], () => ( ""));
1036
- const isDim = qualityMarker === "o" || qualityMarker === "\xB0" || qualityMarker === "dim";
1037
- const isAug = qualityMarker === "+" || qualityMarker === "aug";
1038
- const isHalfDim = qualityMarker === "\xF8";
1039
- const explicitMaj7 = parsedSuffix[2] !== void 0;
1040
- const figures = _nullishCoalesce(parsedSuffix[3], () => ( ""));
1041
- if (figures !== "" && !INVERSION_FIGURES.has(figures)) {
1042
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1043
- `Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`
1044
- );
1045
- }
1046
- const { inversion, seventh } = parseInversion(figures);
1047
- const hasSeventh = explicitMaj7 || seventh || isHalfDim;
1048
- let base;
1049
- if (isDim || isHalfDim) {
1050
- base = "dim";
1051
- } else if (isAug) {
1052
- base = "aug";
1053
- } else {
1054
- base = isUpper ? "maj" : "min";
1055
- }
1056
- let quality;
1057
- if (explicitMaj7) {
1058
- quality = base === "min" ? "minMaj7" : base === "aug" ? "augMaj7" : "maj7";
1059
- } else if (hasSeventh) {
1060
- quality = seventhQuality(base, isHalfDim);
1061
- } else if (base === "dim") {
1062
- quality = "dim";
1063
- } else if (base === "aug") {
1064
- quality = "aug";
1065
- } else {
1066
- quality = base;
1067
- }
1068
- return { rootPc: rootForQuality(quality), quality, inversion };
1069
- }
1070
- var NEAPOLITAN_SIXTH = "N6";
1071
- function chromaticSymbolChord(text, key) {
1072
- if (text === NEAPOLITAN_SIXTH) {
1073
- const rootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc + 1);
1074
- return _chunkUADYAYATcjs.makeChord.call(void 0, rootPc, "maj", _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc + 4));
1075
- }
1076
- return augmentedSixthFromSymbol(text, key);
1077
- }
1078
- function chordFromParsed(parsed) {
1079
- const chord = _chunkUADYAYATcjs.makeChord.call(void 0, parsed.rootPc, parsed.quality);
1080
- if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {
1081
- chord.bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, parsed.rootPc + (_nullishCoalesce(chord.intervals[parsed.inversion], () => ( 0))));
1082
- }
1083
- return chord;
1084
- }
1085
- function romanToChord(text, key) {
1086
- const scale = _chunkUADYAYATcjs.toKeyScale.call(void 0, key);
1087
- const trimmed = text.trim().replace(/(\d)\/(?=\d)/g, "$1");
1088
- const slash = trimmed.indexOf("/");
1089
- if (slash >= 0) {
1090
- const applied = trimmed.slice(0, slash);
1091
- const target = trimmed.slice(slash + 1);
1092
- const targetRoot = parseSimpleRoman(target, scale).rootPc;
1093
- const localKey = _chunkUADYAYATcjs.majorKey.call(void 0, targetRoot);
1094
- return _nullishCoalesce(chromaticSymbolChord(applied, localKey), () => ( chordFromParsed(parseSimpleRoman(applied, localKey))));
1095
- }
1096
- return _nullishCoalesce(chromaticSymbolChord(trimmed, scale), () => ( chordFromParsed(parseSimpleRoman(trimmed, scale))));
1097
- }
1098
- function romanStyle(quality) {
1099
- return ROMAN_STYLE[quality];
1100
- }
1101
- function romanSpelling(rootPc, key) {
1102
- const tones = _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, romanReference(key));
1103
- const diatonic = tones.indexOf(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc));
1104
- if (diatonic >= 0) {
1105
- return { degreeNumber: diatonic + 1, accidental: "" };
1106
- }
1107
- const lowered = loweredDegrees(romanReference(key));
1108
- for (let i = 0; i < tones.length; i += 1) {
1109
- if (lowered.has(i + 1) && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[i], () => ( 0))) + 1) === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc)) {
1110
- return { degreeNumber: i + 1, accidental: "#" };
1111
- }
1112
- }
1113
- for (let i = 0; i < tones.length; i += 1) {
1114
- const degreeNumber2 = i + 1;
1115
- if (degreeNumber2 === 1 || degreeNumber2 === 5) {
1116
- continue;
1117
- }
1118
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[i], () => ( 0))) - 1) === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc)) {
1119
- return { degreeNumber: degreeNumber2, accidental: "b" };
1120
- }
1121
- }
1122
- for (let i = 0; i < tones.length; i += 1) {
1123
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[i], () => ( 0))) + 1) === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc)) {
1124
- return { degreeNumber: i + 1, accidental: "#" };
1125
- }
1126
- }
1127
- const [degreeNumber, accidental] = _nullishCoalesce(OFFSET_SPELLING[_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc - key.rootPc)], () => ( [1, ""]));
1128
- return { degreeNumber, accidental };
1129
- }
1130
- function seventhDegreeAccidental(accidental, degreeNumber, quality, key) {
1131
- if (degreeNumber !== 7 || quality !== "dim" && quality !== "dim7" || !loweredDegrees(romanReference(key)).has(7)) {
1132
- return accidental;
1133
- }
1134
- if (accidental === "#") {
1135
- return "";
1136
- }
1137
- return accidental === "" ? "b" : accidental;
1138
- }
1139
- function baseMarker(quality) {
1140
- if (quality === "dim" || quality === "dim7") {
1141
- return "o";
1142
- }
1143
- if (quality === "m7b5") {
1144
- return "\xF8";
1145
- }
1146
- if (quality === "aug" || quality === "aug7") {
1147
- return "+";
1148
- }
1149
- if (quality === "augMaj7") {
1150
- return "+maj7";
1151
- }
1152
- if (quality === "maj7" || quality === "minMaj7") {
1153
- return "maj7";
1154
- }
1155
- return "";
1156
- }
1157
- var TRIAD_FIGURES = { 1: "6", 2: "64" };
1158
- var SEVENTH_FIGURES = { 1: "65", 2: "43", 3: "42" };
1159
- var TRIAD_FIGURE_QUALITIES = /* @__PURE__ */ new Set(["maj", "min", "dim", "aug"]);
1160
- var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
1161
- "maj7",
1162
- "min7",
1163
- "dom7",
1164
- "dim7",
1165
- "m7b5",
1166
- "minMaj7",
1167
- "aug7",
1168
- "augMaj7"
1169
- ]);
1170
- function numeralFor(degreeNumber, lower) {
1171
- const glyph = ROMAN[degreeNumber - 1];
1172
- if (glyph === void 0) {
1173
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`no Roman numeral for scale degree ${degreeNumber}`);
1174
- }
1175
- return lower ? glyph.toLowerCase() : glyph;
1176
- }
1177
- var LEADING_TONE_QUALITIES = /* @__PURE__ */ new Set(["dim", "dim7", "m7b5"]);
1178
- function degreeTriad(tones, index) {
1179
- const root = _nullishCoalesce(tones[index], () => ( 0));
1180
- return {
1181
- third: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[(index + 2) % tones.length], () => ( 0))) - root),
1182
- fifth: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[(index + 4) % tones.length], () => ( 0))) - root)
1183
- };
1184
- }
1185
- function appliedTarget(chord, key) {
1186
- const dominant = isAppliedDominantSonority(chord);
1187
- const leadingTone = LEADING_TONE_QUALITIES.has(chord.quality);
1188
- if (!dominant && !leadingTone) {
1189
- return null;
1190
- }
1191
- const tones = _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, romanReference(key));
1192
- for (let index = 1; index < tones.length; index += 1) {
1193
- const targetRoot = tones[index];
1194
- if (targetRoot === void 0) {
1195
- continue;
1196
- }
1197
- const triad = degreeTriad(tones, index);
1198
- if (triad.fifth !== 7) {
1199
- continue;
1200
- }
1201
- const expectedRoot = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, targetRoot + (dominant ? 7 : -1));
1202
- if (expectedRoot === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc)) {
1203
- return { degreeNumber: index + 1, rootPc: targetRoot, lower: triad.third === 3 };
1204
- }
1205
- }
1206
- return null;
1207
- }
1208
- function chordToRoman(chord, key, opts = {}) {
1209
- return renderRoman(_chunkUADYAYATcjs.toChordData.call(void 0, chord), _chunkUADYAYATcjs.toKeyScale.call(void 0, key), opts).roman;
1210
- }
1211
- function renderRoman(chord, key, opts) {
1212
- const augmented = augmentedSixthSymbol(chord, key);
1213
- if (augmented !== null) {
1214
- return { roman: augmented, derivation: { kind: "augmentedSixth" } };
1215
- }
1216
- if (opts.applied === true && !isDiatonicChord(chord, key)) {
1217
- const target = appliedTarget(chord, key);
1218
- if (target !== null) {
1219
- const local = renderRoman(chord, _chunkUADYAYATcjs.majorKey.call(void 0, target.rootPc), {}).roman;
1220
- const numeral = numeralFor(target.degreeNumber, target.lower);
1221
- return {
1222
- roman: `${local}/${numeral}`,
1223
- derivation: { kind: "applied", target: numeral }
1224
- };
1225
- }
1226
- }
1227
- const { degreeNumber, accidental: spelledAccidental } = romanSpelling(chord.rootPc, key);
1228
- const { lower, suffix } = romanStyle(chord.quality);
1229
- const cased = numeralFor(degreeNumber, lower);
1230
- const accidental = seventhDegreeAccidental(spelledAccidental, degreeNumber, chord.quality, key);
1231
- let inversion = 0;
1232
- let bass = "root";
1233
- if (chord.bassPc !== void 0) {
1234
- const idx = chord.intervals.findIndex((iv) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + iv) === chord.bassPc);
1235
- if (idx > 0) {
1236
- inversion = idx;
1237
- } else if (idx < 0) {
1238
- bass = "foreign";
1239
- }
1240
- }
1241
- if (opts.neapolitan === true && inversion === 1 && isNeapolitan(chord, key)) {
1242
- return { roman: NEAPOLITAN_SIXTH, derivation: { kind: "neapolitan" } };
1243
- }
1244
- if (inversion > 0) {
1245
- const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality) ? SEVENTH_FIGURES[inversion] : TRIAD_FIGURE_QUALITIES.has(chord.quality) ? TRIAD_FIGURES[inversion] : void 0;
1246
- if (figure !== void 0) {
1247
- return {
1248
- roman: `${accidental}${cased}${baseMarker(chord.quality)}${figure}`,
1249
- derivation: {
1250
- kind: "degree",
1251
- degreeNumber,
1252
- accidental,
1253
- quality: chord.quality,
1254
- bass: "figured",
1255
- figure
1256
- }
1257
- };
1258
- }
1259
- bass = "unfigurable";
1260
- }
1261
- return {
1262
- roman: `${accidental}${cased}${suffix}`,
1263
- derivation: { kind: "degree", degreeNumber, accidental, quality: chord.quality, bass }
1264
- };
1265
- }
1266
- var DEGREE_NAMES = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh"];
1267
- function describeDegree(degreeNumber, accidental) {
1268
- const name = _nullishCoalesce(DEGREE_NAMES[degreeNumber - 1], () => ( `${degreeNumber}th`));
1269
- if (accidental === "") {
1270
- return `the root is the ${name} degree of the key`;
1271
- }
1272
- const direction = accidental === "b" ? "lowered" : "raised";
1273
- return `the root is chromatic, spelled as the ${name} degree ${direction} a semitone`;
1274
- }
1275
- function describeBass(bass, figure) {
1276
- switch (bass) {
1277
- case "root":
1278
- return "and the chord stands on its own root";
1279
- case "figured":
1280
- return figure === void 0 ? "and the bass is an inversion" : `and the bass is an inversion, figured ${figure}`;
1281
- case "unfigurable":
1282
- return "and the bass names no lossless figure for this quality, so the numeral is written in root position";
1283
- case "foreign":
1284
- return "and the bass is not a chord tone, so the numeral names the upper chord alone";
1285
- }
1286
- }
1287
- var SIX_FOUR_NOTE = "; on the dominant bass this chord is heard as a double appoggiatura over the dominant rather than as a tonic of its own, which only the chords around it settle";
1288
- function describeRoman(roman, derivation) {
1289
- switch (derivation.kind) {
1290
- case "augmentedSixth":
1291
- return `${roman}: an augmented sixth, which no numeral spells, standing on the lowered submediant`;
1292
- case "applied":
1293
- return `${roman}: an applied chord, named in the local key its target degree ${derivation.target} makes a tonic`;
1294
- case "neapolitan":
1295
- return `${roman}: the first-inversion Neapolitan under its figured name, the chord bII6 also spells`;
1296
- case "degree": {
1297
- const text = `${roman}: ${describeDegree(derivation.degreeNumber, derivation.accidental)}, the case and suffix come from the ${derivation.quality} quality, ${describeBass(derivation.bass, derivation.figure)}`;
1298
- const tonicSixFour = derivation.degreeNumber === 1 && derivation.accidental === "" && derivation.figure === "64";
1299
- return tonicSixFour ? `${text}${SIX_FOUR_NOTE}` : text;
1300
- }
1301
- }
1302
- }
1303
- function romanAlternatives(chord, key, opts) {
1304
- const emitted = renderRoman(chord, key, opts).roman;
1305
- const out = [];
1306
- const flippedApplied = renderRoman(chord, key, {
1307
- ...opts,
1308
- applied: opts.applied !== true
1309
- }).roman;
1310
- if (flippedApplied !== emitted) {
1311
- out.push({
1312
- label: flippedApplied,
1313
- reason: opts.applied === true ? "Naming the root against the home key is always a correct spelling; `applied` asked for the tonicizing reading instead" : "Whether a chromatic dominant is genuinely applied is a reading only the caller can make, so the root is named against the home key unless `applied` asks otherwise"
1314
- });
1315
- }
1316
- const flippedNeapolitan = renderRoman(chord, key, {
1317
- ...opts,
1318
- neapolitan: opts.neapolitan !== true
1319
- }).roman;
1320
- if (flippedNeapolitan !== emitted) {
1321
- out.push({
1322
- label: flippedNeapolitan,
1323
- reason: "Both spellings name the same chord, so which one to write is a house-style choice `neapolitan` settles"
1324
- });
1325
- }
1326
- return out;
1327
- }
1328
- function explainRoman(chord, key, opts = {}) {
1329
- const data = _chunkUADYAYATcjs.toChordData.call(void 0, chord);
1330
- const scale = _chunkUADYAYATcjs.toKeyScale.call(void 0, key);
1331
- const { roman, derivation } = renderRoman(data, scale, opts);
1332
- return {
1333
- roman,
1334
- rationale: describeRoman(roman, derivation),
1335
- alternatives: opts.alternatives === true ? romanAlternatives(data, scale, opts) : []
1336
- };
1337
- }
1338
-
1339
- // src/analyze/functional/rationale.ts
1340
- function capitalize(text) {
1341
- return text.charAt(0).toUpperCase() + text.slice(1);
1342
- }
1343
-
1344
- // src/analyze/functional/function.ts
1345
- var FUNCTION_BY_OFFSET = [
1346
- "tonic",
1347
- // 0 I
1348
- "subdominant",
1349
- // 1 bII (Neapolitan)
1350
- "subdominant",
1351
- // 2 ii
1352
- "tonic",
1353
- // 3 bIII
1354
- "tonic",
1355
- // 4 iii
1356
- "subdominant",
1357
- // 5 IV
1358
- "dominant",
1359
- // 6 #IV / bV
1360
- "dominant",
1361
- // 7 V
1362
- "subdominant",
1363
- // 8 bVI
1364
- "tonic",
1365
- // 9 vi
1366
- "subdominant",
1367
- // 10 bVII
1368
- "dominant"
1369
- // 11 vii
1370
- ];
1371
- var MINOR_FUNCTION_BY_OFFSET = [
1372
- "tonic",
1373
- "subdominant",
1374
- "subdominant",
1375
- "tonic",
1376
- "dominant",
1377
- "subdominant",
1378
- "dominant",
1379
- "dominant",
1380
- "tonic",
1381
- "tonic",
1382
- "subdominant",
1383
- "dominant"
1384
- ];
1385
- function isMinorKey(key) {
1386
- return isMinorScale(_chunkUADYAYATcjs.toKeyScale.call(void 0, key));
1387
- }
1388
- function functionOf(chord, key) {
1389
- return functionWithReason(_chunkUADYAYATcjs.toChordData.call(void 0, chord), _chunkUADYAYATcjs.toKeyScale.call(void 0, key)).function;
1390
- }
1391
- function degreeFunction(chord, key) {
1392
- const functions = isMinorScale(key) ? MINOR_FUNCTION_BY_OFFSET : FUNCTION_BY_OFFSET;
1393
- return _nullishCoalesce(functions[_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc)], () => ( "tonic"));
1394
- }
1395
- function functionWithReason(chord, key) {
1396
- const offset = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc);
1397
- if (isNeapolitan(chord, key)) {
1398
- return { function: "subdominant", reason: "neapolitan" };
1399
- }
1400
- if (augmentedSixthKindOf(chord, key) !== null) {
1401
- return { function: "subdominant", reason: "augmentedSixth" };
1402
- }
1403
- if (isAppliedDominant(chord, key)) {
1404
- return { function: "dominant", reason: "applied" };
1405
- }
1406
- if (!isMinorScale(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
1407
- return { function: "subdominant", reason: "flatSideMajor" };
1408
- }
1409
- return { function: degreeFunction(chord, key), reason: "degree" };
1410
- }
1411
- function isAppliedDominant(chord, key) {
1412
- if (isDiatonicChord(chord, key)) {
1413
- return false;
1414
- }
1415
- const root = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc);
1416
- const resolvesTo = (step) => _chunkUADYAYATcjs.isScaleTone.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, root + step), key);
1417
- if (isAppliedDominantSonority(chord)) {
1418
- return chord.quality === "maj" ? resolvesTo(5) : resolvesTo(5) || resolvesTo(11);
1419
- }
1420
- return isDiminishedQuality(chord.quality) && resolvesTo(1);
1421
- }
1422
- var FUNCTION_REASON_PHRASE = {
1423
- neapolitan: "the Neapolitan, an altered predominant on the lowered second degree",
1424
- augmentedSixth: "an augmented sixth, an altered predominant resolving outward onto the dominant",
1425
- applied: "an applied dominant sonority resolving onto a diatonic degree",
1426
- flatSideMajor: "a major triad on the flat side, the borrowed cadence chord"
1427
- };
1428
- var BORROWED_SOURCE_PHRASE = {
1429
- parallelMinor: "borrowed from the parallel minor",
1430
- parallelMajor: "borrowed from the parallel major",
1431
- neapolitan: "belonging to neither parallel mode"
1432
- };
1433
- function describeChord(fn, reason, roman, source) {
1434
- const head = reason === "degree" ? `${capitalize(fn)}: ${roman} takes the ${fn} function of its degree in the key` : `${capitalize(fn)}: ${roman} is ${FUNCTION_REASON_PHRASE[reason]}`;
1435
- return source === null || reason === "neapolitan" ? head : `${head}, ${BORROWED_SOURCE_PHRASE[source]}`;
1436
- }
1437
- function functionAlternatives(chord, key, reason, accepted) {
1438
- const out = [];
1439
- if (reason === "degree") {
1440
- if (isAppliedDominantSonority(chord) && isDiatonicChord(chord, key)) {
1441
- out.push({
1442
- label: "applied dominant",
1443
- reason: "The chord is diatonic to the key, so it keeps the function of its degree rather than tonicizing another"
1444
- });
1445
- }
1446
- } else {
1447
- const byDegree = degreeFunction(chord, key);
1448
- if (byDegree !== accepted) {
1449
- out.push({
1450
- label: `${byDegree} by scale degree`,
1451
- reason: `The degree the root stands on reads as ${byDegree}, but the chord is ${FUNCTION_REASON_PHRASE[reason]}`
1452
- });
1453
- }
1454
- }
1455
- if (augmentedSixthKindOf(chord, key) === "german") {
1456
- out.push({
1457
- label: "tritone substitute",
1458
- reason: "The German sixth sounds a dominant seventh, but the lowered submediant under it fixes the reading as an augmented sixth"
1459
- });
1460
- }
1461
- return out;
1462
- }
1463
- function isDiatonic(chord, key) {
1464
- return isDiatonicChord(_chunkUADYAYATcjs.toChordData.call(void 0, chord), _chunkUADYAYATcjs.toKeyScale.call(void 0, key));
1465
- }
1466
- function parallelKey(key) {
1467
- return parallelScale(_chunkUADYAYATcjs.toKeyScale.call(void 0, key));
1468
- }
1469
- function isDiminishedQuality(quality) {
1470
- return quality === "dim" || quality === "dim7" || quality === "m7b5";
1471
- }
1472
- function analyzeChord(chord, key, opts = {}) {
1473
- const data = _chunkUADYAYATcjs.toChordData.call(void 0, chord);
1474
- const scale = _chunkUADYAYATcjs.toKeyScale.call(void 0, key);
1475
- const source = borrowedSourceOf(data, scale);
1476
- const { function: fn, reason } = functionWithReason(data, scale);
1477
- const roman = renderRoman(data, scale, opts).roman;
1478
- return {
1479
- function: fn,
1480
- borrowed: source !== null,
1481
- source,
1482
- roman,
1483
- rationale: describeChord(fn, reason, roman, source),
1484
- alternatives: opts.alternatives === true ? [
1485
- ...functionAlternatives(data, scale, reason, fn),
1486
- ...romanAlternatives(data, scale, opts)
1487
- ] : []
1488
- };
1489
- }
1490
- function secondaryDominant(targetDegree, key) {
1491
- const scale = _chunkUADYAYATcjs.toKeyScale.call(void 0, key);
1492
- const degreeCount = _chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, scale).length;
1493
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, targetDegree, "targetDegree", 1, degreeCount);
1494
- const targetRoot = degreeRootPc(targetDegree, scale);
1495
- return _chunkUADYAYATcjs.makeChord.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, targetRoot + 7), "dom7");
1496
- }
1497
- function secondaryDominantOf(target) {
1498
- const data = _chunkUADYAYATcjs.toChordData.call(void 0, target);
1499
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, data.rootPc, "target chord rootPc");
1500
- const dominant = _chunkUADYAYATcjs.makeChord.call(void 0, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, data.rootPc + 7), "dom7");
1501
- if (data.rootSpelling !== void 0) {
1502
- const moved = _chunkQ2WGKJNNcjs.transposeNote.call(void 0, data.rootSpelling, 7);
1503
- dominant.rootSpelling = { letter: moved.letter, alter: moved.alter };
1504
- }
1505
- return dominant;
1506
- }
1507
-
1508
- // src/analyze/functional/pivot.ts
1509
- var HEPTATONIC_DEGREES = 7;
1510
- function pivotChords(from, to) {
1511
- return pivotsBetween(_chunkUADYAYATcjs.toKeyScale.call(void 0, from), _chunkUADYAYATcjs.toKeyScale.call(void 0, to));
1512
- }
1513
- function pivotsBetween(from, to) {
1514
- if (_chunkUADYAYATcjs.scaleTonesInDegreeOrder.call(void 0, from).length !== HEPTATONIC_DEGREES) {
1515
- return [];
1516
- }
1517
- const pivots = [];
1518
- const seen = /* @__PURE__ */ new Set();
1519
- for (let degree = 1; degree <= HEPTATONIC_DEGREES; degree += 1) {
1520
- const chord = stackedTriad(degree, from);
1521
- if (chord === null || !isDiatonicChord(chord, to)) {
1522
- continue;
1523
- }
1524
- const identity = `${chord.rootPc}:${chord.quality}`;
1525
- if (seen.has(identity)) {
1526
- continue;
1527
- }
1528
- seen.add(identity);
1529
- pivots.push({
1530
- chord,
1531
- romanFrom: renderRoman(chord, from, {}).roman,
1532
- romanTo: renderRoman(chord, to, {}).roman
1533
- });
1534
- }
1535
- return pivots;
1536
- }
1537
- function stackedTriad(degree, key) {
1538
- try {
1539
- return _chunkUADYAYATcjs.diatonicTriad.call(void 0, degree, key);
1540
- } catch (error) {
1541
- if (error instanceof _chunkQ2WGKJNNcjs.InvalidInputError) {
1542
- return null;
1543
- }
1544
- throw error;
1545
- }
1546
- }
1547
-
1548
-
1549
-
1550
-
1551
-
1552
-
1553
-
1554
-
1555
-
1556
-
1557
-
1558
-
1559
-
1560
-
1561
-
1562
-
1563
-
1564
-
1565
-
1566
-
1567
-
1568
-
1569
-
1570
-
1571
-
1572
-
1573
-
1574
-
1575
-
1576
- exports.MODAL_SCALE_NAMES = MODAL_SCALE_NAMES; exports.resolveKeyProfile = resolveKeyProfile; exports.profileScore = profileScore; exports.detectChord = detectChord; exports.detectChordBest = detectChordBest; exports.detectKey = detectKey; exports.detectKeyFromNotes = detectKeyFromNotes; exports.detectKeyBest = detectKeyBest; exports.augmentedSixthChord = augmentedSixthChord; exports.augmentedSixthKind = augmentedSixthKind; exports.augmentedSixthFromPitchClasses = augmentedSixthFromPitchClasses; exports.spellAugmentedSixth = spellAugmentedSixth; exports.isBorrowedChord = isBorrowedChord; exports.borrowedSource = borrowedSource; exports.isCadentialSixFour = isCadentialSixFour; exports.detectCadence = detectCadence; exports.romanToChord = romanToChord; exports.chordToRoman = chordToRoman; exports.explainRoman = explainRoman; exports.isMinorKey = isMinorKey; exports.functionOf = functionOf; exports.isDiatonic = isDiatonic; exports.parallelKey = parallelKey; exports.analyzeChord = analyzeChord; exports.secondaryDominant = secondaryDominant; exports.secondaryDominantOf = secondaryDominantOf; exports.pivotChords = pivotChords;
1577
- //# sourceMappingURL=chunk-OE6WK2Y2.cjs.map