@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
@@ -0,0 +1,3329 @@
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
+ var _chunk35NBV2FTcjs = require('./chunk-35NBV2FT.cjs');
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+ var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+ var _chunkS5BKZZGMcjs = require('./chunk-S5BKZZGM.cjs');
81
+
82
+ // src/analyze/detect/modes.ts
83
+ var MODAL_CANDIDATES = Object.freeze([
84
+ { scaleName: "dorian", mode: "minor", mask: _chunk3R3QVKY5cjs.DORIAN_MASK, substitutions: [[8, 9]] },
85
+ { scaleName: "phrygian", mode: "minor", mask: _chunk3R3QVKY5cjs.PHRYGIAN_MASK, substitutions: [[2, 1]] },
86
+ { scaleName: "lydian", mode: "major", mask: _chunk3R3QVKY5cjs.LYDIAN_MASK, substitutions: [[5, 6]] },
87
+ { scaleName: "mixolydian", mode: "major", mask: _chunk3R3QVKY5cjs.MIXOLYDIAN_MASK, substitutions: [[11, 10]] },
88
+ {
89
+ scaleName: "locrian",
90
+ mode: "minor",
91
+ mask: _chunk3R3QVKY5cjs.LOCRIAN_MASK,
92
+ substitutions: [
93
+ [2, 1],
94
+ [7, 6]
95
+ ]
96
+ }
97
+ ]);
98
+ var MODAL_SCALE_NAMES = Object.freeze(
99
+ MODAL_CANDIDATES.map((candidate) => candidate.scaleName)
100
+ );
101
+ function modalProfileVector(profile, candidate) {
102
+ const parent = candidate.mode === "major" ? profile.major : profile.minor;
103
+ const vector = [...parent];
104
+ for (const [replaced, replacing] of candidate.substitutions) {
105
+ vector[replacing] = _nullishCoalesce(parent[replaced], () => ( 0));
106
+ vector[replaced] = _nullishCoalesce(parent[replacing], () => ( 0));
107
+ }
108
+ return vector;
109
+ }
110
+ function resolveModalCandidates(modes) {
111
+ if (modes === void 0 || modes === false) {
112
+ return [];
113
+ }
114
+ if (modes === true) {
115
+ return MODAL_CANDIDATES;
116
+ }
117
+ if (!Array.isArray(modes)) {
118
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("modes must be a boolean or an array of mode names");
119
+ }
120
+ const requested = new Set(modes);
121
+ for (const name of requested) {
122
+ if (!MODAL_SCALE_NAMES.some((known) => known === name)) {
123
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
124
+ `unknown mode '${String(name)}'; expected one of ${MODAL_SCALE_NAMES.join(", ")}`
125
+ );
126
+ }
127
+ }
128
+ return MODAL_CANDIDATES.filter((candidate) => requested.has(candidate.scaleName));
129
+ }
130
+
131
+ // src/analyze/adjacency.ts
132
+ var HUMANIZE_ADJACENCY = 0.05;
133
+ function sameInstant(beat, other) {
134
+ return Math.abs(beat - other) <= _chunkS5BKZZGMcjs.BEAT_EPS;
135
+ }
136
+ function adjacent(prevEnd, nextStart) {
137
+ return Math.abs(nextStart - prevEnd) <= HUMANIZE_ADJACENCY;
138
+ }
139
+ function hasEnded(prevEnd, beat) {
140
+ return prevEnd <= beat + HUMANIZE_ADJACENCY;
141
+ }
142
+
143
+ // src/analyze/histogram.ts
144
+ var MAX_VELOCITY = 127;
145
+ var ACCENT_DIVISOR = 3;
146
+ function noteWeight(note, beats, meter) {
147
+ const velocityFactor = note.velocity !== void 0 ? note.velocity / MAX_VELOCITY : 1;
148
+ const accent = meter === void 0 ? 1 : 1 + _chunkS5BKZZGMcjs.metricWeight.call(void 0, note.startBeat, meter) / ACCENT_DIVISOR;
149
+ return beats * velocityFactor * accent;
150
+ }
151
+ function bucketNotesBySlot(notes, grid, slotCount, opts) {
152
+ const buckets = Array.from({ length: slotCount }, () => []);
153
+ let memberships = 0;
154
+ for (const note of notes) {
155
+ const first = Math.max(0, Math.floor((note.startBeat - grid.origin) / grid.slotBeats));
156
+ const lastExclusive = Math.min(
157
+ slotCount,
158
+ Math.ceil((note.startBeat + note.durationBeat - grid.origin) / grid.slotBeats)
159
+ );
160
+ memberships += Math.max(0, lastExclusive - first);
161
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, memberships, opts.name, opts.budget);
162
+ for (let slot = first; slot < lastExclusive; slot += 1) {
163
+ _optionalChain([buckets, 'access', _2 => _2[slot], 'optionalAccess', _3 => _3.push, 'call', _4 => _4(note)]);
164
+ }
165
+ }
166
+ return buckets;
167
+ }
168
+ function windowWeights(notes, windowStart, windowEnd, meter) {
169
+ const weights = new Array(12).fill(0);
170
+ let lowestPitch = Number.POSITIVE_INFINITY;
171
+ for (const note of notes) {
172
+ const noteEnd = note.startBeat + note.durationBeat;
173
+ const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);
174
+ if (overlap <= _chunkS5BKZZGMcjs.BEAT_EPS) {
175
+ continue;
176
+ }
177
+ const onsetInWindow = note.startBeat >= windowStart - _chunkS5BKZZGMcjs.BEAT_EPS && note.startBeat < windowEnd - _chunkS5BKZZGMcjs.BEAT_EPS;
178
+ const pc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, note.pitch);
179
+ const weight = noteWeight(note, overlap, onsetInWindow ? meter : void 0);
180
+ weights[pc] = (_nullishCoalesce(weights[pc], () => ( 0))) + weight;
181
+ if (weight > _chunkS5BKZZGMcjs.BEAT_EPS && note.pitch < lowestPitch) {
182
+ lowestPitch = note.pitch;
183
+ }
184
+ }
185
+ let totalWeight = 0;
186
+ let maxWeight = 0;
187
+ for (const w of weights) {
188
+ totalWeight += w;
189
+ maxWeight = Math.max(maxWeight, w);
190
+ }
191
+ return { weights, totalWeight, maxWeight, lowestPitch };
192
+ }
193
+
194
+ // src/analyze/detect/profiles.ts
195
+ var KRUMHANSL_KESSLER_PROFILE = {
196
+ 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],
197
+ 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]
198
+ };
199
+ var TEMPERLEY_KOSTKA_PAYNE_PROFILE = {
200
+ 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],
201
+ 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]
202
+ };
203
+ var FLAT_SCALE_PROFILE = {
204
+ major: [1.5, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1],
205
+ minor: [1.5, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0]
206
+ };
207
+ var PROFILE_LENGTH = 12;
208
+ var SCORE_QUANTUM = 1e12;
209
+ var NAMED_PROFILES = {
210
+ krumhansl: KRUMHANSL_KESSLER_PROFILE,
211
+ temperley: TEMPERLEY_KOSTKA_PAYNE_PROFILE,
212
+ flat: FLAT_SCALE_PROFILE
213
+ };
214
+ function assertProfileVector(vector, name) {
215
+ if (vector.length !== PROFILE_LENGTH) {
216
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must have ${PROFILE_LENGTH} entries`);
217
+ }
218
+ for (let index = 0; index < vector.length; index += 1) {
219
+ const value = vector[index];
220
+ if (typeof value !== "number" || !Number.isFinite(value)) {
221
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}[${index}] must be a finite number`);
222
+ }
223
+ }
224
+ }
225
+ function resolveKeyProfile(profile) {
226
+ if (profile === void 0) {
227
+ return KRUMHANSL_KESSLER_PROFILE;
228
+ }
229
+ if (typeof profile === "string") {
230
+ const named = NAMED_PROFILES[profile];
231
+ if (named === void 0) {
232
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
233
+ `unknown key profile '${profile}'; expected one of ${Object.keys(NAMED_PROFILES).join(", ")}`
234
+ );
235
+ }
236
+ return named;
237
+ }
238
+ if (typeof profile !== "object" || profile === null) {
239
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("profile must be a profile name or a { major, minor } pair");
240
+ }
241
+ const { major, minor } = profile;
242
+ if (!Array.isArray(major) || !Array.isArray(minor)) {
243
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("profile must carry a major and a minor vector");
244
+ }
245
+ assertProfileVector(major, "profile.major");
246
+ assertProfileVector(minor, "profile.minor");
247
+ return { major, minor };
248
+ }
249
+ function normalizedDot(dist, vector, tonic) {
250
+ let dot = 0;
251
+ let distSquares = 0;
252
+ let profileSquares = 0;
253
+ for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {
254
+ const d = _nullishCoalesce(dist[pc], () => ( 0));
255
+ const p = _nullishCoalesce(vector[(pc - tonic + PROFILE_LENGTH) % PROFILE_LENGTH], () => ( 0));
256
+ dot += d * p;
257
+ distSquares += d * d;
258
+ profileSquares += p * p;
259
+ }
260
+ const norm = Math.sqrt(distSquares * profileSquares);
261
+ return norm > 0 ? dot / norm : 0;
262
+ }
263
+ function profileScore(dist, vector, tonic) {
264
+ let distSum = 0;
265
+ let profileSum = 0;
266
+ for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {
267
+ distSum += _nullishCoalesce(dist[pc], () => ( 0));
268
+ profileSum += _nullishCoalesce(vector[pc], () => ( 0));
269
+ }
270
+ const distMean = distSum / PROFILE_LENGTH;
271
+ const profileMean = profileSum / PROFILE_LENGTH;
272
+ let covariance = 0;
273
+ let distVariance = 0;
274
+ let profileVariance = 0;
275
+ for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {
276
+ const d = (_nullishCoalesce(dist[pc], () => ( 0))) - distMean;
277
+ const p = (_nullishCoalesce(vector[(pc - tonic + PROFILE_LENGTH) % PROFILE_LENGTH], () => ( 0))) - profileMean;
278
+ covariance += d * p;
279
+ distVariance += d * d;
280
+ profileVariance += p * p;
281
+ }
282
+ const denominator = Math.sqrt(distVariance * profileVariance);
283
+ const raw = denominator > 0 ? covariance / denominator : normalizedDot(dist, vector, tonic);
284
+ if (!Number.isFinite(raw)) {
285
+ return 0;
286
+ }
287
+ const clamped = Math.min(1, Math.max(-1, raw));
288
+ return Math.round(clamped * SCORE_QUANTUM) / SCORE_QUANTUM;
289
+ }
290
+
291
+ // src/analyze/detect/index.ts
292
+ function uniquePitchClasses(pitches) {
293
+ return [...new Set(pitches.map(_chunkS5BKZZGMcjs.pitchClassOf))].sort((a, b) => a - b);
294
+ }
295
+ function assertPitches(pitches, name, budget = void 0) {
296
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, pitches, name);
297
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, pitches.length, name, budget);
298
+ for (let index = 0; index < pitches.length; index += 1) {
299
+ _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, _nullishCoalesce(pitches[index], () => ( Number.NaN)), `pitches[${index}]`);
300
+ }
301
+ }
302
+ function detectChord(pitches, opts = {}) {
303
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
304
+ assertPitches(pitches, "chord detection pitches");
305
+ if (asked.bassPc !== void 0) _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, asked.bassPc, "bassPc");
306
+ const input = uniquePitchClasses(pitches);
307
+ if (input.length === 0) {
308
+ return [];
309
+ }
310
+ const inputKind = asked.input === void 0 || asked.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : asked.input;
311
+ const bassPc = asked.bassPc !== void 0 ? _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, asked.bassPc) : inputKind === "midi" ? _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
312
+ const inputSet = new Set(input);
313
+ const matches = [];
314
+ const qualities = _chunk3R3QVKY5cjs.chordQualities.call(void 0, );
315
+ const toneCounts = new Map(
316
+ qualities.map((quality) => [quality, _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, _chunk3R3QVKY5cjs.makeChord.call(void 0, 0, quality)).length])
317
+ );
318
+ const qualityRank = new Map(qualities.map((quality, index) => [quality, index]));
319
+ for (const rootPc of input) {
320
+ for (const quality of qualities) {
321
+ const chord = _chunk3R3QVKY5cjs.makeChord.call(void 0, rootPc, quality);
322
+ const tones = _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord);
323
+ const toneSet = new Set(tones);
324
+ const missingPcs = tones.filter((pc) => !inputSet.has(pc));
325
+ const fifthPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc + 7);
326
+ if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
327
+ continue;
328
+ }
329
+ const extraPcs = input.filter((pc) => !toneSet.has(pc));
330
+ const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc + iv) === bassPc);
331
+ const inversion = bassPc === void 0 || bassIndex < 0 ? null : bassIndex;
332
+ const match = {
333
+ rootPc,
334
+ quality,
335
+ missingPcs,
336
+ extraPcs,
337
+ exact: extraPcs.length === 0 && missingPcs.length === 0,
338
+ inversion
339
+ };
340
+ if (bassPc !== void 0 && bassPc !== rootPc) {
341
+ match.bassPc = bassPc;
342
+ }
343
+ matches.push(match);
344
+ }
345
+ }
346
+ matches.sort((a, b) => {
347
+ if (a.extraPcs.length !== b.extraPcs.length) {
348
+ return a.extraPcs.length - b.extraPcs.length;
349
+ }
350
+ if (a.missingPcs.length !== b.missingPcs.length) {
351
+ return a.missingPcs.length - b.missingPcs.length;
352
+ }
353
+ if (a.inversion === 0 !== (b.inversion === 0)) {
354
+ return a.inversion === 0 ? -1 : 1;
355
+ }
356
+ const aSize = _nullishCoalesce(toneCounts.get(a.quality), () => ( 0));
357
+ const bSize = _nullishCoalesce(toneCounts.get(b.quality), () => ( 0));
358
+ if (aSize !== bSize) {
359
+ return bSize - aSize;
360
+ }
361
+ const aRank = _nullishCoalesce(qualityRank.get(a.quality), () => ( qualities.length));
362
+ const bRank = _nullishCoalesce(qualityRank.get(b.quality), () => ( qualities.length));
363
+ if (aRank !== bRank) {
364
+ return aRank - bRank;
365
+ }
366
+ return a.rootPc - b.rootPc;
367
+ });
368
+ return matches;
369
+ }
370
+ function detectChordBest(pitches, opts = {}) {
371
+ const best = detectChord(pitches, opts)[0];
372
+ if (!best) {
373
+ return null;
374
+ }
375
+ return _chunk3R3QVKY5cjs.makeChord.call(void 0, best.rootPc, best.quality, best.bassPc);
376
+ }
377
+ var MINOR_VARIANTS = [
378
+ { variant: "natural", mask: _chunk3R3QVKY5cjs.NATURAL_MINOR_MASK, scaleName: "naturalMinor" },
379
+ { variant: "harmonic", mask: _chunk3R3QVKY5cjs.HARMONIC_MINOR_MASK, scaleName: "harmonicMinor" },
380
+ { variant: "melodic", mask: _chunk3R3QVKY5cjs.MELODIC_MINOR_MASK, scaleName: "melodicMinor" }
381
+ ];
382
+ var MAJOR_VARIANTS = [
383
+ { variant: "major", mask: _chunk3R3QVKY5cjs.MAJOR_MASK, scaleName: "major" }
384
+ ];
385
+ var SCALE_NAME_ORDER = [
386
+ "major",
387
+ "naturalMinor",
388
+ "harmonicMinor",
389
+ "melodicMinor",
390
+ "dorian",
391
+ "phrygian",
392
+ "lydian",
393
+ "mixolydian",
394
+ "locrian"
395
+ ];
396
+ function scaleNameRank(name) {
397
+ return SCALE_NAME_ORDER.indexOf(name);
398
+ }
399
+ var EXPLAINED_RIVALS = 3;
400
+ var SCORE_DIGITS = 2;
401
+ var MAX_SCORE_DIGITS = 6;
402
+ function quoteScores(rival, winner) {
403
+ let digits = SCORE_DIGITS;
404
+ while (digits < MAX_SCORE_DIGITS && rival.toFixed(digits) === winner.toFixed(digits)) {
405
+ digits += 1;
406
+ }
407
+ return `${rival.toFixed(digits)} against ${winner.toFixed(digits)}`;
408
+ }
409
+ function keyLabel(match) {
410
+ const tonic = _chunkS5BKZZGMcjs.formatNote.call(void 0, _chunk3R3QVKY5cjs.spelledKeyOf.call(void 0, match.key).tonic);
411
+ const scale = match.scaleName.replace(/([A-Z])/g, " $1").toLowerCase();
412
+ return `${tonic} ${scale}`;
413
+ }
414
+ function describeKeyMatch(match, inputSize) {
415
+ const inScale = Math.round(match.fit * inputSize);
416
+ return `${keyLabel(match)}: profile correlation ${match.score.toFixed(SCORE_DIGITS)}, with ${inScale} of ${inputSize} input pitch classes in the scale`;
417
+ }
418
+ function keyRivalReason(winner, rival) {
419
+ if (rival.score !== winner.score) {
420
+ return `Ranked below on profile correlation (${quoteScores(rival.score, winner.score)})`;
421
+ }
422
+ if (rival.fit !== winner.fit) {
423
+ return "Tied on correlation, ranked below on the share of the input its scale covers";
424
+ }
425
+ if (rival.key.rootPc !== winner.key.rootPc) {
426
+ return "Tied on correlation and coverage, ranked below by tonic pitch class";
427
+ }
428
+ 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";
429
+ }
430
+ function explainMatches(results, inputSize) {
431
+ for (const match of results) {
432
+ match.rationale = describeKeyMatch(match, inputSize);
433
+ }
434
+ const winner = results[0];
435
+ if (winner === void 0) {
436
+ return;
437
+ }
438
+ winner.alternatives = results.slice(1, EXPLAINED_RIVALS + 1).map((rival) => ({ label: keyLabel(rival), reason: keyRivalReason(winner, rival) }));
439
+ }
440
+ function detectKey(pitches, opts = {}) {
441
+ const asked = _chunkS5BKZZGMcjs.assertRecord.call(void 0, opts, "opts");
442
+ assertPitches(pitches, "key detection pitches", asked.budget);
443
+ const weights = asked.weights;
444
+ if (weights !== void 0 && weights.length !== pitches.length) {
445
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("weights must have one entry per pitch");
446
+ }
447
+ const profile = resolveKeyProfile(opts.profile);
448
+ const modalCandidates = resolveModalCandidates(opts.modes).map((candidate) => ({
449
+ candidate,
450
+ vector: modalProfileVector(profile, candidate)
451
+ }));
452
+ const counts = /* @__PURE__ */ new Map();
453
+ let total = 0;
454
+ for (let index = 0; index < pitches.length; index += 1) {
455
+ const weight = weights === void 0 ? 1 : _nullishCoalesce(weights[index], () => ( 0));
456
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, weight, `weights[${index}]`);
457
+ if (weight <= 0) {
458
+ continue;
459
+ }
460
+ const pc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _nullishCoalesce(pitches[index], () => ( 0)));
461
+ counts.set(pc, (_nullishCoalesce(counts.get(pc), () => ( 0))) + weight);
462
+ total += weight;
463
+ }
464
+ if (total === 0) {
465
+ return [];
466
+ }
467
+ const input = [...counts.keys()];
468
+ const dist = new Array(12).fill(0);
469
+ for (const [pc, count] of counts) {
470
+ dist[pc] = count;
471
+ }
472
+ const results = [];
473
+ for (let tonic = 0; tonic < 12; tonic += 1) {
474
+ for (const mode of ["major", "minor"]) {
475
+ const vector = mode === "major" ? profile.major : profile.minor;
476
+ const score = profileScore(dist, vector, tonic);
477
+ const variants = mode === "major" ? MAJOR_VARIANTS : MINOR_VARIANTS;
478
+ let covered = Number.NEGATIVE_INFINITY;
479
+ let inScale = 0;
480
+ let best = variants[0];
481
+ for (const candidate of variants) {
482
+ let candidateCovered = 0;
483
+ let candidateInScale = 0;
484
+ for (const [pc, count] of counts) {
485
+ const offset = (pc - tonic + 12) % 12;
486
+ if (candidate.mask >> offset & 1) {
487
+ candidateCovered += count;
488
+ candidateInScale += 1;
489
+ }
490
+ }
491
+ if (candidateCovered > covered) {
492
+ covered = candidateCovered;
493
+ inScale = candidateInScale;
494
+ best = candidate;
495
+ }
496
+ }
497
+ results.push({
498
+ key: { rootPc: tonic, modeMask12: best.mask },
499
+ mode,
500
+ variant: best.variant,
501
+ scaleName: best.scaleName,
502
+ fit: inScale / input.length,
503
+ score
504
+ });
505
+ }
506
+ for (const { candidate, vector } of modalCandidates) {
507
+ let inScale = 0;
508
+ for (const pc of counts.keys()) {
509
+ if (candidate.mask >> (pc - tonic + 12) % 12 & 1) {
510
+ inScale += 1;
511
+ }
512
+ }
513
+ results.push({
514
+ key: { rootPc: tonic, modeMask12: candidate.mask },
515
+ mode: candidate.mode,
516
+ variant: "modal",
517
+ scaleName: candidate.scaleName,
518
+ fit: inScale / input.length,
519
+ score: profileScore(dist, vector, tonic)
520
+ });
521
+ }
522
+ }
523
+ results.sort((a, b) => {
524
+ if (a.score !== b.score) {
525
+ return b.score - a.score;
526
+ }
527
+ if (a.fit !== b.fit) {
528
+ return b.fit - a.fit;
529
+ }
530
+ if (a.key.rootPc !== b.key.rootPc) {
531
+ return a.key.rootPc - b.key.rootPc;
532
+ }
533
+ return scaleNameRank(a.scaleName) - scaleNameRank(b.scaleName);
534
+ });
535
+ if (opts.explain === true) {
536
+ explainMatches(results, input.length);
537
+ }
538
+ return results;
539
+ }
540
+ function detectKeyFromNotes(notes, opts = {}) {
541
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
542
+ _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "key detection notes", {
543
+ allowNonPositiveDuration: true,
544
+ budget: asked.budget
545
+ });
546
+ const sounding = notes.filter((note) => note.durationBeat > 0);
547
+ return detectKey(
548
+ sounding.map((note) => note.pitch),
549
+ {
550
+ weights: sounding.map((note) => noteWeight(note, note.durationBeat)),
551
+ profile: asked.profile,
552
+ modes: asked.modes,
553
+ explain: asked.explain,
554
+ budget: asked.budget
555
+ }
556
+ );
557
+ }
558
+ function detectKeyBest(pitches, opts = {}) {
559
+ return _nullishCoalesce(detectKey(pitches, opts)[0], () => ( null));
560
+ }
561
+
562
+ // src/analyze/functional/internal.ts
563
+ function isMinorScale(key) {
564
+ return _chunk3R3QVKY5cjs.isMinorMask.call(void 0, key.modeMask12);
565
+ }
566
+ function parallelScale(key) {
567
+ return {
568
+ rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc),
569
+ modeMask12: isMinorScale(key) ? _chunk3R3QVKY5cjs.MAJOR_MASK : _chunk3R3QVKY5cjs.NATURAL_MINOR_MASK
570
+ };
571
+ }
572
+ function degreeRootPc(degreeNumber, key) {
573
+ const tones = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, key);
574
+ if (tones.length === 0) {
575
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
576
+ }
577
+ const index = ((degreeNumber - 1) % tones.length + tones.length) % tones.length;
578
+ return _nullishCoalesce(tones[index], () => ( _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc)));
579
+ }
580
+ function loweredDegrees(key) {
581
+ const tones = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, key);
582
+ const out = /* @__PURE__ */ new Set();
583
+ if (tones.length !== 7) {
584
+ return out;
585
+ }
586
+ const major = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc), modeMask12: _chunk3R3QVKY5cjs.MAJOR_MASK });
587
+ for (let index = 0; index < 7; index += 1) {
588
+ const tone = tones[index];
589
+ const majorTone = major[index];
590
+ if (tone !== void 0 && majorTone !== void 0 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, majorTone - tone) === 1) {
591
+ out.add(index + 1);
592
+ }
593
+ }
594
+ return out;
595
+ }
596
+ function romanReference(key) {
597
+ return _chunk3R3QVKY5cjs.heptatonicFrameOf.call(void 0, key);
598
+ }
599
+
600
+ // src/analyze/functional/augmented-sixth.ts
601
+ var KINDS = ["italian", "french", "german"];
602
+ var BASS_ABOVE_TONIC = 8;
603
+ var TONES_ABOVE_BASS = {
604
+ italian: [0, 4, 10],
605
+ french: [0, 4, 6, 10],
606
+ german: [0, 4, 7, 10]
607
+ };
608
+ var SPELLED_ABOVE_BASS = {
609
+ italian: ["P1", "M3", "A6"].map((name) => _chunkS5BKZZGMcjs.parseInterval.call(void 0, name)),
610
+ french: ["P1", "M3", "A4", "A6"].map((name) => _chunkS5BKZZGMcjs.parseInterval.call(void 0, name)),
611
+ german: ["P1", "M3", "P5", "A6"].map((name) => _chunkS5BKZZGMcjs.parseInterval.call(void 0, name))
612
+ };
613
+ var BASS_INTERVAL = _chunkS5BKZZGMcjs.parseInterval.call(void 0, "m6");
614
+ var CHORD_SHAPE = {
615
+ italian: { rootAboveBass: 0, quality: "dom7", intervals: [0, 4, 10] },
616
+ french: { rootAboveBass: 6, quality: "7b5", intervals: [0, 4, 6, 10] },
617
+ german: { rootAboveBass: 0, quality: "dom7", intervals: [0, 4, 7, 10] }
618
+ };
619
+ var KIND_BY_SYMBOL = /* @__PURE__ */ new Map([
620
+ ["It6", "italian"],
621
+ ["Fr6", "french"],
622
+ ["Ger6", "german"],
623
+ // The German sixth's figures over its bass are 6/5; both spellings are in use.
624
+ ["Ger65", "german"]
625
+ ]);
626
+ var SYMBOL_BY_KIND = {
627
+ italian: "It6",
628
+ french: "Fr6",
629
+ german: "Ger6"
630
+ };
631
+ function bassPcOf(key) {
632
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc + BASS_ABOVE_TONIC);
633
+ }
634
+ function bareSpelling(note) {
635
+ return { letter: note.letter, alter: note.alter };
636
+ }
637
+ function spelledInChordOrder(kind, tonic) {
638
+ const tones = spelledFromTonic(kind, tonic);
639
+ const rootIndex = TONES_ABOVE_BASS[kind].indexOf(CHORD_SHAPE[kind].rootAboveBass);
640
+ return [...tones.slice(rootIndex), ...tones.slice(0, rootIndex)];
641
+ }
642
+ function augmentedSixthChord(kind, key) {
643
+ return augmentedSixthOn(kind, key);
644
+ }
645
+ function augmentedSixthOn(kind, keyLike) {
646
+ const checked = _chunkS5BKZZGMcjs.assertOneOf.call(void 0, kind, KINDS, "augmented sixth kind");
647
+ const shape = CHORD_SHAPE[checked];
648
+ const { scale: key, tonic } = _chunk3R3QVKY5cjs.resolveKey.call(void 0, keyLike);
649
+ const bassPc = bassPcOf(key);
650
+ const chord = _chunk3R3QVKY5cjs.makeChord.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, bassPc + shape.rootAboveBass), shape.quality, bassPc);
651
+ chord.intervals = [...shape.intervals];
652
+ const bass = _chunkS5BKZZGMcjs.transposeByInterval.call(void 0, tonic, BASS_INTERVAL);
653
+ const tones = spelledInChordOrder(checked, tonic);
654
+ const [root = bass] = tones;
655
+ chord.rootSpelling = bareSpelling(root);
656
+ chord.bassSpelling = bareSpelling(bass);
657
+ chord.toneSpellings = tones.map(bareSpelling);
658
+ return chord;
659
+ }
660
+ function kindOfPitchClasses(pcs, bass) {
661
+ for (const kind of KINDS) {
662
+ const tones = TONES_ABOVE_BASS[kind];
663
+ if (pcs.size === tones.length && tones.every((tone) => pcs.has(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, bass + tone)))) {
664
+ return kind;
665
+ }
666
+ }
667
+ return null;
668
+ }
669
+ function spellingId(note) {
670
+ return `${_chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, note.letter)}:${note.alter}`;
671
+ }
672
+ function spellsAugmentedSixth(chord, kind, key) {
673
+ const tonic = key.tonic;
674
+ const written = new Set(_chunk3R3QVKY5cjs.spellChord.call(void 0, chord, tonic, key.scale).map(spellingId));
675
+ const tones = spelledFromTonic(kind, tonic).map(spellingId);
676
+ return written.size === tones.length && tones.every((tone) => written.has(tone));
677
+ }
678
+ function augmentedSixthKind(chord, key) {
679
+ return augmentedSixthKindOf(_chunk3R3QVKY5cjs.toChordData.call(void 0, chord), key);
680
+ }
681
+ function augmentedSixthKindOf(chord, keyLike) {
682
+ const resolved = _chunk3R3QVKY5cjs.resolveKey.call(void 0, keyLike);
683
+ const bass = bassPcOf(resolved.scale);
684
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _nullishCoalesce(chord.bassPc, () => ( chord.rootPc))) !== bass) {
685
+ return null;
686
+ }
687
+ const kind = kindOfPitchClasses(new Set(_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord)), bass);
688
+ if (kind === null || !spellsAugmentedSixth(chord, kind, resolved)) {
689
+ return null;
690
+ }
691
+ return kind;
692
+ }
693
+ function augmentedSixthFromPitchClasses(pcs, bassPc, key) {
694
+ return augmentedSixthOverBass(_chunkS5BKZZGMcjs.assertArray.call(void 0, pcs, "pcs"), bassPc, key);
695
+ }
696
+ function augmentedSixthOverBass(pcs, bassPc, keyLike) {
697
+ const resolved = _chunk3R3QVKY5cjs.resolveKey.call(void 0, keyLike);
698
+ const bass = bassPcOf(resolved.scale);
699
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, bassPc) !== bass) {
700
+ return null;
701
+ }
702
+ const kind = kindOfPitchClasses(new Set(pcs.map(_chunkS5BKZZGMcjs.pitchClassOf)), bass);
703
+ return kind === null ? null : augmentedSixthOn(kind, resolved);
704
+ }
705
+ function spellAugmentedSixth(kind, tonic) {
706
+ return spelledFromTonic(kind, _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic));
707
+ }
708
+ function spelledFromTonic(kind, tonic) {
709
+ const checked = _chunkS5BKZZGMcjs.assertOneOf.call(void 0, kind, KINDS, "augmented sixth kind");
710
+ const bass = _chunkS5BKZZGMcjs.transposeByInterval.call(void 0, tonic, BASS_INTERVAL);
711
+ return SPELLED_ABOVE_BASS[checked].map((interval) => _chunkS5BKZZGMcjs.transposeByInterval.call(void 0, bass, interval));
712
+ }
713
+ function augmentedSixthFromSymbol(symbol, key) {
714
+ const kind = KIND_BY_SYMBOL.get(symbol);
715
+ return kind === void 0 ? null : augmentedSixthOn(kind, key);
716
+ }
717
+ function augmentedSixthSymbol(chord, key) {
718
+ const kind = augmentedSixthKindOf(chord, key);
719
+ return kind === null ? null : SYMBOL_BY_KIND[kind];
720
+ }
721
+
722
+ // src/analyze/functional/cadence.ts
723
+ var SUBDOMINANT_DEGREE = 4;
724
+ var DOMINANT_DEGREE = 5;
725
+ var SUBMEDIANT_DEGREE = 6;
726
+ function degreeOffset(degreeNumber, key) {
727
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, degreeRootPc(degreeNumber, romanReference(key)) - key.rootPc);
728
+ }
729
+ function thirdBelongsToKey(chord, key) {
730
+ return chord.intervals.some(
731
+ (interval) => (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, interval) === 3 || _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, interval) === 4) && _chunk3R3QVKY5cjs.isScaleTone.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval), key)
732
+ );
733
+ }
734
+ function deceptiveTargets(key) {
735
+ const submediant = degreeOffset(SUBMEDIANT_DEGREE, key);
736
+ if (isMinorScale(key)) {
737
+ return [submediant];
738
+ }
739
+ const borrowed = degreeOffset(SUBMEDIANT_DEGREE, parallelScale(key));
740
+ return borrowed === submediant ? [submediant] : [submediant, borrowed];
741
+ }
742
+ function restsOnDominant(to, key) {
743
+ if (_chunk3R3QVKY5cjs.hasMajorThird.call(void 0, to) || _chunk3R3QVKY5cjs.isDominantChordOf.call(void 0, to, key)) {
744
+ return true;
745
+ }
746
+ if (_chunk3R3QVKY5cjs.scaleSystemOf.call(void 0, key) !== "modal") {
747
+ return false;
748
+ }
749
+ const dominantPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc + degreeOffset(DOMINANT_DEGREE, key));
750
+ const leadingToneDominant = _chunk3R3QVKY5cjs.isScaleTone.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, dominantPc + 4), key);
751
+ return !leadingToneDominant && thirdBelongsToKey(to, key);
752
+ }
753
+ function isTonicSixFour(chord, key) {
754
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) !== 0 || chord.bassPc === void 0) {
755
+ return false;
756
+ }
757
+ const isTriad = chord.intervals.length === 3 && chord.intervals.some((interval) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, interval) === 7);
758
+ return isTriad && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.bassPc - chord.rootPc) === 7;
759
+ }
760
+ function isCadentialSixFour(chord, next, key) {
761
+ return isTonicSixFour(chord, key) && _chunk3R3QVKY5cjs.isDominantChordOf.call(void 0, next, key) && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _nullishCoalesce(chord.bassPc, () => ( chord.rootPc))) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _nullishCoalesce(next.bassPc, () => ( next.rootPc)));
762
+ }
763
+ function outerPitches(voicing) {
764
+ let bass;
765
+ let soprano;
766
+ for (const pitch of voicing) {
767
+ if (!Number.isFinite(pitch)) {
768
+ continue;
769
+ }
770
+ if (bass === void 0 || pitch < bass) {
771
+ bass = pitch;
772
+ }
773
+ if (soprano === void 0 || pitch > soprano) {
774
+ soprano = pitch;
775
+ }
776
+ }
777
+ return { bass, soprano };
778
+ }
779
+ function bassPcOf2(chord, voicedBass) {
780
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _nullishCoalesce(_nullishCoalesce(voicedBass, () => ( chord.bassPc)), () => ( chord.rootPc)));
781
+ }
782
+ function cadenceType(from, to, key, fromBassPc, toBassPc) {
783
+ const tonic = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
784
+ const fromOffset = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, from.rootPc - tonic);
785
+ const toOffset = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, to.rootPc - tonic);
786
+ const dominant = _chunk3R3QVKY5cjs.isDominantChordOf.call(void 0, from, key);
787
+ if ((dominant || _chunk3R3QVKY5cjs.isLeadingToneChordOf.call(void 0, from, key)) && toOffset === 0) {
788
+ return "authentic";
789
+ }
790
+ if (fromOffset === degreeOffset(SUBDOMINANT_DEGREE, key) && toOffset === 0) {
791
+ return "plagal";
792
+ }
793
+ if (fromOffset === 10 && toOffset === 0 && _chunk3R3QVKY5cjs.hasMajorThird.call(void 0, from)) {
794
+ return "modal";
795
+ }
796
+ if (dominant && deceptiveTargets(key).includes(toOffset)) {
797
+ return "deceptive";
798
+ }
799
+ const dominantOffset = degreeOffset(DOMINANT_DEGREE, key);
800
+ const half = toOffset === dominantOffset && restsOnDominant(to, key) && fromOffset !== dominantOffset;
801
+ if (half && isMinorScale(key) && fromOffset === 5 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, fromBassPc - tonic) === 8 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, toBassPc - tonic) === 7) {
802
+ return "phrygian";
803
+ }
804
+ return half ? "half" : null;
805
+ }
806
+ function authenticStrength(leadingTone, rootPosition, voiced, soprano) {
807
+ if (leadingTone || !rootPosition) {
808
+ return "imperfect";
809
+ }
810
+ if (!voiced) {
811
+ return null;
812
+ }
813
+ return soprano === "root" ? "perfect" : "imperfect";
814
+ }
815
+ function imperfectReason(facts) {
816
+ if (facts.leadingTone) {
817
+ return "the dominant root never sounds under the leading-tone chord";
818
+ }
819
+ if (!facts.rootPosition) {
820
+ return "a chord stands on something other than its own root";
821
+ }
822
+ return "the soprano does not land on the tonic";
823
+ }
824
+ function describeNonCadence(facts) {
825
+ if (facts.fromOffset === facts.toOffset) {
826
+ return "No cadence: the harmony repeats, so there is no root motion to cadence with";
827
+ }
828
+ if (facts.toOffset === 0) {
829
+ return "No cadence: the tonic is approached by none of the chords that cadence onto it";
830
+ }
831
+ if (facts.onFifthDegree) {
832
+ 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";
833
+ }
834
+ return "No cadence: the motion arrives on neither the tonic nor the dominant";
835
+ }
836
+ function describeCadence(facts) {
837
+ let text;
838
+ switch (facts.type) {
839
+ case "authentic": {
840
+ const approach = facts.leadingTone ? "leading-tone chord" : "dominant";
841
+ const head = `Authentic cadence: the ${approach} resolves onto the tonic`;
842
+ 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`;
843
+ break;
844
+ }
845
+ case "plagal":
846
+ text = "Plagal cadence: the subdominant falls onto the tonic";
847
+ break;
848
+ case "modal":
849
+ text = "Modal cadence: the major triad on the subtonic rises onto the tonic with no leading tone";
850
+ break;
851
+ case "deceptive":
852
+ text = "Deceptive cadence: the dominant resolves onto the submediant in place of the tonic";
853
+ break;
854
+ case "phrygian":
855
+ text = "Phrygian cadence: the bass falls a semitone from the lowered submediant onto the dominant, and the motion rests there";
856
+ break;
857
+ case "half":
858
+ text = facts.withinSixFour ? "Half cadence: the motion comes to rest on the dominant, which the tonic six-four standing on its bass was already sounding" : "Half cadence: the motion comes to rest on the dominant";
859
+ break;
860
+ case null:
861
+ text = describeNonCadence(facts);
862
+ break;
863
+ }
864
+ if (facts.evaded) {
865
+ text = `${text}; the tonic arrives inverted, so the arrival the ear was promised is withheld`;
866
+ }
867
+ 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;
868
+ }
869
+ function cadenceAlternatives(facts) {
870
+ const out = [];
871
+ if (facts.type === "deceptive") {
872
+ out.push({
873
+ label: "authentic",
874
+ reason: "The dominant was prepared, but the arrival is the submediant rather than the tonic"
875
+ });
876
+ } else if (facts.type !== "authentic" && facts.toOffset === 0) {
877
+ out.push({
878
+ label: "authentic",
879
+ reason: "The arrival is the tonic, but the chord before it is neither the dominant nor a leading-tone chord"
880
+ });
881
+ }
882
+ if (facts.type === "authentic" && facts.strength !== "perfect") {
883
+ out.push({
884
+ label: "perfect authentic",
885
+ 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)}`
886
+ });
887
+ }
888
+ if (facts.type === "half" && facts.minor && facts.fromOffset === 5) {
889
+ out.push({
890
+ label: "phrygian",
891
+ reason: "The dominant is approached from the subdominant, but the bass does not fall a semitone from the lowered submediant onto it"
892
+ });
893
+ }
894
+ if (facts.type === null && facts.onFifthDegree && facts.fromOffset !== facts.toOffset) {
895
+ out.push({
896
+ label: "half",
897
+ 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"
898
+ });
899
+ }
900
+ return out;
901
+ }
902
+ function detectCadence(from, to, key, opts = {}) {
903
+ const { approach, ...rest } = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
904
+ return cadenceBetween(
905
+ _chunk3R3QVKY5cjs.toChordData.call(void 0, from),
906
+ _chunk3R3QVKY5cjs.toChordData.call(void 0, to),
907
+ _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key),
908
+ approach === void 0 ? rest : { ...rest, approach: _chunk3R3QVKY5cjs.toChordData.call(void 0, approach) }
909
+ );
910
+ }
911
+ function cadenceBetween(from, to, key, opts = {}) {
912
+ const [fromVoicing, toVoicing] = _nullishCoalesce(opts.voicing, () => ( []));
913
+ const fromOuter = outerPitches(_nullishCoalesce(fromVoicing, () => ( [])));
914
+ const toOuter = outerPitches(_nullishCoalesce(toVoicing, () => ( [])));
915
+ const fromBassPc = bassPcOf2(from, fromOuter.bass);
916
+ const toBassPc = bassPcOf2(to, toOuter.bass);
917
+ const toRootPosition = toBassPc === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, to.rootPc);
918
+ const rootPosition = fromBassPc === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, from.rootPc) && toRootPosition;
919
+ const soprano = toOuter.soprano === void 0 ? null : _chunk3R3QVKY5cjs.chordToneRole.call(void 0, toOuter.soprano, to);
920
+ const type = cadenceType(from, to, key, fromBassPc, toBassPc);
921
+ const leadingTone = _chunk3R3QVKY5cjs.isLeadingToneChordOf.call(void 0, from, key);
922
+ const facts = {
923
+ type,
924
+ strength: type === "authentic" ? authenticStrength(leadingTone, rootPosition, toOuter.soprano !== void 0, soprano) : null,
925
+ rootPosition,
926
+ // The dominant resolved, but onto an inverted tonic: the arrival the ear was
927
+ // promised is the one it does not get.
928
+ evaded: type === "authentic" && !toRootPosition,
929
+ leadingTone,
930
+ fromOffset: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, from.rootPc - key.rootPc),
931
+ toOffset: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, to.rootPc - key.rootPc),
932
+ minor: isMinorScale(key),
933
+ onFifthDegree: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, to.rootPc - key.rootPc) === degreeOffset(DOMINANT_DEGREE, key),
934
+ sixFour: opts.approach !== void 0 && isCadentialSixFour(opts.approach, from, key),
935
+ withinSixFour: isCadentialSixFour(from, to, key)
936
+ };
937
+ const result = {
938
+ type: facts.type,
939
+ strength: facts.strength,
940
+ rootPosition: facts.rootPosition,
941
+ evaded: facts.evaded,
942
+ rationale: describeCadence(facts),
943
+ alternatives: opts.alternatives === true ? cadenceAlternatives(facts) : []
944
+ };
945
+ if (soprano !== null) {
946
+ result.soprano = soprano;
947
+ }
948
+ return result;
949
+ }
950
+
951
+ // src/analyze/functional/borrowed.ts
952
+ function isMinorScaleAlteration(chord, key) {
953
+ if (!isMinorScale(key)) return false;
954
+ const alteredKey = { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc), modeMask12: _chunk3R3QVKY5cjs.HARMONIC_MINOR_MASK };
955
+ if (_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, alteredKey)) return true;
956
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 11 && _chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc), modeMask12: _chunk3R3QVKY5cjs.MELODIC_MINOR_MASK });
957
+ }
958
+ function isBorrowed(chord, key) {
959
+ if (_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, key)) {
960
+ return false;
961
+ }
962
+ if (_chunk3R3QVKY5cjs.isNeapolitanChordOf.call(void 0, chord, key)) {
963
+ return true;
964
+ }
965
+ if (isMinorScaleAlteration(chord, key)) {
966
+ return false;
967
+ }
968
+ return _chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, parallelScale(key));
969
+ }
970
+ function borrowedSourceOf(chord, key) {
971
+ if (_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, key)) {
972
+ return null;
973
+ }
974
+ if (_chunk3R3QVKY5cjs.isNeapolitanChordOf.call(void 0, chord, key)) return "neapolitan";
975
+ if (isBorrowed(chord, key)) {
976
+ return isMinorScale(key) ? "parallelMajor" : "parallelMinor";
977
+ }
978
+ return null;
979
+ }
980
+ function isBorrowedChord(chord, key) {
981
+ return isBorrowed(_chunk3R3QVKY5cjs.toChordData.call(void 0, chord), _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key));
982
+ }
983
+ function borrowedSource(chord, key) {
984
+ return borrowedSourceOf(_chunk3R3QVKY5cjs.toChordData.call(void 0, chord), _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key));
985
+ }
986
+
987
+ // src/analyze/functional/tonicization.ts
988
+ function isTonicizableRoot(rootPc, key) {
989
+ return _chunk3R3QVKY5cjs.tonicizableDegrees.call(void 0, key).some((degree) => degree.rootPc === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc));
990
+ }
991
+ function appliedTarget(chord, key) {
992
+ const dominant = _chunk3R3QVKY5cjs.hasDominantSonority.call(void 0, chord);
993
+ const leadingTone = _chunk3R3QVKY5cjs.LEADING_TONE_QUALITIES.has(chord.quality);
994
+ if (!dominant && !leadingTone) {
995
+ return null;
996
+ }
997
+ return _chunk3R3QVKY5cjs.appliedDominantTarget.call(void 0, chord.rootPc + (dominant ? 5 : 1), key);
998
+ }
999
+ function isAppliedDominant(chord, key) {
1000
+ if (_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, key)) {
1001
+ return false;
1002
+ }
1003
+ const source = borrowedSourceOf(chord, key);
1004
+ if (source === "parallelMajor" || source === "parallelMinor") {
1005
+ return false;
1006
+ }
1007
+ return pointsAtTonicizableDegree(chord, key);
1008
+ }
1009
+ function pointsAtTonicizableDegree(chord, key) {
1010
+ if (appliedTarget(chord, key) !== null) {
1011
+ return true;
1012
+ }
1013
+ return _chunk3R3QVKY5cjs.soundsDominantSeventh.call(void 0, chord) && isTonicizableRoot(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - 1), key);
1014
+ }
1015
+
1016
+ // src/analyze/functional/roman.ts
1017
+ var ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII"];
1018
+ var ROMAN_TO_DEGREE = {
1019
+ I: 1,
1020
+ II: 2,
1021
+ III: 3,
1022
+ IV: 4,
1023
+ V: 5,
1024
+ VI: 6,
1025
+ VII: 7
1026
+ };
1027
+ var OFFSET_SPELLING = [
1028
+ [1, ""],
1029
+ [2, "b"],
1030
+ [2, ""],
1031
+ [3, "b"],
1032
+ [3, ""],
1033
+ [4, ""],
1034
+ [4, "#"],
1035
+ [5, ""],
1036
+ [6, "b"],
1037
+ [6, ""],
1038
+ [7, "b"],
1039
+ [7, ""]
1040
+ ];
1041
+ function seventhQuality(base, halfDim) {
1042
+ if (halfDim) {
1043
+ return "m7b5";
1044
+ }
1045
+ if (base === "dim") {
1046
+ return "dim7";
1047
+ }
1048
+ if (base === "aug") {
1049
+ return "aug7";
1050
+ }
1051
+ return base === "maj" ? "dom7" : "min7";
1052
+ }
1053
+ var INVERSION_FIGURES = /* @__PURE__ */ new Set(["6", "64", "65", "43", "42", "2", "7"]);
1054
+ var ROMAN_STYLE = {
1055
+ maj: { lower: false, suffix: "" },
1056
+ min: { lower: true, suffix: "" },
1057
+ dim: { lower: true, suffix: "o" },
1058
+ aug: { lower: false, suffix: "+" },
1059
+ maj7: { lower: false, suffix: "maj7" },
1060
+ min7: { lower: true, suffix: "7" },
1061
+ dom7: { lower: false, suffix: "7" },
1062
+ dim7: { lower: true, suffix: "o7" },
1063
+ m7b5: { lower: true, suffix: "\xF87" },
1064
+ minMaj7: { lower: true, suffix: "maj7" },
1065
+ minMaj9: { lower: true, suffix: "maj9" },
1066
+ minMaj11: { lower: true, suffix: "maj11" },
1067
+ minMaj13: { lower: true, suffix: "maj13" },
1068
+ aug7: { lower: false, suffix: "+7" },
1069
+ augMaj7: { lower: false, suffix: "+maj7" },
1070
+ majb5: { lower: false, suffix: "b5" },
1071
+ "6": { lower: false, suffix: "add6" },
1072
+ min6: { lower: true, suffix: "add6" },
1073
+ "6/9": { lower: false, suffix: "69" },
1074
+ sus2: { lower: false, suffix: "sus2" },
1075
+ sus4: { lower: false, suffix: "sus4" },
1076
+ add9: { lower: false, suffix: "add9" },
1077
+ add11: { lower: false, suffix: "add11" },
1078
+ maj9: { lower: false, suffix: "maj9" },
1079
+ min9: { lower: true, suffix: "9" },
1080
+ dom9: { lower: false, suffix: "9" },
1081
+ "7b9": { lower: false, suffix: "7b9" },
1082
+ "7#9": { lower: false, suffix: "7#9" },
1083
+ "7#11": { lower: false, suffix: "7#11" },
1084
+ "7b13": { lower: false, suffix: "7b13" },
1085
+ "11": { lower: false, suffix: "11" },
1086
+ "13": { lower: false, suffix: "13" },
1087
+ "5": { lower: false, suffix: "5" },
1088
+ "7sus4": { lower: false, suffix: "7sus4" },
1089
+ "7b5": { lower: false, suffix: "7b5" },
1090
+ "7alt": { lower: false, suffix: "7alt" },
1091
+ "13b9": { lower: false, suffix: "13b9" },
1092
+ maj13: { lower: false, suffix: "maj13" },
1093
+ "maj7#11": { lower: false, suffix: "maj7#11" },
1094
+ min11: { lower: true, suffix: "11" },
1095
+ min13: { lower: true, suffix: "13" },
1096
+ minAdd9: { lower: true, suffix: "add9" },
1097
+ "min6/9": { lower: true, suffix: "69" }
1098
+ };
1099
+ var SUFFIX_QUALITY = new Map(
1100
+ Object.entries(ROMAN_STYLE).map(
1101
+ ([quality, style]) => [`${style.lower ? "l" : "u"}:${style.suffix}`, quality]
1102
+ )
1103
+ );
1104
+ function parseInversion(figures) {
1105
+ if (figures === "65") {
1106
+ return { inversion: 1, seventh: true };
1107
+ }
1108
+ if (figures === "43") {
1109
+ return { inversion: 2, seventh: true };
1110
+ }
1111
+ if (figures === "42" || figures === "2") {
1112
+ return { inversion: 3, seventh: true };
1113
+ }
1114
+ if (figures === "7") {
1115
+ return { inversion: 0, seventh: true };
1116
+ }
1117
+ if (figures === "64") {
1118
+ return { inversion: 2, seventh: false };
1119
+ }
1120
+ if (figures === "6") {
1121
+ return { inversion: 1, seventh: false };
1122
+ }
1123
+ return { inversion: 0, seventh: false };
1124
+ }
1125
+ function parseSimpleRoman(text, key) {
1126
+ const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());
1127
+ if (!match) {
1128
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Invalid Roman numeral: ${text}`);
1129
+ }
1130
+ const glyphText = _nullishCoalesce(match[2], () => ( ""));
1131
+ if (glyphText !== glyphText.toUpperCase() && glyphText !== glyphText.toLowerCase()) {
1132
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Roman numeral must use one case: ${text}`);
1133
+ }
1134
+ const accidental = match[1] === "b" ? -1 : match[1] === "#" ? 1 : 0;
1135
+ const glyph = glyphText.toUpperCase();
1136
+ const degreeNumber = ROMAN_TO_DEGREE[glyph];
1137
+ if (degreeNumber === void 0) {
1138
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Invalid Roman numeral: ${text}`);
1139
+ }
1140
+ const isUpper = glyphText[0] === _optionalChain([glyphText, 'access', _5 => _5[0], 'optionalAccess', _6 => _6.toUpperCase, 'call', _7 => _7()]);
1141
+ const suffix = _nullishCoalesce(match[3], () => ( ""));
1142
+ const frame = romanReference(key);
1143
+ const redundantFlat = accidental === -1 && loweredDegrees(frame).has(degreeNumber);
1144
+ const diatonicRootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0,
1145
+ degreeRootPc(degreeNumber, frame) + (redundantFlat ? 0 : accidental)
1146
+ );
1147
+ const rootForQuality = (quality2) => accidental === 0 && degreeNumber === 7 && loweredDegrees(frame).has(7) && _chunk3R3QVKY5cjs.LEADING_TONE_QUALITIES.has(quality2) ? _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, diatonicRootPc + 1) : diatonicRootPc;
1148
+ const canonical = SUFFIX_QUALITY.get(`${isUpper ? "u" : "l"}:${suffix}`);
1149
+ if (canonical !== void 0) {
1150
+ return { rootPc: rootForQuality(canonical), quality: canonical, inversion: 0 };
1151
+ }
1152
+ const parsedSuffix = /^(o|°|dim|ø|\+|aug)?(maj7|M7)?(65|64|43|42|7|6|2)?$/.exec(suffix);
1153
+ if (!parsedSuffix) {
1154
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unsupported suffix "${suffix}" in Roman numeral: ${text}`);
1155
+ }
1156
+ const qualityMarker = _nullishCoalesce(parsedSuffix[1], () => ( ""));
1157
+ const isDim = qualityMarker === "o" || qualityMarker === "\xB0" || qualityMarker === "dim";
1158
+ const isAug = qualityMarker === "+" || qualityMarker === "aug";
1159
+ const isHalfDim = qualityMarker === "\xF8";
1160
+ const explicitMaj7 = parsedSuffix[2] !== void 0;
1161
+ const figures = _nullishCoalesce(parsedSuffix[3], () => ( ""));
1162
+ if (figures !== "" && !INVERSION_FIGURES.has(figures)) {
1163
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1164
+ `Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`
1165
+ );
1166
+ }
1167
+ const { inversion, seventh } = parseInversion(figures);
1168
+ const hasSeventh = explicitMaj7 || seventh || isHalfDim;
1169
+ let base;
1170
+ if (isDim || isHalfDim) {
1171
+ base = "dim";
1172
+ } else if (isAug) {
1173
+ base = "aug";
1174
+ } else {
1175
+ base = isUpper ? "maj" : "min";
1176
+ }
1177
+ let quality;
1178
+ if (explicitMaj7) {
1179
+ quality = base === "min" ? "minMaj7" : base === "aug" ? "augMaj7" : "maj7";
1180
+ } else if (hasSeventh) {
1181
+ quality = seventhQuality(base, isHalfDim);
1182
+ } else if (base === "dim") {
1183
+ quality = "dim";
1184
+ } else if (base === "aug") {
1185
+ quality = "aug";
1186
+ } else {
1187
+ quality = base;
1188
+ }
1189
+ return { rootPc: rootForQuality(quality), quality, inversion };
1190
+ }
1191
+ var NEAPOLITAN_SIXTH = "N6";
1192
+ function chromaticSymbolChord(text, key) {
1193
+ if (text === NEAPOLITAN_SIXTH) {
1194
+ const rootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.scale.rootPc + 1);
1195
+ return _chunk3R3QVKY5cjs.makeChord.call(void 0, rootPc, "maj", _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc + 4));
1196
+ }
1197
+ return augmentedSixthFromSymbol(text, key);
1198
+ }
1199
+ function tonicizedKey(targetRoot, key) {
1200
+ return _chunk3R3QVKY5cjs.resolveKey.call(void 0, {
1201
+ scale: _chunk3R3QVKY5cjs.majorKey.call(void 0, targetRoot),
1202
+ tonic: _chunk3R3QVKY5cjs.spellPitchClass.call(void 0, targetRoot, key.tonic, key.scale)
1203
+ });
1204
+ }
1205
+ function augmentedSixthTarget(chord, key) {
1206
+ if (_chunk3R3QVKY5cjs.chordToneSpellings.call(void 0, chord) === void 0) {
1207
+ return null;
1208
+ }
1209
+ for (const degree of _chunk3R3QVKY5cjs.tonicizableDegrees.call(void 0, key.scale)) {
1210
+ if (degree.degreeNumber === 1) {
1211
+ continue;
1212
+ }
1213
+ if (augmentedSixthSymbol(chord, tonicizedKey(degree.rootPc, key)) !== null) {
1214
+ return degree;
1215
+ }
1216
+ }
1217
+ return null;
1218
+ }
1219
+ function chordFromParsed(parsed) {
1220
+ const chord = _chunk3R3QVKY5cjs.makeChord.call(void 0, parsed.rootPc, parsed.quality);
1221
+ if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {
1222
+ chord.bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, parsed.rootPc + (_nullishCoalesce(chord.intervals[parsed.inversion], () => ( 0))));
1223
+ }
1224
+ return chord;
1225
+ }
1226
+ function romanToChord(text, key) {
1227
+ const resolved = _chunk3R3QVKY5cjs.resolveKey.call(void 0, key);
1228
+ const scale = resolved.scale;
1229
+ const trimmed = text.trim().replace(/(\d)\/(?=\d)/g, "$1");
1230
+ const slash = trimmed.indexOf("/");
1231
+ if (slash >= 0) {
1232
+ const applied = trimmed.slice(0, slash);
1233
+ const target = trimmed.slice(slash + 1);
1234
+ const targetRoot = parseSimpleRoman(target, scale).rootPc;
1235
+ const localKey = tonicizedKey(targetRoot, resolved);
1236
+ return _nullishCoalesce(chromaticSymbolChord(applied, localKey), () => ( chordFromParsed(parseSimpleRoman(applied, localKey.scale))));
1237
+ }
1238
+ return _nullishCoalesce(chromaticSymbolChord(trimmed, resolved), () => ( chordFromParsed(parseSimpleRoman(trimmed, scale))));
1239
+ }
1240
+ function romanStyle(quality) {
1241
+ return ROMAN_STYLE[quality];
1242
+ }
1243
+ function romanSpelling(rootPc, key) {
1244
+ const tones = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, romanReference(key));
1245
+ const diatonic = tones.indexOf(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc));
1246
+ if (diatonic >= 0) {
1247
+ return { degreeNumber: diatonic + 1, accidental: "" };
1248
+ }
1249
+ const lowered = loweredDegrees(romanReference(key));
1250
+ for (let i = 0; i < tones.length; i += 1) {
1251
+ if (lowered.has(i + 1) && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[i], () => ( 0))) + 1) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc)) {
1252
+ return { degreeNumber: i + 1, accidental: "#" };
1253
+ }
1254
+ }
1255
+ for (let i = 0; i < tones.length; i += 1) {
1256
+ const degreeNumber2 = i + 1;
1257
+ if (degreeNumber2 === 1 || degreeNumber2 === 5) {
1258
+ continue;
1259
+ }
1260
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[i], () => ( 0))) - 1) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc)) {
1261
+ return { degreeNumber: degreeNumber2, accidental: "b" };
1262
+ }
1263
+ }
1264
+ for (let i = 0; i < tones.length; i += 1) {
1265
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[i], () => ( 0))) + 1) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc)) {
1266
+ return { degreeNumber: i + 1, accidental: "#" };
1267
+ }
1268
+ }
1269
+ const [degreeNumber, accidental] = _nullishCoalesce(OFFSET_SPELLING[_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc - key.rootPc)], () => ( [1, ""]));
1270
+ return { degreeNumber, accidental };
1271
+ }
1272
+ function seventhDegreeAccidental(accidental, degreeNumber, quality, key) {
1273
+ if (degreeNumber !== 7 || !_chunk3R3QVKY5cjs.LEADING_TONE_QUALITIES.has(quality) || !loweredDegrees(romanReference(key)).has(7)) {
1274
+ return accidental;
1275
+ }
1276
+ if (accidental === "#") {
1277
+ return "";
1278
+ }
1279
+ return accidental === "" ? "b" : accidental;
1280
+ }
1281
+ function baseMarker(quality) {
1282
+ if (quality === "dim" || quality === "dim7") {
1283
+ return "o";
1284
+ }
1285
+ if (quality === "m7b5") {
1286
+ return "\xF8";
1287
+ }
1288
+ if (quality === "aug" || quality === "aug7") {
1289
+ return "+";
1290
+ }
1291
+ if (quality === "augMaj7") {
1292
+ return "+maj7";
1293
+ }
1294
+ if (quality === "maj7" || quality === "minMaj7") {
1295
+ return "maj7";
1296
+ }
1297
+ return "";
1298
+ }
1299
+ var TRIAD_FIGURES = { 1: "6", 2: "64" };
1300
+ var SEVENTH_FIGURES = { 1: "65", 2: "43", 3: "42" };
1301
+ var TRIAD_FIGURE_QUALITIES = /* @__PURE__ */ new Set(["maj", "min", "dim", "aug"]);
1302
+ var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
1303
+ "maj7",
1304
+ "min7",
1305
+ "dom7",
1306
+ "dim7",
1307
+ "m7b5",
1308
+ "minMaj7",
1309
+ "aug7",
1310
+ "augMaj7"
1311
+ ]);
1312
+ function numeralFor(degreeNumber, lower) {
1313
+ const glyph = ROMAN[degreeNumber - 1];
1314
+ if (glyph === void 0) {
1315
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`no Roman numeral for scale degree ${degreeNumber}`);
1316
+ }
1317
+ return lower ? glyph.toLowerCase() : glyph;
1318
+ }
1319
+ function chordToRoman(chord, key, opts = {}) {
1320
+ return renderRoman(_chunk3R3QVKY5cjs.toChordData.call(void 0, chord), key, _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts")).roman;
1321
+ }
1322
+ function renderRoman(chord, keyLike, opts) {
1323
+ const resolved = _chunk3R3QVKY5cjs.resolveKey.call(void 0, keyLike);
1324
+ const key = resolved.scale;
1325
+ const augmented = augmentedSixthSymbol(chord, resolved);
1326
+ if (augmented !== null) {
1327
+ return { roman: augmented, derivation: { kind: "augmentedSixth" } };
1328
+ }
1329
+ if (opts.applied === true) {
1330
+ const target = _nullishCoalesce(augmentedSixthTarget(chord, resolved), () => ( (_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, key) ? null : appliedTarget(chord, key))));
1331
+ if (target !== null) {
1332
+ const local = renderRoman(chord, tonicizedKey(target.rootPc, resolved), {}).roman;
1333
+ const numeral = numeralFor(target.degreeNumber, target.lower);
1334
+ return {
1335
+ roman: `${local}/${numeral}`,
1336
+ derivation: { kind: "applied", target: numeral }
1337
+ };
1338
+ }
1339
+ }
1340
+ const { degreeNumber, accidental: spelledAccidental } = romanSpelling(chord.rootPc, key);
1341
+ const { lower, suffix } = romanStyle(chord.quality);
1342
+ const cased = numeralFor(degreeNumber, lower);
1343
+ const accidental = seventhDegreeAccidental(spelledAccidental, degreeNumber, chord.quality, key);
1344
+ let inversion = 0;
1345
+ let bass = "root";
1346
+ if (chord.bassPc !== void 0) {
1347
+ const idx = chord.intervals.findIndex((iv) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + iv) === chord.bassPc);
1348
+ if (idx > 0) {
1349
+ inversion = idx;
1350
+ } else if (idx < 0) {
1351
+ bass = "foreign";
1352
+ }
1353
+ }
1354
+ if (opts.neapolitan === true && inversion === 1 && _chunk3R3QVKY5cjs.isNeapolitanChordOf.call(void 0, chord, key)) {
1355
+ return { roman: NEAPOLITAN_SIXTH, derivation: { kind: "neapolitan" } };
1356
+ }
1357
+ if (inversion > 0) {
1358
+ const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality) ? SEVENTH_FIGURES[inversion] : TRIAD_FIGURE_QUALITIES.has(chord.quality) ? TRIAD_FIGURES[inversion] : void 0;
1359
+ if (figure !== void 0) {
1360
+ return {
1361
+ roman: `${accidental}${cased}${baseMarker(chord.quality)}${figure}`,
1362
+ derivation: {
1363
+ kind: "degree",
1364
+ degreeNumber,
1365
+ accidental,
1366
+ literalAccidental: spelledAccidental,
1367
+ quality: chord.quality,
1368
+ bass: "figured",
1369
+ figure
1370
+ }
1371
+ };
1372
+ }
1373
+ bass = "unfigurable";
1374
+ }
1375
+ return {
1376
+ roman: `${accidental}${cased}${suffix}`,
1377
+ derivation: {
1378
+ kind: "degree",
1379
+ degreeNumber,
1380
+ accidental,
1381
+ literalAccidental: spelledAccidental,
1382
+ quality: chord.quality,
1383
+ bass
1384
+ }
1385
+ };
1386
+ }
1387
+ var DEGREE_NAMES = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh"];
1388
+ function describeDegree(degreeNumber, accidental, literal) {
1389
+ const name = _nullishCoalesce(DEGREE_NAMES[degreeNumber - 1], () => ( `${degreeNumber}th`));
1390
+ if (accidental !== literal) {
1391
+ return literal === "" ? `the root is the ${name} degree of the key, written with a flat because the bare numeral is reserved by convention for the raised leading tone above it` : `the root is the raised leading tone, a semitone above the ${name} degree of the key, which the bare numeral denotes by convention`;
1392
+ }
1393
+ if (literal === "") {
1394
+ return `the root is the ${name} degree of the key`;
1395
+ }
1396
+ const direction = literal === "b" ? "lowered" : "raised";
1397
+ return `the root is chromatic, spelled as the ${name} degree ${direction} a semitone`;
1398
+ }
1399
+ function describeBass(bass, figure) {
1400
+ switch (bass) {
1401
+ case "root":
1402
+ return "and the chord stands on its own root";
1403
+ case "figured":
1404
+ return figure === void 0 ? "and the bass is an inversion" : `and the bass is an inversion, figured ${figure}`;
1405
+ case "unfigurable":
1406
+ return "and the bass names no lossless figure for this quality, so the numeral is written in root position";
1407
+ case "foreign":
1408
+ return "and the bass is not a chord tone, so the numeral names the upper chord alone";
1409
+ }
1410
+ }
1411
+ 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";
1412
+ function describeRoman(roman, derivation) {
1413
+ switch (derivation.kind) {
1414
+ case "augmentedSixth":
1415
+ return `${roman}: an augmented sixth, which no numeral spells, standing on the lowered submediant`;
1416
+ case "applied":
1417
+ return `${roman}: an applied chord, named in the local key its target degree ${derivation.target} makes a tonic`;
1418
+ case "neapolitan":
1419
+ return `${roman}: the first-inversion Neapolitan under its figured name, the chord bII6 also spells`;
1420
+ case "degree": {
1421
+ const text = `${roman}: ${describeDegree(derivation.degreeNumber, derivation.accidental, derivation.literalAccidental)}, the case and suffix come from the ${derivation.quality} quality, ${describeBass(derivation.bass, derivation.figure)}`;
1422
+ const tonicSixFour = derivation.degreeNumber === 1 && derivation.accidental === "" && derivation.figure === "64";
1423
+ return tonicSixFour ? `${text}${SIX_FOUR_NOTE}` : text;
1424
+ }
1425
+ }
1426
+ }
1427
+ function romanAlternatives(chord, key, opts) {
1428
+ const emitted = renderRoman(chord, key, opts).roman;
1429
+ const out = [];
1430
+ const flippedApplied = renderRoman(chord, key, {
1431
+ ...opts,
1432
+ applied: opts.applied !== true
1433
+ }).roman;
1434
+ if (flippedApplied !== emitted) {
1435
+ out.push({
1436
+ label: flippedApplied,
1437
+ 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"
1438
+ });
1439
+ }
1440
+ const flippedNeapolitan = renderRoman(chord, key, {
1441
+ ...opts,
1442
+ neapolitan: opts.neapolitan !== true
1443
+ }).roman;
1444
+ if (flippedNeapolitan !== emitted) {
1445
+ out.push({
1446
+ label: flippedNeapolitan,
1447
+ reason: "Both spellings name the same chord, so which one to write is a house-style choice `neapolitan` settles"
1448
+ });
1449
+ }
1450
+ return out;
1451
+ }
1452
+ function explainRoman(chord, key, opts = {}) {
1453
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
1454
+ const data = _chunk3R3QVKY5cjs.toChordData.call(void 0, chord);
1455
+ const { roman, derivation } = renderRoman(data, key, asked);
1456
+ return {
1457
+ roman,
1458
+ rationale: describeRoman(roman, derivation),
1459
+ alternatives: asked.alternatives === true ? romanAlternatives(data, key, asked) : []
1460
+ };
1461
+ }
1462
+
1463
+ // src/analyze/functional/rationale.ts
1464
+ function capitalize(text) {
1465
+ return text.charAt(0).toUpperCase() + text.slice(1);
1466
+ }
1467
+
1468
+ // src/analyze/functional/function.ts
1469
+ var FUNCTION_BY_OFFSET = [
1470
+ "tonic",
1471
+ // 0 I
1472
+ "subdominant",
1473
+ // 1 bII (Neapolitan)
1474
+ "subdominant",
1475
+ // 2 ii
1476
+ "tonic",
1477
+ // 3 bIII
1478
+ "tonic",
1479
+ // 4 iii
1480
+ "subdominant",
1481
+ // 5 IV
1482
+ "dominant",
1483
+ // 6 #IV / bV
1484
+ "dominant",
1485
+ // 7 V
1486
+ "subdominant",
1487
+ // 8 bVI
1488
+ "tonic",
1489
+ // 9 vi
1490
+ "subdominant",
1491
+ // 10 bVII
1492
+ "dominant"
1493
+ // 11 vii
1494
+ ];
1495
+ var MINOR_FUNCTION_BY_OFFSET = [
1496
+ "tonic",
1497
+ // 0 i
1498
+ "subdominant",
1499
+ // 1 bII (Neapolitan)
1500
+ "subdominant",
1501
+ // 2 iio
1502
+ "tonic",
1503
+ // 3 III
1504
+ "tonic",
1505
+ // 4 #III, the chromatic mediant
1506
+ "subdominant",
1507
+ // 5 iv
1508
+ "dominant",
1509
+ // 6 #iv / bv
1510
+ "dominant",
1511
+ // 7 V
1512
+ "tonic",
1513
+ // 8 VI
1514
+ "tonic",
1515
+ // 9 #VI
1516
+ "subdominant",
1517
+ // 10 VII
1518
+ "dominant"
1519
+ // 11 viio
1520
+ ];
1521
+ function isMinorKey(key) {
1522
+ return isMinorScale(_chunk3R3QVKY5cjs.toKeyScale.call(void 0, key));
1523
+ }
1524
+ function functionOf(chord, key) {
1525
+ return functionWithReason(_chunk3R3QVKY5cjs.toChordData.call(void 0, chord), _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key)).function;
1526
+ }
1527
+ function degreeFunction(chord, key) {
1528
+ const functions = isMinorScale(key) ? MINOR_FUNCTION_BY_OFFSET : FUNCTION_BY_OFFSET;
1529
+ return _nullishCoalesce(functions[_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc)], () => ( "tonic"));
1530
+ }
1531
+ function functionWithReason(chord, key) {
1532
+ const offset = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc);
1533
+ if (_chunk3R3QVKY5cjs.isNeapolitanChordOf.call(void 0, chord, key)) {
1534
+ return { function: "subdominant", reason: "neapolitan" };
1535
+ }
1536
+ if (augmentedSixthKindOf(chord, key) !== null) {
1537
+ return { function: "subdominant", reason: "augmentedSixth" };
1538
+ }
1539
+ if (isAppliedDominant(chord, key)) {
1540
+ return { function: "dominant", reason: "applied" };
1541
+ }
1542
+ if (!isMinorScale(key) && !_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, key) && _chunk3R3QVKY5cjs.hasMajorThird.call(void 0, chord) && (offset === 8 || offset === 10)) {
1543
+ return { function: "subdominant", reason: "flatSideMajor" };
1544
+ }
1545
+ return { function: degreeFunction(chord, key), reason: "degree" };
1546
+ }
1547
+ var FUNCTION_REASON_PHRASE = {
1548
+ neapolitan: "the Neapolitan, an altered predominant on the lowered second degree",
1549
+ augmentedSixth: "an augmented sixth, an altered predominant resolving outward onto the dominant",
1550
+ applied: "an applied dominant sonority resolving onto a diatonic degree",
1551
+ flatSideMajor: "a major triad on the flat side, the borrowed cadence chord"
1552
+ };
1553
+ var BORROWED_SOURCE_PHRASE = {
1554
+ parallelMinor: "borrowed from the parallel minor",
1555
+ parallelMajor: "borrowed from the parallel major",
1556
+ neapolitan: "belonging to neither parallel mode"
1557
+ };
1558
+ function describeChord(fn, reason, roman, source) {
1559
+ 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]}`;
1560
+ return source === null || reason === "neapolitan" ? head : `${head}, ${BORROWED_SOURCE_PHRASE[source]}`;
1561
+ }
1562
+ function functionAlternatives(chord, key, reason, accepted) {
1563
+ const out = [];
1564
+ if (reason === "degree") {
1565
+ if (pointsAtTonicizableDegree(chord, key) && _chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, key)) {
1566
+ out.push({
1567
+ label: "applied dominant",
1568
+ reason: "The chord is diatonic to the key, so it keeps the function of its degree rather than tonicizing another"
1569
+ });
1570
+ }
1571
+ } else {
1572
+ const byDegree = degreeFunction(chord, key);
1573
+ if (byDegree !== accepted) {
1574
+ out.push({
1575
+ label: `${byDegree} by scale degree`,
1576
+ reason: `The degree the root stands on reads as ${byDegree}, but the chord is ${FUNCTION_REASON_PHRASE[reason]}`
1577
+ });
1578
+ }
1579
+ }
1580
+ if (augmentedSixthKindOf(chord, key) === "german") {
1581
+ out.push({
1582
+ label: "tritone substitute",
1583
+ reason: "The German sixth sounds a dominant seventh, but the lowered submediant under it fixes the reading as an augmented sixth"
1584
+ });
1585
+ }
1586
+ return out;
1587
+ }
1588
+ function isDiatonic(chord, key) {
1589
+ return _chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, _chunk3R3QVKY5cjs.toChordData.call(void 0, chord), _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key));
1590
+ }
1591
+ function parallelKey(key) {
1592
+ return parallelScale(_chunk3R3QVKY5cjs.toKeyScale.call(void 0, key));
1593
+ }
1594
+ function analyzeChord(chord, key, opts = {}) {
1595
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
1596
+ const data = _chunk3R3QVKY5cjs.toChordData.call(void 0, chord);
1597
+ const scale = _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key);
1598
+ const source = borrowedSourceOf(data, scale);
1599
+ const { function: fn, reason } = functionWithReason(data, scale);
1600
+ const roman = renderRoman(data, scale, asked).roman;
1601
+ return {
1602
+ function: fn,
1603
+ borrowed: source !== null,
1604
+ source,
1605
+ roman,
1606
+ rationale: describeChord(fn, reason, roman, source),
1607
+ alternatives: asked.alternatives === true ? [
1608
+ ...functionAlternatives(data, scale, reason, fn),
1609
+ ...romanAlternatives(data, scale, asked)
1610
+ ] : []
1611
+ };
1612
+ }
1613
+ function secondaryDominant(targetDegree, key) {
1614
+ const scale = _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key);
1615
+ const degreeCount = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, scale).length;
1616
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, targetDegree, "targetDegree", 1, degreeCount);
1617
+ const targetRoot = degreeRootPc(targetDegree, scale);
1618
+ return _chunk3R3QVKY5cjs.makeChord.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, targetRoot + 7), "dom7");
1619
+ }
1620
+ function secondaryDominantOf(target) {
1621
+ const data = _chunk3R3QVKY5cjs.toChordData.call(void 0, target);
1622
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, data.rootPc, "target chord rootPc");
1623
+ const dominant = _chunk3R3QVKY5cjs.makeChord.call(void 0, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, data.rootPc + 7), "dom7");
1624
+ if (data.rootSpelling !== void 0) {
1625
+ const moved = _chunkS5BKZZGMcjs.transposeNote.call(void 0, data.rootSpelling, 7);
1626
+ dominant.rootSpelling = { letter: moved.letter, alter: moved.alter };
1627
+ }
1628
+ return dominant;
1629
+ }
1630
+
1631
+ // src/analyze/functional/pivot.ts
1632
+ var HEPTATONIC_DEGREES = 7;
1633
+ function pivotChords(from, to) {
1634
+ return pivotsBetween(_chunk3R3QVKY5cjs.toKeyScale.call(void 0, from), _chunk3R3QVKY5cjs.toKeyScale.call(void 0, to));
1635
+ }
1636
+ function pivotsBetween(from, to) {
1637
+ if (_chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, from).length !== HEPTATONIC_DEGREES) {
1638
+ return [];
1639
+ }
1640
+ const pivots = [];
1641
+ const seen = /* @__PURE__ */ new Set();
1642
+ for (let degree = 1; degree <= HEPTATONIC_DEGREES; degree += 1) {
1643
+ const chord = stackedTriad(degree, from);
1644
+ if (chord === null || !_chunk3R3QVKY5cjs.isDiatonicChord.call(void 0, chord, to)) {
1645
+ continue;
1646
+ }
1647
+ const identity = `${chord.rootPc}:${chord.quality}`;
1648
+ if (seen.has(identity)) {
1649
+ continue;
1650
+ }
1651
+ seen.add(identity);
1652
+ pivots.push({
1653
+ chord,
1654
+ romanFrom: renderRoman(chord, from, {}).roman,
1655
+ romanTo: renderRoman(chord, to, {}).roman
1656
+ });
1657
+ }
1658
+ return pivots;
1659
+ }
1660
+ function stackedTriad(degree, key) {
1661
+ try {
1662
+ return _chunk3R3QVKY5cjs.diatonicTriad.call(void 0, degree, key);
1663
+ } catch (error) {
1664
+ if (error instanceof _chunkS5BKZZGMcjs.InvalidInputError) {
1665
+ return null;
1666
+ }
1667
+ throw error;
1668
+ }
1669
+ }
1670
+
1671
+ // src/analyze/form/internal.ts
1672
+ function clamp01(value) {
1673
+ return Math.min(1, Math.max(0, value));
1674
+ }
1675
+ function floorMod(value, modulus) {
1676
+ return (value % modulus + modulus) % modulus;
1677
+ }
1678
+ function firstSoundingBeat(notes, fallback = 0) {
1679
+ let first = Number.POSITIVE_INFINITY;
1680
+ for (const note of notes) {
1681
+ first = Math.min(first, note.startBeat);
1682
+ }
1683
+ return Number.isFinite(first) ? first : fallback;
1684
+ }
1685
+ function lastBarOf(meter, spanStart, spanEnd) {
1686
+ const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, spanStart, meter);
1687
+ const bar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, Math.max(spanStart, spanEnd), meter);
1688
+ if (bar > firstBar && _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar, beat: 0 }, meter) >= spanEnd - _chunkS5BKZZGMcjs.BEAT_EPS) {
1689
+ return bar - 1;
1690
+ }
1691
+ return bar;
1692
+ }
1693
+ function sliceBars(notes, meter, spanStart, spanEnd, budget) {
1694
+ const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, spanStart, meter);
1695
+ const lastBar = lastBarOf(meter, spanStart, spanEnd);
1696
+ const barCount = lastBar - firstBar + 1;
1697
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, barCount, "form bars", budget);
1698
+ const buckets = Array.from({ length: barCount }, () => []);
1699
+ let memberships = 0;
1700
+ for (const note of notes) {
1701
+ const noteEnd = note.startBeat + note.durationBeat;
1702
+ const from = Math.max(firstBar, _chunkS5BKZZGMcjs.barIndexAt.call(void 0, note.startBeat, meter));
1703
+ const to = Math.min(lastBar, _chunkS5BKZZGMcjs.barIndexAt.call(void 0, Math.max(note.startBeat, noteEnd - _chunkS5BKZZGMcjs.BEAT_EPS), meter));
1704
+ memberships += Math.max(0, to - from + 1);
1705
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, memberships, "form note-to-bar memberships", budget);
1706
+ for (let bar = from; bar <= to; bar += 1) {
1707
+ _optionalChain([buckets, 'access', _8 => _8[bar - firstBar], 'optionalAccess', _9 => _9.push, 'call', _10 => _10(note)]);
1708
+ }
1709
+ }
1710
+ const slices = [];
1711
+ for (let bar = firstBar; bar <= lastBar; bar += 1) {
1712
+ const startBeat = _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar, beat: 0 }, meter);
1713
+ const endBeat = _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: bar + 1, beat: 0 }, meter);
1714
+ const barNotes = _nullishCoalesce(buckets[bar - firstBar], () => ( []));
1715
+ const { weights, totalWeight } = windowWeights(barNotes, startBeat, endBeat, meter);
1716
+ slices.push({ index: bar, startBeat, endBeat, notes: barNotes, weights, totalWeight });
1717
+ }
1718
+ return slices;
1719
+ }
1720
+ function weightSimilarity(a, b) {
1721
+ let dot = 0;
1722
+ let normA = 0;
1723
+ let normB = 0;
1724
+ for (let i = 0; i < a.length; i += 1) {
1725
+ const x = _nullishCoalesce(a[i], () => ( 0));
1726
+ const y = _nullishCoalesce(b[i], () => ( 0));
1727
+ dot += x * y;
1728
+ normA += x * x;
1729
+ normB += y * y;
1730
+ }
1731
+ if (normA <= _chunkS5BKZZGMcjs.BEAT_EPS && normB <= _chunkS5BKZZGMcjs.BEAT_EPS) {
1732
+ return 1;
1733
+ }
1734
+ if (normA <= _chunkS5BKZZGMcjs.BEAT_EPS || normB <= _chunkS5BKZZGMcjs.BEAT_EPS) {
1735
+ return 0;
1736
+ }
1737
+ return clamp01(dot / Math.sqrt(normA * normB));
1738
+ }
1739
+ function harmonicNovelty(slices, index) {
1740
+ const current = slices[index];
1741
+ if (current === void 0) {
1742
+ return 0;
1743
+ }
1744
+ const previous = slices[index - 1];
1745
+ if (previous === void 0) {
1746
+ return current.index >= 0 ? 1 : 0;
1747
+ }
1748
+ return 1 - weightSimilarity(previous.weights, current.weights);
1749
+ }
1750
+ function combineEvidence(strengths) {
1751
+ let remaining = 1;
1752
+ for (const strength of strengths) {
1753
+ remaining *= 1 - clamp01(strength);
1754
+ }
1755
+ return clamp01(1 - remaining);
1756
+ }
1757
+
1758
+ // src/analyze/grid.ts
1759
+ function gridOriginOf(firstOnset, slotBeats) {
1760
+ const startBeat = firstOnset < 0 && firstOnset > -HUMANIZE_ADJACENCY ? 0 : firstOnset;
1761
+ return { origin: Math.floor(startBeat / slotBeats + _chunkS5BKZZGMcjs.BEAT_EPS) * slotBeats, startBeat };
1762
+ }
1763
+ function gridForNotes(notes, slotBeats) {
1764
+ return gridOriginOf(firstSoundingBeat(notes), slotBeats);
1765
+ }
1766
+ function barGridStart(firstOnset, meter) {
1767
+ return gridOriginOf(firstOnset, _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, firstOnset, meter)).startBeat;
1768
+ }
1769
+
1770
+ // src/analyze/keys/index.ts
1771
+ var PITCH_CLASSES = 12;
1772
+ var EMPTY_WEIGHTS = {
1773
+ weights: new Array(PITCH_CLASSES).fill(0),
1774
+ totalWeight: 0,
1775
+ maxWeight: 0,
1776
+ lowestPitch: Number.POSITIVE_INFINITY
1777
+ };
1778
+ var MAX_METRIC_WEIGHT = 3;
1779
+ var MODULATION_COST = 0.15;
1780
+ var PAST_CONTEXT_SHARE = 0.5;
1781
+ var FUTURE_CONTEXT_SHARE = 0.25;
1782
+ var STRONG_BEAT_DISCOUNT = 0.4;
1783
+ var FIFTHS_DISTANCE_COST = 0.18;
1784
+ var KEY_CANDIDATES = (() => {
1785
+ const candidates = [];
1786
+ for (let rootPc = 0; rootPc < 12; rootPc += 1) {
1787
+ candidates.push({ key: _chunk3R3QVKY5cjs.majorKey.call(void 0, rootPc), rootPc, minor: false });
1788
+ candidates.push({ key: _chunk3R3QVKY5cjs.minorKey.call(void 0, rootPc), rootPc, minor: true });
1789
+ }
1790
+ return candidates;
1791
+ })();
1792
+ function fifthsOf(rootPc, minor) {
1793
+ const majorRoot = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, minor ? rootPc + 3 : rootPc);
1794
+ const raw = majorRoot * 7 % 12;
1795
+ return raw > 6 ? raw - 12 : raw;
1796
+ }
1797
+ function fifthsDistance(a, b) {
1798
+ const diff = Math.abs(a - b) % 12;
1799
+ return Math.min(diff, 12 - diff);
1800
+ }
1801
+ var CANDIDATE_DISTANCE = KEY_CANDIDATES.map(
1802
+ (from) => KEY_CANDIDATES.map(
1803
+ (to) => fifthsDistance(fifthsOf(from.rootPc, from.minor), fifthsOf(to.rootPc, to.minor))
1804
+ )
1805
+ );
1806
+ function chooseKeys(slots, changeCost, meter, budget) {
1807
+ const count = KEY_CANDIDATES.length;
1808
+ if (slots.length === 0) {
1809
+ return [];
1810
+ }
1811
+ const back = _chunkS5BKZZGMcjs.allocateChoiceTable.call(void 0, slots.length, count, "key search choices", budget);
1812
+ let costs = _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0, 1, count, "key search costs", budget);
1813
+ let spare = _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0, 1, count, "key search costs", budget);
1814
+ const opening = _nullishCoalesce(_optionalChain([slots, 'access', _11 => _11[0], 'optionalAccess', _12 => _12.scores]), () => ( []));
1815
+ for (let c = 0; c < count; c += 1) {
1816
+ costs[c] = -(_nullishCoalesce(opening[c], () => ( 0)));
1817
+ }
1818
+ for (let i = 1; i < slots.length; i += 1) {
1819
+ const slot = slots[i];
1820
+ const previous = costs;
1821
+ const next = spare;
1822
+ const accent = _chunkS5BKZZGMcjs.metricWeight.call(void 0, _nullishCoalesce(_optionalChain([slot, 'optionalAccess', _13 => _13.startBeat]), () => ( 0)), meter);
1823
+ const metricFactor = 1 - STRONG_BEAT_DISCOUNT * accent / MAX_METRIC_WEIGHT;
1824
+ const rowBase = i * count;
1825
+ for (let to = 0; to < count; to += 1) {
1826
+ let best2 = _nullishCoalesce(previous[to], () => ( Number.POSITIVE_INFINITY));
1827
+ let bestFrom = to;
1828
+ for (let from = 0; from < count; from += 1) {
1829
+ if (from === to) {
1830
+ continue;
1831
+ }
1832
+ const distance = _nullishCoalesce(_optionalChain([CANDIDATE_DISTANCE, 'access', _14 => _14[from], 'optionalAccess', _15 => _15[to]]), () => ( 0));
1833
+ const penalty = changeCost * metricFactor * (1 + FIFTHS_DISTANCE_COST * distance);
1834
+ const cost = (_nullishCoalesce(previous[from], () => ( Number.POSITIVE_INFINITY))) + penalty;
1835
+ if (cost < best2) {
1836
+ best2 = cost;
1837
+ bestFrom = from;
1838
+ }
1839
+ }
1840
+ back[rowBase + to] = bestFrom;
1841
+ next[to] = best2 - (_nullishCoalesce(_optionalChain([slot, 'optionalAccess', _16 => _16.scores, 'access', _17 => _17[to]]), () => ( 0)));
1842
+ }
1843
+ costs = next;
1844
+ spare = previous;
1845
+ }
1846
+ let best = 0;
1847
+ for (let c = 1; c < count; c += 1) {
1848
+ if ((_nullishCoalesce(costs[c], () => ( Number.POSITIVE_INFINITY))) < (_nullishCoalesce(costs[best], () => ( Number.POSITIVE_INFINITY)))) {
1849
+ best = c;
1850
+ }
1851
+ }
1852
+ const path = new Array(slots.length);
1853
+ let current = best;
1854
+ for (let i = slots.length - 1; i >= 0; i -= 1) {
1855
+ path[i] = current;
1856
+ if (i > 0) {
1857
+ current = _nullishCoalesce(back[i * count + current], () => ( current));
1858
+ }
1859
+ }
1860
+ return path;
1861
+ }
1862
+ function regionsFromPath(slots, path) {
1863
+ const regions = [];
1864
+ for (let i = 0; i < slots.length; i += 1) {
1865
+ const last = regions[regions.length - 1];
1866
+ const slot = slots[i];
1867
+ if (slot === void 0) {
1868
+ continue;
1869
+ }
1870
+ if (last !== void 0 && last.candidate === path[i]) {
1871
+ last.endBeat = slot.endBeat;
1872
+ last.toExclusive = i + 1;
1873
+ continue;
1874
+ }
1875
+ regions.push({
1876
+ startBeat: slot.startBeat,
1877
+ endBeat: slot.endBeat,
1878
+ candidate: _nullishCoalesce(path[i], () => ( 0)),
1879
+ from: i,
1880
+ toExclusive: i + 1
1881
+ });
1882
+ }
1883
+ return regions;
1884
+ }
1885
+ function enforceMinimumRegion(slots, path, minBeats, budget) {
1886
+ const chosen = [...path];
1887
+ const initial = regionsFromPath(slots, chosen);
1888
+ if (initial.length < 2) {
1889
+ return chosen;
1890
+ }
1891
+ const candidates = KEY_CANDIDATES.length;
1892
+ const totals = _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0, slots.length, candidates, "key region scores", budget);
1893
+ for (let i = 0; i < slots.length; i += 1) {
1894
+ const scores = _nullishCoalesce(_optionalChain([slots, 'access', _18 => _18[i], 'optionalAccess', _19 => _19.scores]), () => ( []));
1895
+ const base = i * candidates;
1896
+ const previous = base - candidates;
1897
+ for (let c = 0; c < candidates; c += 1) {
1898
+ totals[base + c] = (i === 0 ? 0 : _nullishCoalesce(totals[previous + c], () => ( 0))) + (_nullishCoalesce(scores[c], () => ( 0)));
1899
+ }
1900
+ }
1901
+ const scoreOver = (region, candidate) => (_nullishCoalesce(totals[(region.toExclusive - 1) * candidates + candidate], () => ( 0))) - (region.from === 0 ? 0 : _nullishCoalesce(totals[(region.from - 1) * candidates + candidate], () => ( 0)));
1902
+ const nodes = initial.map(
1903
+ (region) => ({ ...region, previous: void 0, next: void 0, merged: false })
1904
+ );
1905
+ for (let i = 0; i < nodes.length; i += 1) {
1906
+ const node = nodes[i];
1907
+ if (node === void 0) {
1908
+ continue;
1909
+ }
1910
+ node.previous = nodes[i - 1];
1911
+ node.next = nodes[i + 1];
1912
+ }
1913
+ const pending = new RegionHeap();
1914
+ for (const node of nodes) {
1915
+ pending.push(node);
1916
+ }
1917
+ for (; ; ) {
1918
+ const region = pending.pop();
1919
+ if (region === void 0) {
1920
+ return chosen;
1921
+ }
1922
+ const length = region.endBeat - region.startBeat;
1923
+ if (region.merged || length >= minBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
1924
+ continue;
1925
+ }
1926
+ const previous = region.previous;
1927
+ const next = region.next;
1928
+ const before = previous !== void 0 && Math.abs(previous.endBeat - region.startBeat) < _chunkS5BKZZGMcjs.BEAT_EPS ? previous : void 0;
1929
+ const after = next !== void 0 && Math.abs(next.startBeat - region.endBeat) < _chunkS5BKZZGMcjs.BEAT_EPS ? next : void 0;
1930
+ if (before === void 0 && after === void 0) {
1931
+ continue;
1932
+ }
1933
+ const left = _optionalChain([before, 'optionalAccess', _20 => _20.candidate]);
1934
+ const right = _optionalChain([after, 'optionalAccess', _21 => _21.candidate]);
1935
+ const winner = left === void 0 ? _nullishCoalesce(right, () => ( region.candidate)) : right === void 0 || scoreOver(region, left) >= scoreOver(region, right) ? left : right;
1936
+ for (let i = region.from; i < region.toExclusive; i += 1) {
1937
+ chosen[i] = winner;
1938
+ }
1939
+ region.candidate = winner;
1940
+ let grown = region;
1941
+ if (previous !== void 0 && previous.candidate === winner) {
1942
+ grown = absorbNext(previous);
1943
+ }
1944
+ if (_optionalChain([grown, 'access', _22 => _22.next, 'optionalAccess', _23 => _23.candidate]) === winner) {
1945
+ grown = absorbNext(grown);
1946
+ }
1947
+ pending.push(grown);
1948
+ }
1949
+ }
1950
+ function absorbNext(region) {
1951
+ const next = region.next;
1952
+ if (next === void 0) {
1953
+ return region;
1954
+ }
1955
+ region.endBeat = next.endBeat;
1956
+ region.toExclusive = next.toExclusive;
1957
+ region.next = next.next;
1958
+ if (next.next !== void 0) {
1959
+ next.next.previous = region;
1960
+ }
1961
+ next.merged = true;
1962
+ return region;
1963
+ }
1964
+ var RegionHeap = class _RegionHeap {
1965
+ #entries = [];
1966
+ push(region) {
1967
+ const entries = this.#entries;
1968
+ entries.push({ region, length: region.endBeat - region.startBeat });
1969
+ let index = entries.length - 1;
1970
+ while (index > 0) {
1971
+ const parent = index - 1 >> 1;
1972
+ if (!_RegionHeap.#before(entries[index], entries[parent])) {
1973
+ break;
1974
+ }
1975
+ _RegionHeap.#swap(entries, index, parent);
1976
+ index = parent;
1977
+ }
1978
+ }
1979
+ /** The shortest region still worth merging, skipping entries since spent. */
1980
+ pop() {
1981
+ const entries = this.#entries;
1982
+ for (; ; ) {
1983
+ const top = entries[0];
1984
+ if (top === void 0) {
1985
+ return void 0;
1986
+ }
1987
+ const last = entries.pop();
1988
+ if (entries.length > 0 && last !== void 0) {
1989
+ entries[0] = last;
1990
+ this.#sink();
1991
+ }
1992
+ if (!top.region.merged && top.region.endBeat - top.region.startBeat === top.length) {
1993
+ return top.region;
1994
+ }
1995
+ }
1996
+ }
1997
+ #sink() {
1998
+ const entries = this.#entries;
1999
+ let index = 0;
2000
+ for (; ; ) {
2001
+ const left = index * 2 + 1;
2002
+ const right = left + 1;
2003
+ let smallest = index;
2004
+ if (left < entries.length && _RegionHeap.#before(entries[left], entries[smallest])) {
2005
+ smallest = left;
2006
+ }
2007
+ if (right < entries.length && _RegionHeap.#before(entries[right], entries[smallest])) {
2008
+ smallest = right;
2009
+ }
2010
+ if (smallest === index) {
2011
+ return;
2012
+ }
2013
+ _RegionHeap.#swap(entries, index, smallest);
2014
+ index = smallest;
2015
+ }
2016
+ }
2017
+ static #before(a, b) {
2018
+ if (a === void 0 || b === void 0) {
2019
+ return a !== void 0;
2020
+ }
2021
+ return a.length === b.length ? a.region.from < b.region.from : a.length < b.length;
2022
+ }
2023
+ static #swap(entries, a, b) {
2024
+ const left = entries[a];
2025
+ const right = entries[b];
2026
+ if (left !== void 0 && right !== void 0) {
2027
+ entries[a] = right;
2028
+ entries[b] = left;
2029
+ }
2030
+ }
2031
+ };
2032
+ function withRelations(regions) {
2033
+ for (let i = 1; i < regions.length; i += 1) {
2034
+ const previous = regions[i - 1];
2035
+ const current = regions[i];
2036
+ if (previous === void 0 || current === void 0) {
2037
+ continue;
2038
+ }
2039
+ const relation = _chunk3R3QVKY5cjs.keyRelationBetween.call(void 0, previous.key, current.key);
2040
+ if (relation !== null && relation !== "same") {
2041
+ current.modulation = relation;
2042
+ }
2043
+ }
2044
+ return regions;
2045
+ }
2046
+ function keyTimelineFromNotes(notes, opts = {}) {
2047
+ const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "key timeline meters");
2048
+ const bar = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, 0, meters);
2049
+ const expectedKeyBeats = _nullishCoalesce(opts.expectedKeyBeats, () => ( bar * 4));
2050
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, expectedKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "expectedKeyBeats");
2051
+ const minKeyBeats = _nullishCoalesce(opts.minKeyBeats, () => ( bar));
2052
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, minKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "minKeyBeats");
2053
+ const budget = opts.budget;
2054
+ _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "key timeline notes", { allowNonPositiveDuration: true, budget });
2055
+ const sounding = notes.filter((note) => note.durationBeat > 0);
2056
+ const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
2057
+ const totalBeats = _nullishCoalesce(opts.totalBeats, () => ( lastEnd));
2058
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "key timeline totalBeats");
2059
+ const slotBeats = Math.min(minKeyBeats, expectedKeyBeats);
2060
+ const { origin, startBeat: musicStart } = gridForNotes(sounding, slotBeats);
2061
+ const slotCount = Math.max(0, Math.ceil((totalBeats - origin) / slotBeats - _chunkS5BKZZGMcjs.BEAT_EPS));
2062
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, slotCount * KEY_CANDIDATES.length, "key timeline slots", budget);
2063
+ if (slotCount === 0 || sounding.length === 0) {
2064
+ return [];
2065
+ }
2066
+ const profile = resolveKeyProfile(opts.profile);
2067
+ const slotNotes = bucketNotesBySlot(sounding, { origin, slotBeats }, slotCount, {
2068
+ name: "key timeline note-to-slot memberships",
2069
+ budget
2070
+ });
2071
+ const slotWeights = [];
2072
+ for (let i = 0; i < slotCount; i += 1) {
2073
+ const startBeat = origin + i * slotBeats;
2074
+ slotWeights.push(
2075
+ windowWeights(
2076
+ _nullishCoalesce(slotNotes[i], () => ( [])),
2077
+ startBeat,
2078
+ Math.min(startBeat + slotBeats, totalBeats),
2079
+ meters
2080
+ )
2081
+ );
2082
+ }
2083
+ const slots = [];
2084
+ let weightSum = 0;
2085
+ let soundingSlots = 0;
2086
+ for (let i = 0; i < slotCount; i += 1) {
2087
+ const startBeat = origin + i * slotBeats;
2088
+ const endBeat = Math.min(startBeat + slotBeats, totalBeats);
2089
+ const own = _nullishCoalesce(slotWeights[i], () => ( EMPTY_WEIGHTS));
2090
+ const before = _nullishCoalesce(slotWeights[i - 1], () => ( EMPTY_WEIGHTS));
2091
+ const after = _nullishCoalesce(slotWeights[i + 1], () => ( EMPTY_WEIGHTS));
2092
+ const weights = own.weights.map(
2093
+ (weight, pc) => weight + PAST_CONTEXT_SHARE * (_nullishCoalesce(before.weights[pc], () => ( 0))) + FUTURE_CONTEXT_SHARE * (_nullishCoalesce(after.weights[pc], () => ( 0)))
2094
+ );
2095
+ const totalWeight = own.totalWeight;
2096
+ const scores = KEY_CANDIDATES.map(
2097
+ (candidate) => profileScore(weights, candidate.minor ? profile.minor : profile.major, candidate.rootPc) * totalWeight
2098
+ );
2099
+ slots.push({ startBeat, endBeat, scores, weight: totalWeight });
2100
+ if (totalWeight > _chunkS5BKZZGMcjs.BEAT_EPS) {
2101
+ weightSum += totalWeight;
2102
+ soundingSlots += 1;
2103
+ }
2104
+ }
2105
+ if (soundingSlots === 0) {
2106
+ return [];
2107
+ }
2108
+ const meanSlotWeight = weightSum / soundingSlots;
2109
+ const changeCost = MODULATION_COST * meanSlotWeight * (expectedKeyBeats / slotBeats);
2110
+ const path = chooseKeys(slots, changeCost, meters, budget);
2111
+ const grouped = regionsFromPath(slots, path);
2112
+ const head = grouped[0];
2113
+ const next = grouped[1];
2114
+ if (head !== void 0 && next !== void 0 && head.endBeat - Math.max(head.startBeat, musicStart) < slotBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
2115
+ next.startBeat = head.startBeat;
2116
+ next.from = head.from;
2117
+ grouped.shift();
2118
+ }
2119
+ const regions = grouped.map((region) => {
2120
+ const candidate = KEY_CANDIDATES[region.candidate];
2121
+ const key = _chunk3R3QVKY5cjs.resolveKey.call(void 0, _nullishCoalesce(_optionalChain([candidate, 'optionalAccess', _24 => _24.key]), () => ( _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))));
2122
+ const startBeat = Math.max(region.startBeat, musicStart);
2123
+ const weights = new Array(PITCH_CLASSES).fill(0);
2124
+ for (let i = region.from; i < region.toExclusive; i += 1) {
2125
+ const slot = slotWeights[i];
2126
+ if (slot === void 0) {
2127
+ continue;
2128
+ }
2129
+ for (let pc = 0; pc < PITCH_CLASSES; pc += 1) {
2130
+ weights[pc] = (_nullishCoalesce(weights[pc], () => ( 0))) + (_nullishCoalesce(slot.weights[pc], () => ( 0)));
2131
+ }
2132
+ }
2133
+ const correlation = profileScore(
2134
+ weights,
2135
+ _optionalChain([candidate, 'optionalAccess', _25 => _25.minor]) ? profile.minor : profile.major,
2136
+ _nullishCoalesce(_optionalChain([candidate, 'optionalAccess', _26 => _26.rootPc]), () => ( 0))
2137
+ );
2138
+ return {
2139
+ startBeat,
2140
+ endBeat: region.endBeat,
2141
+ key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, key),
2142
+ confidence: Math.max(0, correlation)
2143
+ };
2144
+ });
2145
+ return withRelations(regions);
2146
+ }
2147
+ var CHORD_TONE_CREDIT = 1;
2148
+ var DOMINANT_CREDIT = 0.7;
2149
+ var TONIC_CREDIT = 0.4;
2150
+ var CADENCE_CREDIT = 0.6;
2151
+ var CHORD_MODULATION_COST = 0.6;
2152
+ function chordFitsKey(chord, candidate) {
2153
+ const tones = _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord);
2154
+ if (tones.length === 0) {
2155
+ return 0;
2156
+ }
2157
+ let inKey = 0;
2158
+ for (const pc of tones) {
2159
+ if (_chunk3R3QVKY5cjs.isScaleTone.call(void 0, pc, candidate.key)) {
2160
+ inKey += 1;
2161
+ }
2162
+ }
2163
+ let score = CHORD_TONE_CREDIT * ((inKey - (tones.length - inKey)) / tones.length);
2164
+ const rootDegree = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - candidate.rootPc);
2165
+ if (rootDegree === 7) {
2166
+ if (_chunk3R3QVKY5cjs.soundsDominantSeventh.call(void 0, chord)) {
2167
+ score += DOMINANT_CREDIT;
2168
+ } else if (chord.quality === "maj") {
2169
+ score += DOMINANT_CREDIT * 0.5;
2170
+ }
2171
+ }
2172
+ if (rootDegree === 0) {
2173
+ score += TONIC_CREDIT;
2174
+ }
2175
+ return score;
2176
+ }
2177
+ function detectModulations(chords, opts = {}) {
2178
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, chords, "modulation chord segments");
2179
+ chords.forEach((segment, index) => {
2180
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, segment, `modulation chord segments[${index}]`);
2181
+ });
2182
+ const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "modulation meters");
2183
+ const bar = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, 0, meters);
2184
+ const expectedKeyBeats = _nullishCoalesce(opts.expectedKeyBeats, () => ( bar * 4));
2185
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, expectedKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "expectedKeyBeats");
2186
+ const minKeyBeats = _nullishCoalesce(opts.minKeyBeats, () => ( bar));
2187
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, minKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "minKeyBeats");
2188
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
2189
+ chords.length * KEY_CANDIDATES.length,
2190
+ "modulation chord segments",
2191
+ opts.budget
2192
+ );
2193
+ const sorted = [...chords].filter((segment) => segment.endBeat > segment.startBeat).sort((a, b) => a.startBeat - b.startBeat);
2194
+ const lastChordEnd = sorted.reduce((end, segment) => Math.max(end, segment.endBeat), 0);
2195
+ const totalBeats = _nullishCoalesce(opts.totalBeats, () => ( lastChordEnd));
2196
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "modulation totalBeats");
2197
+ const ordered = sorted.map((segment) => ({ ...segment, endBeat: Math.min(segment.endBeat, totalBeats) })).filter((segment) => segment.endBeat > segment.startBeat);
2198
+ if (ordered.length === 0) {
2199
+ return [];
2200
+ }
2201
+ const slots = ordered.map((segment) => {
2202
+ const length = segment.endBeat - segment.startBeat;
2203
+ return {
2204
+ startBeat: segment.startBeat,
2205
+ endBeat: segment.endBeat,
2206
+ // A chord that holds for two bars argues twice as hard as one that
2207
+ // passes in half a bar, exactly as a heavier slot does in the note path.
2208
+ scores: KEY_CANDIDATES.map((candidate) => chordFitsKey(segment.chord, candidate) * length),
2209
+ weight: length
2210
+ };
2211
+ });
2212
+ for (let i = 1; i < slots.length; i += 1) {
2213
+ const arriving = _optionalChain([ordered, 'access', _27 => _27[i], 'optionalAccess', _28 => _28.chord]);
2214
+ const approaching = _optionalChain([ordered, 'access', _29 => _29[i - 1], 'optionalAccess', _30 => _30.chord]);
2215
+ const slot = slots[i];
2216
+ if (arriving === void 0 || approaching === void 0 || slot === void 0) {
2217
+ continue;
2218
+ }
2219
+ const isDominantMotion = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, approaching.rootPc - arriving.rootPc) === 7 && _chunk3R3QVKY5cjs.soundsDominantSeventh.call(void 0, approaching);
2220
+ if (!isDominantMotion) {
2221
+ continue;
2222
+ }
2223
+ const length = slot.endBeat - slot.startBeat;
2224
+ for (let c = 0; c < KEY_CANDIDATES.length; c += 1) {
2225
+ const candidate = KEY_CANDIDATES[c];
2226
+ if (candidate !== void 0 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, arriving.rootPc - candidate.rootPc) === 0) {
2227
+ slot.scores[c] = (_nullishCoalesce(slot.scores[c], () => ( 0))) + CADENCE_CREDIT * length;
2228
+ }
2229
+ }
2230
+ }
2231
+ const changeCost = CHORD_MODULATION_COST * expectedKeyBeats;
2232
+ const path = chooseKeys(slots, changeCost, meters, opts.budget);
2233
+ const grouped = regionsFromPath(
2234
+ slots,
2235
+ enforceMinimumRegion(slots, path, Math.min(minKeyBeats, expectedKeyBeats), opts.budget)
2236
+ );
2237
+ const profile = resolveKeyProfile(opts.profile);
2238
+ const regions = grouped.map((region) => {
2239
+ const candidate = KEY_CANDIDATES[region.candidate];
2240
+ const key = _chunk3R3QVKY5cjs.resolveKey.call(void 0, _nullishCoalesce(_optionalChain([candidate, 'optionalAccess', _31 => _31.key]), () => ( _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))));
2241
+ const weights = new Array(PITCH_CLASSES).fill(0);
2242
+ for (let i = region.from; i < region.toExclusive; i += 1) {
2243
+ const segment = ordered[i];
2244
+ if (segment === void 0) {
2245
+ continue;
2246
+ }
2247
+ const overlap = segment.endBeat - segment.startBeat;
2248
+ if (overlap <= _chunkS5BKZZGMcjs.BEAT_EPS) {
2249
+ continue;
2250
+ }
2251
+ for (const pc of _chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, segment.chord)) {
2252
+ weights[pc] = (_nullishCoalesce(weights[pc], () => ( 0))) + overlap;
2253
+ }
2254
+ }
2255
+ const correlation = profileScore(
2256
+ weights,
2257
+ _optionalChain([candidate, 'optionalAccess', _32 => _32.minor]) ? profile.minor : profile.major,
2258
+ _nullishCoalesce(_optionalChain([candidate, 'optionalAccess', _33 => _33.rootPc]), () => ( 0))
2259
+ );
2260
+ return {
2261
+ startBeat: region.startBeat,
2262
+ endBeat: region.endBeat,
2263
+ key,
2264
+ confidence: Math.max(0, correlation)
2265
+ };
2266
+ });
2267
+ const last = regions[regions.length - 1];
2268
+ if (last !== void 0 && totalBeats > last.endBeat) {
2269
+ last.endBeat = totalBeats;
2270
+ }
2271
+ return attachPivots(withRelations(regions), ordered);
2272
+ }
2273
+ var THIRD_SEMITONES = [3, 4];
2274
+ var FIFTH_SEMITONES = [6, 7, 8];
2275
+ function triadCore(chord) {
2276
+ const root = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc);
2277
+ let third;
2278
+ let fifth;
2279
+ for (const interval of chord.intervals) {
2280
+ if (interval < 0 || interval >= 12) {
2281
+ continue;
2282
+ }
2283
+ if (third === void 0 && THIRD_SEMITONES.includes(interval)) {
2284
+ third = interval;
2285
+ } else if (fifth === void 0 && FIFTH_SEMITONES.includes(interval)) {
2286
+ fifth = interval;
2287
+ }
2288
+ }
2289
+ if (third === void 0 || fifth === void 0) {
2290
+ return null;
2291
+ }
2292
+ return [root, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, root + third), _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, root + fifth)].sort((a, b) => a - b);
2293
+ }
2294
+ function lastChordFinishedBy(chords) {
2295
+ const byEnd = chords.map((_, index) => index).sort((a, b) => (_nullishCoalesce(_optionalChain([chords, 'access', _34 => _34[a], 'optionalAccess', _35 => _35.endBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([chords, 'access', _36 => _36[b], 'optionalAccess', _37 => _37.endBeat]), () => ( 0))));
2296
+ const latest = new Int32Array(byEnd.length);
2297
+ let running = 0;
2298
+ for (let i = 0; i < byEnd.length; i += 1) {
2299
+ running = Math.max(running, _nullishCoalesce(byEnd[i], () => ( 0)));
2300
+ latest[i] = running;
2301
+ }
2302
+ return (beat) => {
2303
+ let low = 0;
2304
+ let high = byEnd.length;
2305
+ while (low < high) {
2306
+ const mid = low + high >> 1;
2307
+ if ((_nullishCoalesce(_optionalChain([chords, 'access', _38 => _38[_nullishCoalesce(byEnd[mid], () => ( 0))], 'optionalAccess', _39 => _39.endBeat]), () => ( 0))) <= beat) {
2308
+ low = mid + 1;
2309
+ } else {
2310
+ high = mid;
2311
+ }
2312
+ }
2313
+ return low === 0 ? void 0 : chords[_nullishCoalesce(latest[low - 1], () => ( 0))];
2314
+ };
2315
+ }
2316
+ function sameTones(a, b) {
2317
+ return a.length === b.length && a.every((pc, index) => pc === b[index]);
2318
+ }
2319
+ function attachPivots(regions, chords) {
2320
+ if (regions.length < 2 || chords.length === 0) {
2321
+ return regions;
2322
+ }
2323
+ const finished = lastChordFinishedBy(chords);
2324
+ for (let i = 1; i < regions.length; i += 1) {
2325
+ const previous = regions[i - 1];
2326
+ const current = regions[i];
2327
+ if (previous === void 0 || current === void 0) {
2328
+ continue;
2329
+ }
2330
+ const last = finished(current.startBeat + _chunkS5BKZZGMcjs.BEAT_EPS);
2331
+ if (last === void 0) {
2332
+ continue;
2333
+ }
2334
+ const core = triadCore(last.chord);
2335
+ if (core === null) {
2336
+ continue;
2337
+ }
2338
+ const root = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, last.chord.rootPc);
2339
+ const pivot = pivotChords(previous.key, current.key).find(
2340
+ (entry) => entry.chord.rootPc === root && sameTones(_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, entry.chord), core)
2341
+ );
2342
+ if (pivot !== void 0) {
2343
+ current.pivot = pivot;
2344
+ }
2345
+ }
2346
+ return regions;
2347
+ }
2348
+ function keyLookup(regions, fallback) {
2349
+ return (beat) => {
2350
+ let low = 0;
2351
+ let high = regions.length;
2352
+ while (low < high) {
2353
+ const middle = Math.floor((low + high) / 2);
2354
+ if ((_nullishCoalesce(_optionalChain([regions, 'access', _40 => _40[middle], 'optionalAccess', _41 => _41.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat) {
2355
+ low = middle + 1;
2356
+ } else {
2357
+ high = middle;
2358
+ }
2359
+ }
2360
+ const candidate = regions[low - 1];
2361
+ return candidate !== void 0 && beat < candidate.endBeat ? candidate.key : fallback;
2362
+ };
2363
+ }
2364
+ function prevailingKeyOf(regions) {
2365
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, regions, "key regions");
2366
+ const totals = /* @__PURE__ */ new Map();
2367
+ for (const [index, given] of regions.entries()) {
2368
+ const region = _chunkS5BKZZGMcjs.assertRecord.call(void 0, given, `key regions[${index}]`);
2369
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, region.key, `key regions[${index}].key`);
2370
+ const id = `${_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, region.key.scale.rootPc)}:${region.key.scale.modeMask12}`;
2371
+ const entry = totals.get(id);
2372
+ const length = Math.max(0, region.endBeat - region.startBeat);
2373
+ if (entry === void 0) {
2374
+ totals.set(id, { key: region.key, length });
2375
+ } else {
2376
+ entry.length += length;
2377
+ }
2378
+ }
2379
+ let best = null;
2380
+ let bestLength = -1;
2381
+ for (const entry of totals.values()) {
2382
+ if (entry.length > bestLength) {
2383
+ bestLength = entry.length;
2384
+ best = entry.key;
2385
+ }
2386
+ }
2387
+ return best;
2388
+ }
2389
+
2390
+ // src/analyze/voice/index.ts
2391
+ function keyScaleAt(key) {
2392
+ if (typeof key === "function") {
2393
+ return key;
2394
+ }
2395
+ const scale = _chunk3R3QVKY5cjs.toKeyScale.call(void 0, key);
2396
+ return () => scale;
2397
+ }
2398
+ function toVoiceNotes(events) {
2399
+ _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, events, "voice notes", { allowNonPositiveDuration: true });
2400
+ return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));
2401
+ }
2402
+ function intervalAboveBass(pitch, others, chord) {
2403
+ let bass = Number.POSITIVE_INFINITY;
2404
+ for (const ov of others) {
2405
+ bass = Math.min(bass, ov.pitch);
2406
+ }
2407
+ if (Number.isFinite(bass) && bass < pitch) {
2408
+ return ((pitch - bass) % 12 + 12) % 12;
2409
+ }
2410
+ const ref = _nullishCoalesce(chord.bassPc, () => ( chord.rootPc));
2411
+ return ((_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch) - _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, ref)) % 12 + 12) % 12;
2412
+ }
2413
+ var TENSION_DEGREE_BY_INTERVAL = {
2414
+ 1: 9,
2415
+ 2: 9,
2416
+ 3: 9,
2417
+ 5: 11,
2418
+ 6: 11,
2419
+ 8: 13,
2420
+ 9: 13
2421
+ };
2422
+ var NATURAL_TENSION_INTERVALS = /* @__PURE__ */ new Set([2, 5, 9]);
2423
+ function tensionDegree(ic) {
2424
+ return TENSION_DEGREE_BY_INTERVAL[ic];
2425
+ }
2426
+ function asSounded(chord) {
2427
+ if (chord.bassPc === void 0) {
2428
+ return chord;
2429
+ }
2430
+ return { ...chord, intervals: [...chord.intervals, _chunk3R3QVKY5cjs.intervalAboveRoot.call(void 0, chord.bassPc, chord)] };
2431
+ }
2432
+ function isStep(a, b) {
2433
+ const d = Math.abs(a - b);
2434
+ return d === 1 || d === 2;
2435
+ }
2436
+ var LEAP_SEMITONES = 3;
2437
+ function suspensionType(ic, delta) {
2438
+ if (delta > 0) {
2439
+ return ic === 1 || ic === 2 ? "sus2-3" : null;
2440
+ }
2441
+ if (ic === 2) {
2442
+ return "sus9-8";
2443
+ }
2444
+ if (ic === 10 || ic === 11) {
2445
+ return "sus7-6";
2446
+ }
2447
+ if (ic === 5 || ic === 6) {
2448
+ return "sus4-3";
2449
+ }
2450
+ if (ic === 8 || ic === 9) {
2451
+ return "sus6-5";
2452
+ }
2453
+ return null;
2454
+ }
2455
+ var STEP_RESOLUTION = 2;
2456
+ var NEAREST_CHORD_TONE_REACH = 12;
2457
+ function nearestChordTone(pitch, chord, maxDistance) {
2458
+ for (let delta = 1; delta <= maxDistance; delta += 1) {
2459
+ if (_chunk3R3QVKY5cjs.isChordMember.call(void 0, pitch - delta, chord)) {
2460
+ return pitch - delta;
2461
+ }
2462
+ if (_chunk3R3QVKY5cjs.isChordMember.call(void 0, pitch + delta, chord)) {
2463
+ return pitch + delta;
2464
+ }
2465
+ }
2466
+ return void 0;
2467
+ }
2468
+ function analyzeVoice(voice, chordAtBeat2, key, otherVoicesAtBeat = () => []) {
2469
+ _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, voice, "voice notes", { allowNonPositiveDuration: true });
2470
+ _chunkS5BKZZGMcjs.assertFunction.call(void 0, chordAtBeat2, "chordAtBeat");
2471
+ _chunkS5BKZZGMcjs.assertFunction.call(void 0, otherVoicesAtBeat, "otherVoicesAtBeat");
2472
+ const keyAt = keyScaleAt(key);
2473
+ const result = [];
2474
+ for (let i = 0; i < voice.length; i += 1) {
2475
+ const note = voice[i];
2476
+ if (!note) {
2477
+ continue;
2478
+ }
2479
+ const prevNote = i > 0 ? voice[i - 1] : void 0;
2480
+ const nextNote = i + 1 < voice.length ? voice[i + 1] : void 0;
2481
+ const prevEnd = prevNote === void 0 ? Number.NEGATIVE_INFINITY : prevNote.startBeat + prevNote.durationBeat;
2482
+ const noteEnd = note.startBeat + note.durationBeat;
2483
+ const prev = prevNote !== void 0 && adjacent(prevEnd, note.startBeat) ? prevNote : void 0;
2484
+ const next = nextNote !== void 0 && adjacent(noteEnd, nextNote.startBeat) ? nextNote : void 0;
2485
+ const chord = chordAtBeat2(note.startBeat);
2486
+ const labels = [];
2487
+ const member = _chunk3R3QVKY5cjs.isChordMember.call(void 0, note.pitch, chord);
2488
+ let handled = false;
2489
+ if (chord && member) {
2490
+ const role = _chunk3R3QVKY5cjs.chordToneRole.call(void 0, note.pitch, chord);
2491
+ const degree = role === null ? tensionDegree(_chunk3R3QVKY5cjs.intervalAboveRoot.call(void 0, note.pitch, chord)) : void 0;
2492
+ if (role) {
2493
+ labels.push({ kind: "chordTone", role });
2494
+ handled = true;
2495
+ } else if (degree !== void 0) {
2496
+ labels.push({ kind: "tension", degree });
2497
+ handled = true;
2498
+ } else {
2499
+ const bassRole = _chunk3R3QVKY5cjs.chordToneRole.call(void 0, note.pitch, asSounded(chord));
2500
+ if (bassRole !== null) {
2501
+ labels.push({ kind: "chordTone", role: bassRole });
2502
+ handled = true;
2503
+ }
2504
+ }
2505
+ }
2506
+ if (!handled && chord && prev && next) {
2507
+ const prevChord = chordAtBeat2(prev.startBeat);
2508
+ const prepared = prev.pitch === note.pitch && _chunk3R3QVKY5cjs.isChordMember.call(void 0, prev.pitch, prevChord);
2509
+ const resolves = isStep(next.pitch, note.pitch);
2510
+ const others = otherVoicesAtBeat(note.startBeat);
2511
+ const verticallyDissonant = others.some(
2512
+ (ov) => _chunk3R3QVKY5cjs.createsVerticalDissonance.call(void 0, note.pitch, ov.pitch, true)
2513
+ );
2514
+ if (prepared && verticallyDissonant && resolves) {
2515
+ const ic = intervalAboveBass(note.pitch, others, chord);
2516
+ const type = suspensionType(ic, next.pitch - note.pitch);
2517
+ if (type !== null) {
2518
+ labels.push({ kind: "suspension", type, resolveTo: next.pitch });
2519
+ handled = true;
2520
+ }
2521
+ }
2522
+ }
2523
+ if (!handled && chord && prev && next) {
2524
+ const prevMember = _chunk3R3QVKY5cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat2(prev.startBeat));
2525
+ const nextMember = _chunk3R3QVKY5cjs.isChordMember.call(void 0, next.pitch, chordAtBeat2(next.startBeat));
2526
+ const up = note.pitch - prev.pitch > 0;
2527
+ const contInto = next.pitch - note.pitch > 0;
2528
+ const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;
2529
+ if (prevMember && nextMember && !member && isStep(note.pitch, prev.pitch) && isStep(next.pitch, note.pitch) && sameDir) {
2530
+ labels.push({ kind: "passing" });
2531
+ handled = true;
2532
+ }
2533
+ }
2534
+ if (!handled && chord && prev && next && !member && prev.pitch === next.pitch && _chunk3R3QVKY5cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat2(prev.startBeat)) && isStep(note.pitch, prev.pitch)) {
2535
+ labels.push({ kind: "neighbor" });
2536
+ handled = true;
2537
+ }
2538
+ if (!handled && chord && next && !member && note.pitch === next.pitch) {
2539
+ const nextChord = chordAtBeat2(next.startBeat);
2540
+ if (_chunk3R3QVKY5cjs.isChordMember.call(void 0, next.pitch, nextChord)) {
2541
+ labels.push({ kind: "anticipation" });
2542
+ handled = true;
2543
+ }
2544
+ }
2545
+ if (!handled && chord && prev && next && !member) {
2546
+ const prevMember = _chunk3R3QVKY5cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat2(prev.startBeat));
2547
+ const nextMember = _chunk3R3QVKY5cjs.isChordMember.call(void 0, next.pitch, chordAtBeat2(next.startBeat));
2548
+ const leapFromPrev = Math.abs(note.pitch - prev.pitch) >= LEAP_SEMITONES;
2549
+ const stepToNext = isStep(next.pitch, note.pitch);
2550
+ const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;
2551
+ if (prevMember && nextMember && leapFromPrev && stepToNext && opposite) {
2552
+ labels.push({ kind: "appoggiatura", resolveTo: next.pitch });
2553
+ handled = true;
2554
+ }
2555
+ }
2556
+ if (!handled && chord && prev && next && !member) {
2557
+ const prevMember = _chunk3R3QVKY5cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat2(prev.startBeat));
2558
+ const nextMember = _chunk3R3QVKY5cjs.isChordMember.call(void 0, next.pitch, chordAtBeat2(next.startBeat));
2559
+ const stepFromPrev = isStep(note.pitch, prev.pitch);
2560
+ const leapToNext = Math.abs(next.pitch - note.pitch) >= LEAP_SEMITONES;
2561
+ const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;
2562
+ if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {
2563
+ labels.push({ kind: "escape" });
2564
+ handled = true;
2565
+ }
2566
+ }
2567
+ if (!handled && chord && !member) {
2568
+ const ic = _chunk3R3QVKY5cjs.intervalAboveRoot.call(void 0, note.pitch, chord);
2569
+ const degree = tensionDegree(ic);
2570
+ const colours = NATURAL_TENSION_INTERVALS.has(ic) || chord.intervals.some((i2) => i2 === 10 || i2 === 11);
2571
+ const avoid = ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5) || ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10);
2572
+ if (avoid) {
2573
+ labels.push({ kind: "avoid" });
2574
+ const resolveTo = nearestChordTone(note.pitch, chord, STEP_RESOLUTION);
2575
+ if (resolveTo !== void 0) {
2576
+ labels.push({ kind: "needsResolution", resolveTo });
2577
+ }
2578
+ } else if (degree !== void 0 && colours) {
2579
+ labels.push({ kind: "tension", degree });
2580
+ } else {
2581
+ const resolveTo = nearestChordTone(note.pitch, chord, NEAREST_CHORD_TONE_REACH);
2582
+ labels.push(
2583
+ resolveTo === void 0 ? { kind: "avoid" } : { kind: "needsResolution", resolveTo }
2584
+ );
2585
+ }
2586
+ }
2587
+ if (next && _chunk3R3QVKY5cjs.isLeadingToneResolution.call(void 0, note.pitch, next.pitch, keyAt(next.startBeat))) {
2588
+ labels.push({ kind: "leadingTone", resolveTo: next.pitch });
2589
+ }
2590
+ const analyzed = {
2591
+ noteId: _nullishCoalesce(note.id, () => ( i)),
2592
+ pitch: note.pitch,
2593
+ startBeat: note.startBeat,
2594
+ durationBeat: note.durationBeat,
2595
+ labels,
2596
+ rationale: describe(labels)
2597
+ };
2598
+ if (note.originalIndex !== void 0) {
2599
+ analyzed.originalIndex = note.originalIndex;
2600
+ }
2601
+ result.push(analyzed);
2602
+ }
2603
+ return result;
2604
+ }
2605
+ function describe(labels) {
2606
+ const primary = labels[0];
2607
+ if (!primary) {
2608
+ return "Unclassified note";
2609
+ }
2610
+ switch (primary.kind) {
2611
+ case "chordTone":
2612
+ return `Chord ${primary.role}`;
2613
+ case "tension":
2614
+ return `${primary.degree}th tension`;
2615
+ case "avoid":
2616
+ return "Avoid note";
2617
+ case "passing":
2618
+ return "Passing tone between chord tones";
2619
+ case "neighbor":
2620
+ return "Neighbor tone";
2621
+ case "suspension":
2622
+ return `Suspension (${primary.type})`;
2623
+ case "appoggiatura":
2624
+ return "Appoggiatura leaning on the harmony, resolving by step";
2625
+ case "anticipation":
2626
+ return "Anticipation of the next chord";
2627
+ case "escape":
2628
+ return "Escape tone";
2629
+ case "needsResolution":
2630
+ return "Unresolved dissonance";
2631
+ case "leadingTone":
2632
+ return "Leading tone resolving to the tonic";
2633
+ }
2634
+ }
2635
+
2636
+ // src/analyze/timeline/index.ts
2637
+ function assertChordTimeline(timeline, name = "timeline") {
2638
+ const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, timeline, name);
2639
+ _chunkS5BKZZGMcjs.assertFunction.call(void 0, read.at, `${name}.at`);
2640
+ const segments = _chunkS5BKZZGMcjs.assertArray.call(void 0, read.segments, `${name}.segments`);
2641
+ for (let index = 0; index < segments.length; index += 1) {
2642
+ const segment = _chunkS5BKZZGMcjs.assertRecord.call(void 0, segments[index], `${name}.segments[${index}]`);
2643
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, segment.startBeat, `${name}.segments[${index}].startBeat`);
2644
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, segment.endBeat, `${name}.segments[${index}].endBeat`);
2645
+ }
2646
+ return read;
2647
+ }
2648
+ function chordTimelineFromChords(chords, totalBeats) {
2649
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
2650
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, chords, "chords");
2651
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, chords.length, "timeline chords");
2652
+ for (let index = 0; index < chords.length; index += 1) {
2653
+ _chunkS5BKZZGMcjs.assertRange.call(void 0,
2654
+ _nullishCoalesce(_optionalChain([chords, 'access', _42 => _42[index], 'optionalAccess', _43 => _43.startBeat]), () => ( Number.NaN)),
2655
+ -Number.MAX_SAFE_INTEGER,
2656
+ Number.MAX_SAFE_INTEGER,
2657
+ `chords[${index}].startBeat`
2658
+ );
2659
+ }
2660
+ const sorted = [...chords].filter((chord) => chord.startBeat < totalBeats).sort((a, b) => a.startBeat - b.startBeat);
2661
+ const segments = sorted.map((gc, i) => {
2662
+ const next = sorted[i + 1];
2663
+ const nextBeat = next ? next.startBeat : totalBeats;
2664
+ const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));
2665
+ return {
2666
+ startBeat: gc.startBeat,
2667
+ endBeat,
2668
+ chord: _chunk3R3QVKY5cjs.chordFromSpan.call(void 0, gc)
2669
+ };
2670
+ }).filter((seg) => seg.endBeat > seg.startBeat);
2671
+ return { at: segmentLookup(segments), segments };
2672
+ }
2673
+ function chordAtBeat(segments, beat) {
2674
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, beat, "timeline query beat");
2675
+ const candidate = segments[firstAfter(segments, beat) - 1];
2676
+ return candidate !== void 0 && beat >= candidate.startBeat && beat < candidate.endBeat ? candidate.chord : null;
2677
+ }
2678
+ function firstAfter(segments, beat) {
2679
+ let low = 0;
2680
+ let high = segments.length;
2681
+ while (low < high) {
2682
+ const middle = Math.floor((low + high) / 2);
2683
+ if ((_nullishCoalesce(_optionalChain([segments, 'access', _44 => _44[middle], 'optionalAccess', _45 => _45.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat) {
2684
+ low = middle + 1;
2685
+ } else {
2686
+ high = middle;
2687
+ }
2688
+ }
2689
+ return low;
2690
+ }
2691
+ function segmentStartingAt(segments, beat) {
2692
+ const index = firstAfter(segments, beat);
2693
+ for (const candidate of [segments[index - 1], segments[index]]) {
2694
+ if (candidate !== void 0 && Math.abs(candidate.startBeat - beat) < _chunkS5BKZZGMcjs.BEAT_EPS) {
2695
+ return candidate;
2696
+ }
2697
+ }
2698
+ return void 0;
2699
+ }
2700
+ function segmentLookup(segments) {
2701
+ return (beat) => chordAtBeat(segments, beat);
2702
+ }
2703
+ var NOISE_THRESHOLD_RATIO = 0.2;
2704
+ var MAX_DETECTION_PCS = 6;
2705
+ var INEXACT_CONFIDENCE_FACTOR = 0.85;
2706
+ var DIATONIC_BONUS = 0.5;
2707
+ var EXACT_BONUS = 0.5;
2708
+ var EXTRA_TONE_PENALTY = 0.6;
2709
+ var OMITTED_TONE_PENALTY = 0.15;
2710
+ var BASS_ROOT_STANDING = 0.95;
2711
+ var OCTAVE = 12;
2712
+ function scoreMatch(match, weights, maxWeight, key, pedalPc) {
2713
+ const rootWeight = maxWeight > 0 ? (_nullishCoalesce(weights[match.rootPc], () => ( 0))) / maxWeight : 0;
2714
+ const rootStanding = match.inversion === 0 ? Math.max(rootWeight, BASS_ROOT_STANDING) : rootWeight;
2715
+ const chord = _chunk3R3QVKY5cjs.makeChord.call(void 0, match.rootPc, match.quality);
2716
+ const account = matchAccount(match, chord, pedalPc);
2717
+ let score = rootStanding;
2718
+ if (_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord).every((pc) => _chunk3R3QVKY5cjs.isScaleTone.call(void 0, pc, key))) {
2719
+ score += DIATONIC_BONUS;
2720
+ }
2721
+ if (account.exact) {
2722
+ score += EXACT_BONUS;
2723
+ }
2724
+ score -= EXTRA_TONE_PENALTY * account.extras;
2725
+ score -= OMITTED_TONE_PENALTY * account.missing;
2726
+ return score;
2727
+ }
2728
+ var SLASH_UPPER_QUALITIES = /* @__PURE__ */ new Set(["maj", "min"]);
2729
+ function isSlashReading(match, pedalPc) {
2730
+ return pedalPc !== null && match.inversion === null && match.missingPcs.length === 0 && match.extraPcs.length === 1 && match.extraPcs[0] === pedalPc && SLASH_UPPER_QUALITIES.has(match.quality);
2731
+ }
2732
+ function namesAsExtension(chord, pc) {
2733
+ return chord.intervals.some((iv) => iv >= OCTAVE && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + iv) === pc);
2734
+ }
2735
+ function matchAccount(match, chord, pedalPc) {
2736
+ const unvoiced = pedalPc !== null && namesAsExtension(chord, pedalPc) ? 1 : 0;
2737
+ const named = isSlashReading(match, pedalPc) ? 1 : 0;
2738
+ return {
2739
+ extras: match.extraPcs.length - named + unvoiced,
2740
+ missing: match.missingPcs.length + unvoiced,
2741
+ exact: match.exact && unvoiced === 0
2742
+ };
2743
+ }
2744
+ function bassSoundsOnlyBelow(notes, windowStart, windowEnd, bassPc) {
2745
+ let upperFloor = Number.POSITIVE_INFINITY;
2746
+ let highestBass = Number.NEGATIVE_INFINITY;
2747
+ for (const note of notes) {
2748
+ const overlap = Math.min(note.startBeat + note.durationBeat, windowEnd) - Math.max(note.startBeat, windowStart);
2749
+ if (overlap <= _chunkS5BKZZGMcjs.BEAT_EPS) {
2750
+ continue;
2751
+ }
2752
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, note.pitch) === bassPc) {
2753
+ highestBass = Math.max(highestBass, note.pitch);
2754
+ } else {
2755
+ upperFloor = Math.min(upperFloor, note.pitch);
2756
+ }
2757
+ }
2758
+ return highestBass <= upperFloor;
2759
+ }
2760
+ function chordConfidence(chord, weights, totalWeight, exact) {
2761
+ const tones = new Set(_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord));
2762
+ let chordWeight = 0;
2763
+ for (let pc = 0; pc < 12; pc += 1) {
2764
+ if (tones.has(pc)) {
2765
+ chordWeight += _nullishCoalesce(weights[pc], () => ( 0));
2766
+ }
2767
+ }
2768
+ const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;
2769
+ const confidence = Math.min(1, Math.max(0, raw));
2770
+ return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;
2771
+ }
2772
+ function analyzeWindow(notes, windowStart, windowEnd, meters, key) {
2773
+ const { weights, totalWeight, maxWeight, lowestPitch } = windowWeights(
2774
+ notes,
2775
+ windowStart,
2776
+ windowEnd,
2777
+ meters
2778
+ );
2779
+ if (totalWeight <= _chunkS5BKZZGMcjs.BEAT_EPS) {
2780
+ return null;
2781
+ }
2782
+ const ranked = weights.map((weight, pc) => ({ pc, weight })).filter(({ weight }) => weight > 0).sort((a, b) => b.weight - a.weight || a.pc - b.pc);
2783
+ const selected = ranked.filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO).slice(0, MAX_DETECTION_PCS).map(({ pc }) => pc);
2784
+ const bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, lowestPitch);
2785
+ const hasBass = selected.includes(bassPc);
2786
+ const detectionPitches = hasBass ? selected.map((pc) => pc === bassPc ? pc : pc + 12) : selected;
2787
+ const pedalPc = hasBass && bassSoundsOnlyBelow(notes, windowStart, windowEnd, bassPc) ? bassPc : null;
2788
+ const matches = detectChord(detectionPitches, { input: hasBass ? "midi" : "pitchClass" });
2789
+ let bestMatch = null;
2790
+ let bestScore = Number.NEGATIVE_INFINITY;
2791
+ for (const match of matches) {
2792
+ const score = scoreMatch(match, weights, maxWeight, _chunk3R3QVKY5cjs.scaleOf.call(void 0, key), pedalPc);
2793
+ if (score > bestScore) {
2794
+ bestScore = score;
2795
+ bestMatch = match;
2796
+ }
2797
+ }
2798
+ if (bestMatch) {
2799
+ const chord = _chunk3R3QVKY5cjs.makeChord.call(void 0, bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);
2800
+ const account = matchAccount(
2801
+ bestMatch,
2802
+ _chunk3R3QVKY5cjs.makeChord.call(void 0, bestMatch.rootPc, bestMatch.quality),
2803
+ pedalPc
2804
+ );
2805
+ const reading = {
2806
+ chord: shared(chord),
2807
+ confidence: chordConfidence(chord, weights, totalWeight, account.exact)
2808
+ };
2809
+ const augmented = hasBass ? augmentedSixthFromPitchClasses(selected, bassPc, key) : null;
2810
+ if (augmented !== null) {
2811
+ reading.augmented = {
2812
+ chord: shared(augmented),
2813
+ confidence: chordConfidence(augmented, weights, totalWeight, true)
2814
+ };
2815
+ }
2816
+ return reading;
2817
+ }
2818
+ return null;
2819
+ }
2820
+ function shared(chord) {
2821
+ Object.freeze(chord.intervals);
2822
+ return Object.freeze(chord);
2823
+ }
2824
+ var DOMINANT_ABOVE_TONIC = 7;
2825
+ function resolvesOntoDominant(readings, index, key) {
2826
+ const dominantPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc + DOMINANT_ABOVE_TONIC);
2827
+ const candidate = _optionalChain([readings, 'access', _46 => _46[index], 'optionalAccess', _47 => _47.chord]);
2828
+ for (let i = index + 1; i < readings.length; i += 1) {
2829
+ const next = readings[i];
2830
+ if (!next) {
2831
+ continue;
2832
+ }
2833
+ const chord = next.chord;
2834
+ if (candidate !== void 0 && sameChord(chord, candidate)) {
2835
+ continue;
2836
+ }
2837
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _nullishCoalesce(chord.bassPc, () => ( chord.rootPc))) !== dominantPc) {
2838
+ return false;
2839
+ }
2840
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc) === dominantPc) {
2841
+ return true;
2842
+ }
2843
+ }
2844
+ return true;
2845
+ }
2846
+ function sameChord(a, b) {
2847
+ return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;
2848
+ }
2849
+ var SEGMENTATION_QUALITIES = [
2850
+ "maj",
2851
+ "min",
2852
+ "dim",
2853
+ "aug",
2854
+ "dom7",
2855
+ "maj7",
2856
+ "min7",
2857
+ "m7b5",
2858
+ "dim7",
2859
+ "sus4",
2860
+ "6",
2861
+ "min6"
2862
+ ];
2863
+ var LEXICON_SIZE = 12 * SEGMENTATION_QUALITIES.length;
2864
+ var LEXICON_ROOTS = new Int32Array(LEXICON_SIZE);
2865
+ var LEXICON_TONE_START = new Int32Array(LEXICON_SIZE + 1);
2866
+ var LEXICON_TONES = (() => {
2867
+ const tones = [];
2868
+ let candidate = 0;
2869
+ for (let rootPc = 0; rootPc < 12; rootPc += 1) {
2870
+ for (const quality of SEGMENTATION_QUALITIES) {
2871
+ LEXICON_ROOTS[candidate] = rootPc;
2872
+ LEXICON_TONE_START[candidate] = tones.length;
2873
+ tones.push(..._chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, _chunk3R3QVKY5cjs.makeChord.call(void 0, rootPc, quality)));
2874
+ candidate += 1;
2875
+ }
2876
+ }
2877
+ LEXICON_TONE_START[candidate] = tones.length;
2878
+ return Int32Array.from(tones);
2879
+ })();
2880
+ var OUTSIDE_TONE_PENALTY = 1;
2881
+ var MISSING_TONE_PENALTY = 0.5;
2882
+ var ROOT_PRESENCE_BONUS = 0.3;
2883
+ var CHANGE_COST = 0.35;
2884
+ var STRONG_BEAT_DISCOUNT2 = 0.5;
2885
+ var MAX_METRIC_WEIGHT2 = 3;
2886
+ function candidateScore(candidate, weights, totalWeight) {
2887
+ const first = _nullishCoalesce(LEXICON_TONE_START[candidate], () => ( 0));
2888
+ const last = _nullishCoalesce(LEXICON_TONE_START[candidate + 1], () => ( first));
2889
+ let covered = 0;
2890
+ let missing = 0;
2891
+ for (let index = first; index < last; index += 1) {
2892
+ const weight = _nullishCoalesce(weights[_nullishCoalesce(LEXICON_TONES[index], () => ( 0))], () => ( 0));
2893
+ covered += weight;
2894
+ if (weight <= _chunkS5BKZZGMcjs.BEAT_EPS) {
2895
+ missing += 1;
2896
+ }
2897
+ }
2898
+ const outside = totalWeight - covered;
2899
+ const averageToneWeight = totalWeight / (last - first);
2900
+ return covered - OUTSIDE_TONE_PENALTY * outside - MISSING_TONE_PENALTY * missing * averageToneWeight + ROOT_PRESENCE_BONUS * (_nullishCoalesce(weights[_nullishCoalesce(LEXICON_ROOTS[candidate], () => ( 0))], () => ( 0)));
2901
+ }
2902
+ function beatOfSlot(grid, index) {
2903
+ return grid.origin + index * grid.slotBeats;
2904
+ }
2905
+ function slotOfBeat(grid, beat) {
2906
+ return (beat - grid.origin) / grid.slotBeats;
2907
+ }
2908
+ function createBoundaryTables(slotCount, budget) {
2909
+ return {
2910
+ scores: _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0, slotCount, LEXICON_SIZE, "timeline window scores", budget),
2911
+ back: _chunkS5BKZZGMcjs.allocateChoiceTable.call(void 0, slotCount, LEXICON_SIZE, "timeline window choices", budget),
2912
+ costs: _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0, 1, LEXICON_SIZE, "timeline window costs", budget),
2913
+ spare: _chunkS5BKZZGMcjs.allocateCandidateTable.call(void 0, 1, LEXICON_SIZE, "timeline window costs", budget),
2914
+ path: _chunkS5BKZZGMcjs.allocateChoiceTable.call(void 0, slotCount, 1, "timeline window path", budget)
2915
+ };
2916
+ }
2917
+ function scoreSlot(tables, slot, index) {
2918
+ const base = index * LEXICON_SIZE;
2919
+ if (slot.totalWeight <= _chunkS5BKZZGMcjs.BEAT_EPS) {
2920
+ tables.scores.fill(0, base, base + LEXICON_SIZE);
2921
+ return;
2922
+ }
2923
+ for (let c = 0; c < LEXICON_SIZE; c += 1) {
2924
+ tables.scores[base + c] = candidateScore(c, slot.weights, slot.totalWeight);
2925
+ }
2926
+ }
2927
+ function chooseBoundaries(tables, from, to, grid, meters, changeCost) {
2928
+ const width = to - from;
2929
+ if (width <= 0) {
2930
+ return [];
2931
+ }
2932
+ const { scores, back, path } = tables;
2933
+ let costs = tables.costs;
2934
+ let next = tables.spare;
2935
+ for (let c = 0; c < LEXICON_SIZE; c += 1) {
2936
+ costs[c] = -(_nullishCoalesce(scores[from * LEXICON_SIZE + c], () => ( 0)));
2937
+ }
2938
+ for (let i = 1; i < width; i += 1) {
2939
+ let cheapest = Number.POSITIVE_INFINITY;
2940
+ let cheapestIndex = 0;
2941
+ for (let c = 0; c < LEXICON_SIZE; c += 1) {
2942
+ const cost = _nullishCoalesce(costs[c], () => ( Number.POSITIVE_INFINITY));
2943
+ if (cost < cheapest) {
2944
+ cheapest = cost;
2945
+ cheapestIndex = c;
2946
+ }
2947
+ }
2948
+ const slotStart = beatOfSlot(grid, from + i);
2949
+ const accent = _chunkS5BKZZGMcjs.metricWeight.call(void 0, slotStart, meters);
2950
+ const penalty = changeCost * (1 - STRONG_BEAT_DISCOUNT2 * accent / MAX_METRIC_WEIGHT2);
2951
+ const rowBase = (from + i) * LEXICON_SIZE;
2952
+ const change = cheapest + penalty;
2953
+ for (let c = 0; c < LEXICON_SIZE; c += 1) {
2954
+ const stay = _nullishCoalesce(costs[c], () => ( Number.POSITIVE_INFINITY));
2955
+ const carried = stay <= change ? stay : change;
2956
+ back[rowBase + c] = stay <= change ? c : cheapestIndex;
2957
+ next[c] = carried - (_nullishCoalesce(scores[rowBase + c], () => ( 0)));
2958
+ }
2959
+ const previous = costs;
2960
+ costs = next;
2961
+ next = previous;
2962
+ }
2963
+ let best = 0;
2964
+ for (let c = 1; c < LEXICON_SIZE; c += 1) {
2965
+ if ((_nullishCoalesce(costs[c], () => ( Number.POSITIVE_INFINITY))) < (_nullishCoalesce(costs[best], () => ( Number.POSITIVE_INFINITY)))) {
2966
+ best = c;
2967
+ }
2968
+ }
2969
+ let current = best;
2970
+ for (let i = width - 1; i >= 0; i -= 1) {
2971
+ path[from + i] = current;
2972
+ if (i > 0) {
2973
+ current = _nullishCoalesce(back[(from + i) * LEXICON_SIZE + current], () => ( current));
2974
+ }
2975
+ }
2976
+ const spans = [];
2977
+ let runStart = 0;
2978
+ for (let i = 1; i <= width; i += 1) {
2979
+ if (i === width || path[from + i] !== path[from + runStart]) {
2980
+ spans.push({
2981
+ startBeat: beatOfSlot(grid, from + runStart),
2982
+ endBeat: beatOfSlot(grid, from + i)
2983
+ });
2984
+ runStart = i;
2985
+ }
2986
+ }
2987
+ return spans;
2988
+ }
2989
+ function slotBeatsWithin(gridUnit, minChordBeats, harmonicRhythm) {
2990
+ const resolution = Math.min(minChordBeats, harmonicRhythm);
2991
+ const divisions = Math.max(1, Math.ceil(gridUnit / resolution - _chunkS5BKZZGMcjs.BEAT_EPS));
2992
+ return gridUnit / divisions;
2993
+ }
2994
+ function gridSpans(slotCount, grid) {
2995
+ return Array.from({ length: slotCount }, (_, i) => ({
2996
+ startBeat: beatOfSlot(grid, i),
2997
+ endBeat: beatOfSlot(grid, i + 1)
2998
+ }));
2999
+ }
3000
+ function dynamicSpans(slotWeights, tables, grid, harmonicRhythm, meters) {
3001
+ const slotBeats = grid.slotBeats;
3002
+ const slotCount = slotWeights.length;
3003
+ let soundingSlots = 0;
3004
+ let weightSum = 0;
3005
+ for (let i = 0; i < slotCount; i += 1) {
3006
+ const weight = _nullishCoalesce(_optionalChain([slotWeights, 'access', _48 => _48[i], 'optionalAccess', _49 => _49.totalWeight]), () => ( 0));
3007
+ if (weight > _chunkS5BKZZGMcjs.BEAT_EPS) {
3008
+ soundingSlots += 1;
3009
+ weightSum += weight;
3010
+ }
3011
+ }
3012
+ if (soundingSlots === 0) {
3013
+ return [];
3014
+ }
3015
+ const meanSlotWeight = weightSum / soundingSlots;
3016
+ const changeCost = CHANGE_COST * meanSlotWeight * (harmonicRhythm / slotBeats);
3017
+ const silent = slotWeights.map((slot) => slot.totalWeight <= _chunkS5BKZZGMcjs.BEAT_EPS);
3018
+ const breaks = new Array(slotCount).fill(false);
3019
+ for (let i = 0; i < slotCount; ) {
3020
+ if (!silent[i]) {
3021
+ i += 1;
3022
+ continue;
3023
+ }
3024
+ let end = i;
3025
+ while (end < slotCount && silent[end]) {
3026
+ end += 1;
3027
+ }
3028
+ if ((end - i) * slotBeats >= harmonicRhythm - _chunkS5BKZZGMcjs.BEAT_EPS) {
3029
+ for (let k = i; k < end; k += 1) {
3030
+ breaks[k] = true;
3031
+ }
3032
+ }
3033
+ i = end;
3034
+ }
3035
+ const spans = [];
3036
+ for (let i = 0; i < slotCount; ) {
3037
+ if (breaks[i]) {
3038
+ i += 1;
3039
+ continue;
3040
+ }
3041
+ let end = i;
3042
+ while (end < slotCount && !breaks[end]) {
3043
+ end += 1;
3044
+ }
3045
+ spans.push(...chooseBoundaries(tables, i, end, grid, meters, changeCost));
3046
+ i = end;
3047
+ }
3048
+ return spans;
3049
+ }
3050
+ function notesOfSpan(slotNotes, span, grid) {
3051
+ const first = Math.round(slotOfBeat(grid, span.startBeat));
3052
+ const lastExclusive = Math.round(slotOfBeat(grid, span.endBeat));
3053
+ if (lastExclusive - first === 1) {
3054
+ return [..._nullishCoalesce(slotNotes[first], () => ( []))];
3055
+ }
3056
+ const seen = /* @__PURE__ */ new Set();
3057
+ for (let i = first; i < lastExclusive; i += 1) {
3058
+ for (const note of _nullishCoalesce(slotNotes[i], () => ( []))) {
3059
+ seen.add(note);
3060
+ }
3061
+ }
3062
+ return [...seen];
3063
+ }
3064
+ function chordTimelineFromNotes(notes, opts = {}) {
3065
+ return analyzeTimeline(notes, opts).result;
3066
+ }
3067
+ var DIRTY_SLOT_MARGIN = 1;
3068
+ function dirtySlotsFor(evidence, startBeat, endBeat) {
3069
+ const first = Math.floor(slotOfBeat(evidence.grid, startBeat)) - DIRTY_SLOT_MARGIN;
3070
+ const lastExclusive = Math.ceil(slotOfBeat(evidence.grid, endBeat)) + DIRTY_SLOT_MARGIN;
3071
+ return {
3072
+ from: Math.max(0, Math.min(evidence.slotCount, first)),
3073
+ to: Math.max(0, Math.min(evidence.slotCount, lastExclusive))
3074
+ };
3075
+ }
3076
+ function spanIsClean(grid, span, dirtyFrom, dirtyTo) {
3077
+ const first = Math.round(slotOfBeat(grid, span.startBeat));
3078
+ const lastExclusive = Math.round(slotOfBeat(grid, span.endBeat));
3079
+ return lastExclusive <= dirtyFrom || first >= dirtyTo;
3080
+ }
3081
+ function keyId(key) {
3082
+ return `${key.rootPc}:${key.modeMask12}`;
3083
+ }
3084
+ function windowId(startBeat, endBeat, key) {
3085
+ return `${startBeat}|${endBeat}|${keyId(key)}`;
3086
+ }
3087
+ function evidenceFits(previous, grid, slotCount, totalBeats, segmentation, harmonicRhythm, keyGiven) {
3088
+ return previous.grid.origin === grid.origin && previous.grid.slotBeats === grid.slotBeats && previous.slotCount === slotCount && previous.totalBeats === totalBeats && previous.segmentation === segmentation && previous.harmonicRhythm === harmonicRhythm && previous.keyGiven === keyGiven;
3089
+ }
3090
+ function analyzeTimeline(notes, opts = {}, previous, dirty) {
3091
+ const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "timeline meters");
3092
+ const harmonicRhythm = _nullishCoalesce(opts.harmonicRhythm, () => ( _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, 0, meters)));
3093
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "harmonic rhythm");
3094
+ const segmentation = _nullishCoalesce(opts.segmentation, () => ( "dynamic"));
3095
+ const gridUnit = _chunkS5BKZZGMcjs.metricGridUnit.call(void 0, meters);
3096
+ const minChordBeats = _nullishCoalesce(opts.minChordBeats, () => ( gridUnit));
3097
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, minChordBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "minChordBeats");
3098
+ const budget = opts.budget;
3099
+ const noteOptions = { allowNonPositiveDuration: true, budget };
3100
+ if (opts.pickupBeats !== void 0) {
3101
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "timeline pickupBeats");
3102
+ noteOptions.minStartBeat = -opts.pickupBeats;
3103
+ }
3104
+ _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "timeline notes", noteOptions);
3105
+ const sounding = _chunk35NBV2FTcjs.sortedNoteEvents.call(void 0, notes.filter((note) => note.durationBeat > 0));
3106
+ const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
3107
+ const totalBeats = _nullishCoalesce(opts.totalBeats, () => ( lastNoteEnd));
3108
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
3109
+ const slotBeats = segmentation === "grid" ? harmonicRhythm : slotBeatsWithin(gridUnit, minChordBeats, harmonicRhythm);
3110
+ const { origin, startBeat: musicStart } = gridForNotes(sounding, slotBeats);
3111
+ const grid = { origin, slotBeats };
3112
+ const slotCount = Math.max(0, Math.ceil((totalBeats - grid.origin) / slotBeats - _chunkS5BKZZGMcjs.BEAT_EPS));
3113
+ const dynamic = segmentation === "dynamic";
3114
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
3115
+ dynamic ? slotCount * LEXICON_SIZE : slotCount,
3116
+ "timeline windows",
3117
+ budget
3118
+ );
3119
+ const keys = opts.key !== void 0 ? [
3120
+ {
3121
+ startBeat: musicStart,
3122
+ endBeat: totalBeats,
3123
+ key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, opts.key),
3124
+ confidence: 1
3125
+ }
3126
+ ] : keyTimelineFromNotes(sounding, { meters, totalBeats, budget });
3127
+ const prevailingKey = _nullishCoalesce(prevailingKeyOf(keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))));
3128
+ const keyAt = keyLookup(keys, prevailingKey);
3129
+ const scaleAt = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
3130
+ const segments = [];
3131
+ const segmentConfidence = [];
3132
+ const slotNotes = bucketNotesBySlot(sounding, grid, slotCount, {
3133
+ name: "timeline note-to-window memberships",
3134
+ budget
3135
+ });
3136
+ const carried = previous !== void 0 && dirty !== void 0 && previous.tables !== void 0 && evidenceFits(
3137
+ previous,
3138
+ grid,
3139
+ slotCount,
3140
+ totalBeats,
3141
+ segmentation,
3142
+ harmonicRhythm,
3143
+ opts.key !== void 0
3144
+ ) ? previous : void 0;
3145
+ const dirtyFrom = carried === void 0 ? 0 : Math.max(0, _nullishCoalesce(_optionalChain([dirty, 'optionalAccess', _50 => _50.from]), () => ( 0)));
3146
+ const dirtyTo = carried === void 0 ? slotCount : Math.min(slotCount, _nullishCoalesce(_optionalChain([dirty, 'optionalAccess', _51 => _51.to]), () => ( slotCount)));
3147
+ const tables = _nullishCoalesce(_optionalChain([carried, 'optionalAccess', _52 => _52.tables]), () => ( createBoundaryTables(dynamic ? slotCount : 0, budget)));
3148
+ if (carried !== void 0) {
3149
+ carried.tables = void 0;
3150
+ }
3151
+ const slotWeights = new Array(slotCount);
3152
+ for (let i = 0; i < slotCount; i += 1) {
3153
+ const reusable = i < dirtyFrom || i >= dirtyTo ? _optionalChain([carried, 'optionalAccess', _53 => _53.slotWeights, 'access', _54 => _54[i]]) : void 0;
3154
+ if (reusable !== void 0) {
3155
+ slotWeights[i] = reusable;
3156
+ continue;
3157
+ }
3158
+ const start = beatOfSlot(grid, i);
3159
+ const weights = windowWeights(
3160
+ _nullishCoalesce(slotNotes[i], () => ( [])),
3161
+ start,
3162
+ Math.min(start + slotBeats, totalBeats),
3163
+ meters
3164
+ );
3165
+ slotWeights[i] = weights;
3166
+ if (dynamic) {
3167
+ scoreSlot(tables, weights, i);
3168
+ }
3169
+ }
3170
+ const spans = segmentation === "grid" ? gridSpans(slotCount, grid) : dynamicSpans(slotWeights, tables, grid, harmonicRhythm, meters);
3171
+ const windows = /* @__PURE__ */ new Map();
3172
+ const readings = [];
3173
+ const readingKeys = [];
3174
+ const readingBounds = [];
3175
+ for (const span of spans) {
3176
+ const start = Math.max(span.startBeat, musicStart);
3177
+ const end = Math.min(span.endBeat, totalBeats);
3178
+ const spanKey = scaleAt(start);
3179
+ const spanKeyWhole = keyAt(start);
3180
+ const id = windowId(start, end, spanKey);
3181
+ const cached = carried !== void 0 && spanIsClean(grid, span, dirtyFrom, dirtyTo) ? carried.windows.get(id) : void 0;
3182
+ const inferred = cached !== void 0 ? cached : analyzeWindow(notesOfSpan(slotNotes, span, grid), start, end, meters, spanKeyWhole);
3183
+ windows.set(id, inferred);
3184
+ readings.push(inferred);
3185
+ readingKeys.push(spanKey);
3186
+ readingBounds.push({ start, end });
3187
+ }
3188
+ for (let i = 0; i < readings.length; i += 1) {
3189
+ const offered = _optionalChain([readings, 'access', _55 => _55[i], 'optionalAccess', _56 => _56.augmented]);
3190
+ const spanKey = readingKeys[i];
3191
+ if (offered !== void 0 && spanKey !== void 0 && resolvesOntoDominant(readings, i, spanKey)) {
3192
+ readings[i] = offered;
3193
+ }
3194
+ }
3195
+ for (let i = 0; i < readings.length; i += 1) {
3196
+ const inferred = readings[i];
3197
+ const bounds = readingBounds[i];
3198
+ if (!inferred || bounds === void 0) {
3199
+ continue;
3200
+ }
3201
+ const { start, end } = bounds;
3202
+ const last = segments[segments.length - 1];
3203
+ if (last && sameChord(last.chord, inferred.chord) && Math.abs(last.endBeat - start) < _chunkS5BKZZGMcjs.BEAT_EPS) {
3204
+ const lastLength = last.endBeat - last.startBeat;
3205
+ const length = end - start;
3206
+ const lastConfidence = _nullishCoalesce(segmentConfidence[segmentConfidence.length - 1], () => ( 0));
3207
+ segmentConfidence[segmentConfidence.length - 1] = (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);
3208
+ last.endBeat = end;
3209
+ } else {
3210
+ segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });
3211
+ segmentConfidence.push(inferred.confidence);
3212
+ }
3213
+ }
3214
+ return {
3215
+ result: {
3216
+ timeline: { at: segmentLookup(segments), segments },
3217
+ keys: attachPivots(keys, segments),
3218
+ prevailingKey,
3219
+ segmentConfidence
3220
+ },
3221
+ evidence: {
3222
+ grid,
3223
+ slotCount,
3224
+ totalBeats,
3225
+ segmentation,
3226
+ harmonicRhythm,
3227
+ keyGiven: opts.key !== void 0,
3228
+ slotWeights,
3229
+ tables,
3230
+ windows
3231
+ }
3232
+ };
3233
+ }
3234
+ function detectCadences(timeline, key) {
3235
+ assertChordTimeline(timeline);
3236
+ const keyAt = keyScaleAt(key);
3237
+ const hits = [];
3238
+ for (let i = 1; i < timeline.segments.length; i += 1) {
3239
+ const prev = timeline.segments[i - 1];
3240
+ const cur = timeline.segments[i];
3241
+ if (!prev || !cur) {
3242
+ continue;
3243
+ }
3244
+ if (Math.abs(cur.startBeat - prev.endBeat) > _chunkS5BKZZGMcjs.BEAT_EPS) {
3245
+ continue;
3246
+ }
3247
+ const before = timeline.segments[i - 2];
3248
+ const approach = before !== void 0 && Math.abs(prev.startBeat - before.endBeat) <= _chunkS5BKZZGMcjs.BEAT_EPS ? before.chord : void 0;
3249
+ const key2 = keyAt(cur.startBeat);
3250
+ const cadence = detectCadence(prev.chord, cur.chord, key2, {
3251
+ ...approach === void 0 ? {} : { approach }
3252
+ });
3253
+ if (cadence.type === null) {
3254
+ continue;
3255
+ }
3256
+ if (cadence.type === "half" && isCadentialSixFour(prev.chord, cur.chord, key2)) {
3257
+ const next = timeline.segments[i + 1];
3258
+ if (next !== void 0 && Math.abs(next.startBeat - cur.endBeat) <= _chunkS5BKZZGMcjs.BEAT_EPS) {
3259
+ const onward = detectCadence(cur.chord, next.chord, keyAt(next.startBeat)).type;
3260
+ if (onward === "authentic" || onward === "deceptive") {
3261
+ continue;
3262
+ }
3263
+ }
3264
+ }
3265
+ hits.push({ atBeat: cur.startBeat, cadence, from: prev.chord, to: cur.chord });
3266
+ }
3267
+ return hits;
3268
+ }
3269
+
3270
+
3271
+
3272
+
3273
+
3274
+
3275
+
3276
+
3277
+
3278
+
3279
+
3280
+
3281
+
3282
+
3283
+
3284
+
3285
+
3286
+
3287
+
3288
+
3289
+
3290
+
3291
+
3292
+
3293
+
3294
+
3295
+
3296
+
3297
+
3298
+
3299
+
3300
+
3301
+
3302
+
3303
+
3304
+
3305
+
3306
+
3307
+
3308
+
3309
+
3310
+
3311
+
3312
+
3313
+
3314
+
3315
+
3316
+
3317
+
3318
+
3319
+
3320
+
3321
+
3322
+
3323
+
3324
+
3325
+
3326
+
3327
+
3328
+ exports.HUMANIZE_ADJACENCY = HUMANIZE_ADJACENCY; exports.sameInstant = sameInstant; exports.adjacent = adjacent; exports.hasEnded = hasEnded; exports.windowWeights = windowWeights; exports.MODAL_SCALE_NAMES = MODAL_SCALE_NAMES; 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.isCadentialSixFour = isCadentialSixFour; exports.detectCadence = detectCadence; exports.isBorrowedChord = isBorrowedChord; exports.borrowedSource = borrowedSource; exports.isAppliedDominant = isAppliedDominant; 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; exports.clamp01 = clamp01; exports.floorMod = floorMod; exports.firstSoundingBeat = firstSoundingBeat; exports.lastBarOf = lastBarOf; exports.sliceBars = sliceBars; exports.weightSimilarity = weightSimilarity; exports.harmonicNovelty = harmonicNovelty; exports.combineEvidence = combineEvidence; exports.gridForNotes = gridForNotes; exports.barGridStart = barGridStart; exports.keyTimelineFromNotes = keyTimelineFromNotes; exports.detectModulations = detectModulations; exports.attachPivots = attachPivots; exports.keyLookup = keyLookup; exports.prevailingKeyOf = prevailingKeyOf; exports.keyScaleAt = keyScaleAt; exports.toVoiceNotes = toVoiceNotes; exports.analyzeVoice = analyzeVoice; exports.assertChordTimeline = assertChordTimeline; exports.chordTimelineFromChords = chordTimelineFromChords; exports.chordAtBeat = chordAtBeat; exports.segmentStartingAt = segmentStartingAt; exports.chordTimelineFromNotes = chordTimelineFromNotes; exports.dirtySlotsFor = dirtySlotsFor; exports.analyzeTimeline = analyzeTimeline; exports.detectCadences = detectCadences;
3329
+ //# sourceMappingURL=chunk-SGLEQVJH.cjs.map