@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
@@ -24,13 +24,23 @@
24
24
 
25
25
 
26
26
 
27
- var _chunkQ2WGKJNNcjs = require('./chunk-Q2WGKJNN.cjs');
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+ var _chunkS5BKZZGMcjs = require('./chunk-S5BKZZGM.cjs');
28
37
 
29
38
  // src/theory/scale/masks.ts
30
39
  function maskFromOffsets(offsets) {
40
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, offsets, "offsets");
31
41
  let mask = 1;
32
42
  for (const [index, offset] of offsets.entries()) {
33
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, offset, `offsets[${index}]`);
43
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, offset, `offsets[${index}]`);
34
44
  mask |= 1 << (offset % 12 + 12) % 12;
35
45
  }
36
46
  return mask;
@@ -51,6 +61,12 @@ var WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);
51
61
  var OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);
52
62
  var OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);
53
63
  var CHROMATIC_MASK = 4095;
64
+ function assertModeMask(modeMask12, name = "modeMask12") {
65
+ return _chunkS5BKZZGMcjs.assertInteger.call(void 0, modeMask12, name, 1, CHROMATIC_MASK);
66
+ }
67
+ function maskOf(scale, name = "key") {
68
+ return assertModeMask(_chunkS5BKZZGMcjs.assertRecord.call(void 0, scale, name).modeMask12, `${name}.modeMask12`);
69
+ }
54
70
  var LYDIAN_DOMINANT_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 10]);
55
71
  var MIXOLYDIAN_B13_MASK = maskFromOffsets([0, 2, 4, 5, 7, 8, 10]);
56
72
  var LOCRIAN_NATURAL2_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 10]);
@@ -156,6 +172,9 @@ var SCALE_ALIASES = Object.freeze({
156
172
  poorvi: "purvi"
157
173
  });
158
174
  function resolveScaleName(name) {
175
+ if (typeof name !== "string") {
176
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`scale name must be a string; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, name)}`);
177
+ }
159
178
  if (Object.hasOwn(NAMED_SCALES, name)) {
160
179
  return name;
161
180
  }
@@ -174,61 +193,158 @@ function namedScaleMask(name) {
174
193
  function requireScaleMask(name, label = "scale") {
175
194
  const mask = namedScaleMask(name);
176
195
  if (mask === void 0) {
177
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown ${label}: ${String(name)}`);
196
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unknown ${label}: ${String(name)}`);
178
197
  }
179
198
  return mask;
180
199
  }
200
+ var VARIANT_MASKS = {
201
+ major: MAJOR_MASK,
202
+ natural: NATURAL_MINOR_MASK,
203
+ harmonic: HARMONIC_MINOR_MASK,
204
+ melodic: MELODIC_MINOR_MASK
205
+ };
206
+ function isMinorMask(modeMask12) {
207
+ return (modeMask12 >> 3 & 1) === 1 && (modeMask12 >> 4 & 1) === 0;
208
+ }
209
+ function variantOfMask(modeMask12) {
210
+ assertModeMask(modeMask12);
211
+ for (const [variant, mask] of Object.entries(VARIANT_MASKS)) {
212
+ if (mask === modeMask12) {
213
+ return variant;
214
+ }
215
+ }
216
+ return "modal";
217
+ }
218
+ var KEY_VARIANTS = [
219
+ ...Object.keys(VARIANT_MASKS),
220
+ "modal"
221
+ ];
222
+ function assertKeyVariant(variant, modeMask12) {
223
+ _chunkS5BKZZGMcjs.assertOneOf.call(void 0, variant, KEY_VARIANTS, "variant");
224
+ assertModeMask(modeMask12);
225
+ const named = VARIANT_MASKS[variant];
226
+ const matches = named === void 0 ? !Object.values(VARIANT_MASKS).includes(modeMask12) : named === modeMask12;
227
+ if (!matches) {
228
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
229
+ `variant ${variant} does not match the scale mask ${modeMask12}; pass the scale that variant names, or omit the variant`
230
+ );
231
+ }
232
+ }
181
233
 
182
- // src/theory/scale/key.ts
183
- function majorKey(rootPc) {
184
- return { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc), modeMask12: MAJOR_MASK };
234
+ // src/theory/scale/scales.ts
235
+ function majorScale(rootPc) {
236
+ return { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc), modeMask12: MAJOR_MASK };
185
237
  }
186
- function minorKey(rootPc) {
187
- return { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc), modeMask12: NATURAL_MINOR_MASK };
238
+ function minorScale(rootPc) {
239
+ return { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc), modeMask12: NATURAL_MINOR_MASK };
188
240
  }
189
- function scaleByName(name, rootPc) {
190
- return { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc), modeMask12: requireScaleMask(name) };
241
+ function namedScale(name, rootPc) {
242
+ return { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc), modeMask12: requireScaleMask(name) };
243
+ }
244
+
245
+ // src/theory/scale/name.ts
246
+ function scaleWordKey(text) {
247
+ return text.replace(/[^a-z0-9]/gi, "").toLowerCase();
248
+ }
249
+ var BUILT_IN_SCALE_NAMES = [
250
+ ...Object.keys(NAMED_SCALES),
251
+ ...Object.keys(WORLD_SCALES),
252
+ ...Object.keys(SCALE_ALIASES)
253
+ ];
254
+ var SCALE_BY_WORD = new Map(
255
+ BUILT_IN_SCALE_NAMES.map((name) => [scaleWordKey(name), name])
256
+ );
257
+ function scaleNameOfMask(mask) {
258
+ for (const name of Object.keys(NAMED_SCALES)) {
259
+ if (NAMED_SCALES[name] === mask) {
260
+ return name;
261
+ }
262
+ }
263
+ for (const name of Object.keys(WORLD_SCALES)) {
264
+ if (WORLD_SCALES[name] === mask) {
265
+ return name;
266
+ }
267
+ }
268
+ return void 0;
269
+ }
270
+ function bareTonic(note) {
271
+ return { letter: note.letter, alter: note.alter };
272
+ }
273
+ function tryNamedScaleKey(text) {
274
+ const trimmed = text.trim();
275
+ const separator = trimmed.search(/\s/);
276
+ if (separator < 0) {
277
+ return null;
278
+ }
279
+ const name = SCALE_BY_WORD.get(scaleWordKey(trimmed.slice(separator)));
280
+ if (name === void 0) {
281
+ return null;
282
+ }
283
+ const tonic = _chunkS5BKZZGMcjs.tryParseNote.call(void 0, trimmed.slice(0, separator));
284
+ if (!tonic.ok) {
285
+ return null;
286
+ }
287
+ const scale = namedScale(name, _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, tonic.value));
288
+ return { scale, tonic: bareTonic(tonic.value), variant: variantOfMask(scale.modeMask12) };
289
+ }
290
+ function tryResolveKeyName(text, opts) {
291
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
292
+ if (typeof text === "string" && (asked.system === void 0 || asked.system === "english")) {
293
+ const named = tryNamedScaleKey(text);
294
+ if (named !== null) {
295
+ return { ok: true, value: named };
296
+ }
297
+ }
298
+ const parsed = _chunkS5BKZZGMcjs.tryParseKeyName.call(void 0, text, asked);
299
+ if (!parsed.ok) {
300
+ return parsed;
301
+ }
302
+ const tonic = bareTonic(parsed.value.tonic);
303
+ const rootPc = _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, tonic);
304
+ return parsed.value.mode === "major" ? { ok: true, value: { scale: majorScale(rootPc), tonic, variant: "major" } } : { ok: true, value: { scale: minorScale(rootPc), tonic, variant: "natural" } };
305
+ }
306
+ function resolveKeyName(text, opts) {
307
+ return _chunkS5BKZZGMcjs.unwrapParse.call(void 0, tryResolveKeyName(text, opts));
191
308
  }
192
309
 
193
310
  // src/theory/scale/coerce.ts
194
311
  function normalizedKeyScale(scale) {
195
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, scale.modeMask12, "key.modeMask12", 1, CHROMATIC_MASK);
312
+ assertModeMask(scale.modeMask12, "key.modeMask12");
196
313
  if ((scale.modeMask12 & 1) === 0) {
197
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
314
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
198
315
  `key.modeMask12 must include its root; received ${scale.modeMask12}`
199
316
  );
200
317
  }
201
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, scale.rootPc, "key.rootPc");
202
- return { rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, scale.rootPc), modeMask12: scale.modeMask12 };
318
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, scale.rootPc, "key.rootPc");
319
+ return { rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, scale.rootPc), modeMask12: scale.modeMask12 };
203
320
  }
204
321
  function toKeyScale(value) {
205
322
  if (typeof value === "string") {
206
- const name = _chunkQ2WGKJNNcjs.parseKeyName.call(void 0, value);
207
- const rootPc = _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, name.tonic);
208
- return name.mode === "major" ? majorKey(rootPc) : minorKey(rootPc);
323
+ return resolveKeyName(value).scale;
209
324
  }
210
325
  if (typeof value === "object" && value !== null) {
211
- const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON().scale : value;
326
+ const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON().scale : "scale" in value ? value.scale : value;
212
327
  if (typeof data === "object" && data !== null) {
213
328
  return normalizedKeyScale(data);
214
329
  }
215
330
  }
216
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`key must be a key name or a key/scale; received ${typeof value}`);
331
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`key must be a key name or a key/scale; received ${typeof value}`);
217
332
  }
218
333
 
219
334
  // src/theory/scale/degrees.ts
220
335
  function scaleOffset(pitch, key) {
221
- return (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitch) - _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc) + 12) % 12;
336
+ return (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch) - _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc) + 12) % 12;
222
337
  }
223
338
  function inScale(pitch, scale) {
224
339
  return (scale.modeMask12 >> scaleOffset(pitch, scale) & 1) === 1;
225
340
  }
226
341
  function isScaleTone(pitch, key) {
227
- return inScale(pitch, key);
342
+ return inScale(pitch, toKeyScale(key));
228
343
  }
229
344
  function nearestScaleTone(pitch, key) {
230
345
  const scale = toKeyScale(key);
231
- const base = _chunkQ2WGKJNNcjs.clampToMidi.call(void 0, Math.round(pitch), "pitch");
346
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, pitch, "pitch");
347
+ const base = _chunkS5BKZZGMcjs.clampToMidi.call(void 0, Math.round(pitch), "pitch");
232
348
  for (let distance = 0; distance < 12; distance += 1) {
233
349
  const lower = base - distance;
234
350
  if (lower >= 0 && inScale(lower, scale)) {
@@ -251,12 +367,16 @@ function scaleOffsets(key) {
251
367
  return offsets;
252
368
  }
253
369
  function scaleLadderPosition(pitch, key) {
370
+ return ladderPosition(pitch, toKeyScale(key));
371
+ }
372
+ function ladderPosition(pitch, key) {
373
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, pitch, "pitch");
254
374
  const offsets = scaleOffsets(key);
255
375
  const size = offsets.length;
256
376
  if (size === 0) {
257
377
  return { rung: 0, offset: 0 };
258
378
  }
259
- const relative = pitch - _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
379
+ const relative = pitch - _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
260
380
  const octave = Math.floor(relative / 12);
261
381
  const within = relative - octave * 12;
262
382
  let degree = 0;
@@ -268,22 +388,27 @@ function scaleLadderPosition(pitch, key) {
268
388
  return { rung: octave * size + degree, offset: within - (_nullishCoalesce(offsets[degree], () => ( 0))) };
269
389
  }
270
390
  function scaleLadderPitch(rung, key) {
391
+ return ladderPitch(rung, toKeyScale(key));
392
+ }
393
+ function ladderPitch(rung, key) {
394
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, rung, "rung");
271
395
  const offsets = scaleOffsets(key);
272
396
  const size = offsets.length;
273
397
  if (size === 0) {
274
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
398
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
275
399
  }
276
400
  const octave = Math.floor(rung / size);
277
401
  const degree = rung - octave * size;
278
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc) + octave * 12 + (_nullishCoalesce(offsets[degree], () => ( 0)));
402
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc) + octave * 12 + (_nullishCoalesce(offsets[degree], () => ( 0)));
279
403
  }
280
404
  function shiftByScaleDegrees(pitch, degrees, key) {
281
- const steps = Math.trunc(degrees);
405
+ const scale = toKeyScale(key);
406
+ const steps = Math.trunc(_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, degrees, "degrees"));
282
407
  if (steps === 0) {
283
408
  return pitch;
284
409
  }
285
- const { rung, offset } = scaleLadderPosition(pitch, key);
286
- return scaleLadderPitch(rung + steps, key) + offset;
410
+ const { rung, offset } = ladderPosition(pitch, scale);
411
+ return ladderPitch(rung + steps, scale) + offset;
287
412
  }
288
413
  function pitchToScaleDegree(pitch, key) {
289
414
  const scale = toKeyScale(key);
@@ -300,19 +425,11 @@ function pitchToScaleDegree(pitch, key) {
300
425
  return degree;
301
426
  }
302
427
  function diatonicPitchClasses(key) {
303
- const scale = key;
304
- const root = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, scale.rootPc);
305
- const pcs = [];
306
- for (let n = 0; n < 12; n += 1) {
307
- if ((scale.modeMask12 >> n & 1) === 1) {
308
- pcs.push((root + n) % 12);
309
- }
310
- }
311
- return pcs.sort((a, b) => a - b);
428
+ return scaleTonesInDegreeOrder(key).sort((a, b) => a - b);
312
429
  }
313
430
  function scaleTonesInDegreeOrder(key) {
314
- const scale = key;
315
- const root = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, scale.rootPc);
431
+ const scale = toKeyScale(key);
432
+ const root = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, scale.rootPc);
316
433
  const pcs = [];
317
434
  for (let n = 0; n < 12; n += 1) {
318
435
  if ((scale.modeMask12 >> n & 1) === 1) {
@@ -348,64 +465,57 @@ var DIATONIC_MODE_OFFSETS = /* @__PURE__ */ new Map([
348
465
  [rotateMask(MAJOR_MASK, 11), -5]
349
466
  // locrian
350
467
  ]);
351
- function hasMinorThird(modeMask12) {
352
- return (modeMask12 >> 3 & 1) === 1 && (modeMask12 >> 4 & 1) === 0;
353
- }
354
468
  function modeOffset(key) {
355
469
  const diatonic = DIATONIC_MODE_OFFSETS.get(key.modeMask12);
356
470
  if (diatonic !== void 0) {
357
471
  return diatonic;
358
472
  }
359
- return hasMinorThird(key.modeMask12) ? MINOR_MODE_OFFSET : 0;
473
+ return isMinorMask(key.modeMask12) ? MINOR_MODE_OFFSET : 0;
360
474
  }
361
475
  function isSignatureKey(key) {
362
- return DIATONIC_MODE_OFFSETS.has(key.modeMask12) || key.modeMask12 === HARMONIC_MINOR_MASK || key.modeMask12 === MELODIC_MINOR_MASK;
476
+ const mask = maskOf(key);
477
+ return DIATONIC_MODE_OFFSETS.has(mask) || mask === HARMONIC_MINOR_MASK || mask === MELODIC_MINOR_MASK;
363
478
  }
364
479
  function keySignatureFifths(tonic, key) {
365
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
366
- const scale = key;
367
- const letter = _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, note.letter);
368
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, note.alter, "tonic.alter");
369
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, scale.modeMask12, "key.modeMask12", 1, 4095);
480
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
481
+ const scale = toKeyScale(key);
482
+ const letter = _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, note.letter);
483
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, note.alter, "tonic.alter");
484
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, scale.modeMask12, "key.modeMask12", 1, 4095);
370
485
  return (_nullishCoalesce(LETTER_FIFTHS[letter], () => ( 0))) + FIFTHS_PER_ALTERATION * note.alter + modeOffset(scale);
371
486
  }
372
487
  function keyFromFifths(fifths, mode = "major") {
373
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, fifths, "fifths", -MAX_FIFTHS, MAX_FIFTHS);
374
- const which = _chunkQ2WGKJNNcjs.assertOneOf.call(void 0, mode, ["major", "minor"], "mode");
488
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, fifths, "fifths", -MAX_FIFTHS, MAX_FIFTHS);
489
+ const which = _chunkS5BKZZGMcjs.assertOneOf.call(void 0, mode, ["major", "minor"], "mode");
375
490
  const index = fifths + 1 + (which === "minor" ? -MINOR_MODE_OFFSET : 0);
376
491
  const letter = _nullishCoalesce(FIFTHS_LETTERS[(index % 7 + 7) % 7], () => ( 0));
377
492
  const alter = Math.floor(index / 7);
378
- const rootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, letter) + alter);
493
+ const rootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, letter) + alter);
494
+ const scale = which === "minor" ? minorScale(rootPc) : majorScale(rootPc);
379
495
  return {
380
496
  tonic: { letter, alter },
381
- key: which === "minor" ? minorKey(rootPc) : majorKey(rootPc)
497
+ scale,
498
+ variant: variantOfMask(scale.modeMask12)
382
499
  };
383
500
  }
384
501
 
385
502
  // src/theory/scale/relations.ts
386
503
  var ENHARMONIC_FIFTHS = 12;
387
504
  var MAX_CONVENTIONAL_FIFTHS = 7;
388
- var DOUBLE_ACCIDENTAL = 2;
389
505
  var MINOR_TONIC_FIFTHS = 3;
390
506
  var FLATTEST_TONIC_FIFTHS = -MAX_CONVENTIONAL_FIFTHS;
391
507
  var SHARPEST_TONIC_FIFTHS = MAX_CONVENTIONAL_FIFTHS + MINOR_TONIC_FIFTHS;
392
- function isMinorMode(modeMask12) {
393
- return (modeMask12 >> 3 & 1) === 1 && (modeMask12 >> 4 & 1) === 0;
394
- }
395
- function assertModeMask(key, name = "key") {
396
- return _chunkQ2WGKJNNcjs.assertInteger.call(void 0, key.modeMask12, `${name}.modeMask12`, 1, CHROMATIC_MASK);
397
- }
398
508
  function modeOf(key) {
399
- return isMinorMode(assertModeMask(key)) ? "minor" : "major";
509
+ return isMinorMask(maskOf(key)) ? "minor" : "major";
400
510
  }
401
511
  function oppositeMode(mode) {
402
512
  return mode === "major" ? "minor" : "major";
403
513
  }
404
514
  function soundsLike(a, b) {
405
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, a.rootPc) === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, b.rootPc) && isMinorMode(a.modeMask12) === isMinorMode(b.modeMask12);
515
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, a.rootPc) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, b.rootPc) && isMinorMask(a.modeMask12) === isMinorMask(b.modeMask12);
406
516
  }
407
517
  function spelledLike(a, b) {
408
- return _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, a.letter) === _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, b.letter) && a.alter === b.alter;
518
+ return _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, a.letter) === _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, b.letter) && a.alter === b.alter;
409
519
  }
410
520
  function tonicCost(tonic, key) {
411
521
  let doubles = 0;
@@ -413,7 +523,7 @@ function tonicCost(tonic, key) {
413
523
  for (const note of spellScale(tonic, key)) {
414
524
  const alter = Math.abs(note.alter);
415
525
  accidentals += alter;
416
- if (alter >= DOUBLE_ACCIDENTAL) {
526
+ if (alter >= _chunkS5BKZZGMcjs.MAX_NAME_ACCIDENTALS) {
417
527
  doubles += 1;
418
528
  }
419
529
  }
@@ -429,14 +539,14 @@ function spellsBetter(a, b, signatureKey) {
429
539
  return false;
430
540
  }
431
541
  function spelledKeyOf(key) {
432
- const scale = key;
433
- const rootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, scale.rootPc);
542
+ const scale = _chunkS5BKZZGMcjs.assertRecord.call(void 0, key, "key");
543
+ const rootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, scale.rootPc);
434
544
  const signatureKey = isSignatureKey(scale);
435
545
  let tonic = keyFromFifths(0).tonic;
436
546
  let best;
437
547
  for (let fifths = FLATTEST_TONIC_FIFTHS; fifths <= SHARPEST_TONIC_FIFTHS; fifths += 1) {
438
548
  const candidate = keyFromFifths(fifths).tonic;
439
- if (_chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, candidate) !== rootPc) {
549
+ if (_chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, candidate) !== rootPc) {
440
550
  continue;
441
551
  }
442
552
  const cost = tonicCost(candidate, scale);
@@ -445,73 +555,76 @@ function spelledKeyOf(key) {
445
555
  best = cost;
446
556
  }
447
557
  }
448
- return { tonic, key };
558
+ return { tonic, scale: key, variant: variantOfMask(key.modeMask12) };
449
559
  }
450
560
  function relativeKeyOf(tonic, key) {
451
- const scale = toKeyScale(key);
452
- return keyFromFifths(keySignatureFifths(tonic, scale), oppositeMode(modeOf(scale)));
561
+ const mode = modeOf(toKeyScale(key));
562
+ return keyFromFifths(tonicFifths(_chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic), mode), oppositeMode(mode));
453
563
  }
454
564
  function parallelKeyOf(tonic, key) {
455
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
565
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
456
566
  const mode = oppositeMode(modeOf(toKeyScale(key)));
457
- const rootPc = _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, note);
567
+ const rootPc = _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, note);
568
+ const scale = mode === "minor" ? minorScale(rootPc) : majorScale(rootPc);
458
569
  return {
459
- tonic: { letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, note.letter), alter: note.alter },
460
- key: mode === "minor" ? minorKey(rootPc) : majorKey(rootPc)
570
+ tonic: { letter: _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, note.letter), alter: note.alter },
571
+ scale,
572
+ variant: variantOfMask(scale.modeMask12)
461
573
  };
462
574
  }
463
575
  function tonicPosition(tonic) {
464
- return keySignatureFifths(tonic, majorKey(_chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, tonic)));
576
+ return keySignatureFifths(tonic, majorScale(_chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, tonic)));
465
577
  }
466
578
  function tonicFifths(tonic, mode) {
467
579
  return tonicPosition(tonic) - (mode === "minor" ? MINOR_TONIC_FIFTHS : 0);
468
580
  }
469
581
  function isWrittenTonic(tonic, key) {
470
582
  const position = tonicPosition(tonic);
471
- if (position < FLATTEST_TONIC_FIFTHS || position > SHARPEST_TONIC_FIFTHS) {
472
- return false;
583
+ if (isSignatureKey(key)) {
584
+ return Math.abs(position) <= MAX_FIFTHS && Math.abs(keySignatureFifths(tonic, key)) <= MAX_CONVENTIONAL_FIFTHS;
473
585
  }
474
- return isSignatureKey(key) ? Math.abs(keySignatureFifths(tonic, key)) <= MAX_CONVENTIONAL_FIFTHS : tonicCost(tonic, key).doubles === 0;
586
+ return position >= FLATTEST_TONIC_FIFTHS && position <= SHARPEST_TONIC_FIFTHS && tonicCost(tonic, { rootPc: _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, tonic), modeMask12: key.modeMask12 }).doubles === 0;
475
587
  }
476
588
  function dominantKeyOf(tonic, key) {
477
589
  const mode = modeOf(toKeyScale(key));
478
- return keyFromFifths(tonicFifths(_chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic), mode) + 1, mode);
590
+ return keyFromFifths(tonicFifths(_chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic), mode) + 1, mode);
479
591
  }
480
592
  function subdominantKeyOf(tonic, key) {
481
593
  const mode = modeOf(toKeyScale(key));
482
- return keyFromFifths(tonicFifths(_chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic), mode) - 1, mode);
594
+ return keyFromFifths(tonicFifths(_chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic), mode) - 1, mode);
483
595
  }
484
596
  function enharmonicKeyOf(tonic, key) {
485
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
597
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
486
598
  const scale = toKeyScale(key);
487
- assertModeMask(scale);
599
+ maskOf(scale);
488
600
  for (const position of [
489
601
  tonicPosition(note) - ENHARMONIC_FIFTHS,
490
602
  tonicPosition(note) + ENHARMONIC_FIFTHS
491
603
  ]) {
492
- if (position < FLATTEST_TONIC_FIFTHS || position > SHARPEST_TONIC_FIFTHS) {
604
+ if (Math.abs(position) > MAX_FIFTHS) {
493
605
  continue;
494
606
  }
495
- const spelled = keyFromFifths(position).tonic;
496
- if (!isWrittenTonic(spelled, scale)) {
607
+ const spelled2 = keyFromFifths(position).tonic;
608
+ if (!isWrittenTonic(spelled2, scale)) {
497
609
  continue;
498
610
  }
499
611
  return {
500
- tonic: spelled,
612
+ tonic: spelled2,
501
613
  // The mask is the caller's; only the root is re-read, so a tonic that did
502
614
  // not spell the mask's own root still comes back consistent.
503
- key: { rootPc: _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, spelled), modeMask12: scale.modeMask12 }
615
+ scale: { rootPc: _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, spelled2), modeMask12: scale.modeMask12 },
616
+ variant: variantOfMask(scale.modeMask12)
504
617
  };
505
618
  }
506
619
  return null;
507
620
  }
508
621
  function relativeOfDominantKeyOf(tonic, key) {
509
622
  const dominant = dominantKeyOf(tonic, key);
510
- return relativeKeyOf(dominant.tonic, dominant.key);
623
+ return relativeKeyOf(dominant.tonic, dominant.scale);
511
624
  }
512
625
  function relativeOfSubdominantKeyOf(tonic, key) {
513
626
  const subdominant = subdominantKeyOf(tonic, key);
514
- return relativeKeyOf(subdominant.tonic, subdominant.key);
627
+ return relativeKeyOf(subdominant.tonic, subdominant.scale);
515
628
  }
516
629
  var CLOSELY_RELATED = [
517
630
  { relation: "relative", of: relativeKeyOf },
@@ -522,24 +635,73 @@ var CLOSELY_RELATED = [
522
635
  { relation: "relativeOfSubdominant", of: relativeOfSubdominantKeyOf }
523
636
  ];
524
637
  function relatedKeysOf(tonic, key) {
525
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
638
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
526
639
  const scale = toKeyScale(key);
527
640
  return CLOSELY_RELATED.map(({ relation, of }) => ({ ...of(note, scale), relation }));
528
641
  }
529
642
  function keyRelationBetween(a, b) {
530
- assertModeMask(a.key, "a.key");
531
- assertModeMask(b.key, "b.key");
532
- if (soundsLike(a.key, b.key)) {
533
- return spelledLike(a.tonic, b.tonic) ? "same" : "enharmonic";
643
+ const from = _chunkS5BKZZGMcjs.assertRecord.call(void 0, a, "a");
644
+ const to = _chunkS5BKZZGMcjs.assertRecord.call(void 0, b, "b");
645
+ maskOf(from.scale, "a.scale");
646
+ maskOf(to.scale, "b.scale");
647
+ if (soundsLike(from.scale, to.scale)) {
648
+ return spelledLike(from.tonic, to.tonic) ? "same" : "enharmonic";
534
649
  }
535
650
  for (const { relation, of } of CLOSELY_RELATED) {
536
- if (soundsLike(of(a.tonic, a.key).key, b.key)) {
651
+ if (soundsLike(of(from.tonic, from.scale).scale, to.scale)) {
537
652
  return relation;
538
653
  }
539
654
  }
540
655
  return null;
541
656
  }
542
657
 
658
+ // src/theory/scale/identity.ts
659
+ function carriesIdentity(value) {
660
+ return typeof value === "object" && value !== null;
661
+ }
662
+ function carriedTonic(tonic, scale) {
663
+ if (_chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, tonic) !== scale.rootPc) {
664
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
665
+ `tonic ${_chunkS5BKZZGMcjs.formatNote.call(void 0, tonic)} does not match the scale root pitch class ${scale.rootPc}; pass a tonic that spells the scale root, or omit it to have one chosen`
666
+ );
667
+ }
668
+ return { letter: tonic.letter, alter: tonic.alter };
669
+ }
670
+ function carriedVariant(variant, scale) {
671
+ assertKeyVariant(variant, scale.modeMask12);
672
+ return variant;
673
+ }
674
+ function resolveKey(value) {
675
+ if (typeof value === "string") {
676
+ return resolveKeyName(value);
677
+ }
678
+ const scale = toKeyScale(value);
679
+ const carried = carriesIdentity(value) && "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
680
+ const tonic = carriesIdentity(carried) && carried.tonic !== void 0 ? carriedTonic(carried.tonic, scale) : spelledKeyOf(scale).tonic;
681
+ const variant = carriesIdentity(carried) && carried.variant !== void 0 ? carriedVariant(carried.variant, scale) : variantOfMask(scale.modeMask12);
682
+ return { scale, tonic, variant };
683
+ }
684
+ function scaleOf(key) {
685
+ const scale = _chunkS5BKZZGMcjs.assertRecord.call(void 0, key, "key").scale;
686
+ maskOf(scale, "key.scale");
687
+ return scale;
688
+ }
689
+
690
+ // src/theory/scale/key.ts
691
+ function spelled(scale) {
692
+ const { tonic, variant } = spelledKeyOf(scale);
693
+ return { ...scale, tonic, variant };
694
+ }
695
+ function majorKey(rootPc) {
696
+ return spelled(majorScale(rootPc));
697
+ }
698
+ function minorKey(rootPc) {
699
+ return spelled(minorScale(rootPc));
700
+ }
701
+ function scaleByName(name, rootPc) {
702
+ return spelled(namedScale(name, rootPc));
703
+ }
704
+
543
705
  // src/theory/scale/system.ts
544
706
  var SCALE_SYSTEMS = Object.freeze({
545
707
  major: "common-practice",
@@ -603,11 +765,11 @@ function scaleSystemOf(scale) {
603
765
  if (typeof scale === "string") {
604
766
  const canonical = resolveScaleName(scale);
605
767
  if (canonical === void 0) {
606
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown scale: ${scale}`);
768
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unknown scale: ${scale}`);
607
769
  }
608
770
  return SCALE_SYSTEMS[canonical];
609
771
  }
610
- return SYSTEM_BY_MASK.get(scale.modeMask12);
772
+ return SYSTEM_BY_MASK.get(maskOf(scale, "scale"));
611
773
  }
612
774
  function supportsFunctionalHarmony(scale) {
613
775
  return scaleSystemOf(scale) !== "non-functional";
@@ -653,6 +815,7 @@ var BASE_TONES = {
653
815
  var SEVENTH_SEMITONES = { maj7: 11, min7: 10, dim7: 9 };
654
816
  var DEGREE_SEMITONES = { 6: 9, 9: 14, 11: 17, 13: 21 };
655
817
  var PERFECT_FIFTH = 7;
818
+ var FIFTH_KEY = "5";
656
819
  var OMITTABLE_DEGREES = [1, 3, 5];
657
820
  var BASE_QUALITY = {
658
821
  maj: "maj",
@@ -676,114 +839,149 @@ function parts(base, spec = {}) {
676
839
  }
677
840
  return built;
678
841
  }
679
- var QUALITY_SPECS = {
680
- maj: parts("maj"),
681
- min: parts("min"),
682
- dim: parts("dim"),
683
- aug: parts("aug"),
684
- majb5: parts("maj", { alt: [[5, -1]] }),
685
- maj7: parts("maj", { seventh: "maj7" }),
686
- min7: parts("min", { seventh: "min7" }),
687
- dom7: parts("maj", { seventh: "min7" }),
688
- dim7: parts("dim", { seventh: "dim7" }),
689
- m7b5: parts("dim", { seventh: "min7" }),
690
- minMaj7: parts("min", { seventh: "maj7" }),
691
- minMaj9: parts("min", { seventh: "maj7", alt: [[9, 0]] }),
692
- minMaj11: parts("min", {
693
- seventh: "maj7",
694
- alt: [
695
- [9, 0],
696
- [11, 0]
697
- ]
698
- }),
699
- minMaj13: parts("min", {
700
- seventh: "maj7",
701
- alt: [
702
- [9, 0],
703
- [13, 0]
704
- ]
705
- }),
706
- aug7: parts("aug", { seventh: "min7" }),
707
- augMaj7: parts("aug", { seventh: "maj7" }),
708
- "6": parts("maj", { add: [6] }),
709
- min6: parts("min", { add: [6] }),
710
- "6/9": parts("maj", { add: [6, 9] }),
711
- sus2: parts("sus2"),
712
- sus4: parts("sus4"),
713
- add9: parts("maj", { add: [9] }),
714
- add11: parts("maj", { add: [11] }),
715
- maj9: parts("maj", { seventh: "maj7", alt: [[9, 0]] }),
716
- min9: parts("min", { seventh: "min7", alt: [[9, 0]] }),
717
- dom9: parts("maj", { seventh: "min7", alt: [[9, 0]] }),
718
- "7b9": parts("maj", { seventh: "min7", alt: [[9, -1]] }),
719
- "7#9": parts("maj", { seventh: "min7", alt: [[9, 1]] }),
720
- "7#11": parts("maj", { seventh: "min7", alt: [[11, 1]] }),
721
- "7b13": parts("maj", { seventh: "min7", alt: [[13, -1]] }),
842
+ var QUALITY_SPECS = [
843
+ ["maj", parts("maj")],
844
+ ["min", parts("min")],
845
+ ["dim", parts("dim")],
846
+ ["aug", parts("aug")],
847
+ ["majb5", parts("maj", { alt: [[5, -1]] })],
848
+ ["maj7", parts("maj", { seventh: "maj7" })],
849
+ ["min7", parts("min", { seventh: "min7" })],
850
+ ["dom7", parts("maj", { seventh: "min7" })],
851
+ ["dim7", parts("dim", { seventh: "dim7" })],
852
+ ["m7b5", parts("dim", { seventh: "min7" })],
853
+ ["minMaj7", parts("min", { seventh: "maj7" })],
854
+ ["minMaj9", parts("min", { seventh: "maj7", alt: [[9, 0]] })],
855
+ [
856
+ "minMaj11",
857
+ parts("min", {
858
+ seventh: "maj7",
859
+ alt: [
860
+ [9, 0],
861
+ [11, 0]
862
+ ]
863
+ })
864
+ ],
865
+ [
866
+ "minMaj13",
867
+ parts("min", {
868
+ seventh: "maj7",
869
+ alt: [
870
+ [9, 0],
871
+ [13, 0]
872
+ ]
873
+ })
874
+ ],
875
+ ["aug7", parts("aug", { seventh: "min7" })],
876
+ ["augMaj7", parts("aug", { seventh: "maj7" })],
877
+ ["6", parts("maj", { add: [6] })],
878
+ ["min6", parts("min", { add: [6] })],
879
+ ["6/9", parts("maj", { add: [6, 9] })],
880
+ ["sus2", parts("sus2")],
881
+ ["sus4", parts("sus4")],
882
+ ["add9", parts("maj", { add: [9] })],
883
+ ["add11", parts("maj", { add: [11] })],
884
+ ["maj9", parts("maj", { seventh: "maj7", alt: [[9, 0]] })],
885
+ ["min9", parts("min", { seventh: "min7", alt: [[9, 0]] })],
886
+ ["dom9", parts("maj", { seventh: "min7", alt: [[9, 0]] })],
887
+ ["7b9", parts("maj", { seventh: "min7", alt: [[9, -1]] })],
888
+ ["7#9", parts("maj", { seventh: "min7", alt: [[9, 1]] })],
889
+ ["7#11", parts("maj", { seventh: "min7", alt: [[11, 1]] })],
890
+ ["7b13", parts("maj", { seventh: "min7", alt: [[13, -1]] })],
722
891
  // The eleventh chord as a chart voices it: the third gives way to the
723
892
  // eleventh a semitone above it rather than sounding against it.
724
- "11": parts("maj", {
725
- seventh: "min7",
726
- alt: [
727
- [9, 0],
728
- [11, 0]
729
- ],
730
- omit: [3]
731
- }),
732
- "13": parts("maj", {
733
- seventh: "min7",
734
- alt: [
735
- [9, 0],
736
- [13, 0]
737
- ]
738
- }),
739
- "5": parts("power"),
740
- "7sus4": parts("sus4", { seventh: "min7" }),
741
- "7b5": parts("maj", { seventh: "min7", alt: [[5, -1]] }),
893
+ [
894
+ "11",
895
+ parts("maj", {
896
+ seventh: "min7",
897
+ alt: [
898
+ [9, 0],
899
+ [11, 0]
900
+ ],
901
+ omit: [3]
902
+ })
903
+ ],
904
+ [
905
+ "13",
906
+ parts("maj", {
907
+ seventh: "min7",
908
+ alt: [
909
+ [9, 0],
910
+ [13, 0]
911
+ ]
912
+ })
913
+ ],
914
+ ["5", parts("power")],
915
+ ["7sus4", parts("sus4", { seventh: "min7" })],
916
+ ["7b5", parts("maj", { seventh: "min7", alt: [[5, -1]] })],
742
917
  // The altered dominant as it is voiced from a lead sheet: a dominant seventh
743
918
  // with a raised fifth and a raised ninth. The full seven-note altered stack
744
919
  // has no seven-letter spelling, so it is not what this symbol denotes.
745
- "7alt": parts("aug", { seventh: "min7", alt: [[9, 1]] }),
746
- "13b9": parts("maj", {
747
- seventh: "min7",
748
- alt: [
749
- [9, -1],
750
- [13, 0]
751
- ]
752
- }),
753
- maj13: parts("maj", {
754
- seventh: "maj7",
755
- alt: [
756
- [9, 0],
757
- [13, 0]
758
- ]
759
- }),
760
- "maj7#11": parts("maj", { seventh: "maj7", alt: [[11, 1]] }),
761
- min11: parts("min", {
762
- seventh: "min7",
763
- alt: [
764
- [9, 0],
765
- [11, 0]
766
- ]
767
- }),
768
- min13: parts("min", {
769
- seventh: "min7",
770
- alt: [
771
- [9, 0],
772
- [13, 0]
773
- ]
774
- }),
775
- minAdd9: parts("min", { add: [9] }),
776
- "min6/9": parts("min", { add: [6, 9] })
777
- };
920
+ ["7alt", parts("aug", { seventh: "min7", alt: [[9, 1]] })],
921
+ [
922
+ "13b9",
923
+ parts("maj", {
924
+ seventh: "min7",
925
+ alt: [
926
+ [9, -1],
927
+ [13, 0]
928
+ ]
929
+ })
930
+ ],
931
+ [
932
+ "maj13",
933
+ parts("maj", {
934
+ seventh: "maj7",
935
+ alt: [
936
+ [9, 0],
937
+ [13, 0]
938
+ ]
939
+ })
940
+ ],
941
+ ["maj7#11", parts("maj", { seventh: "maj7", alt: [[11, 1]] })],
942
+ [
943
+ "min11",
944
+ parts("min", {
945
+ seventh: "min7",
946
+ alt: [
947
+ [9, 0],
948
+ [11, 0]
949
+ ]
950
+ })
951
+ ],
952
+ [
953
+ "min13",
954
+ parts("min", {
955
+ seventh: "min7",
956
+ alt: [
957
+ [9, 0],
958
+ [13, 0]
959
+ ]
960
+ })
961
+ ],
962
+ ["minAdd9", parts("min", { add: [9] })],
963
+ ["min6/9", parts("min", { add: [6, 9] })]
964
+ ];
965
+ var QUALITY_ORDER = QUALITY_SPECS.map(([quality]) => quality);
966
+ var SPEC_BY_QUALITY = new Map(QUALITY_SPECS);
778
967
  function normalizeChordSpec(spec) {
779
968
  let base = spec.base;
780
969
  const alterations = /* @__PURE__ */ new Map();
781
970
  for (const alteration of _nullishCoalesce(spec.alterations, () => ( []))) {
782
- alterations.set(alteration.degree, alteration.alter);
971
+ const { degree, alter } = alteration;
972
+ if (degree === 5) {
973
+ const current = alterations.get(FIFTH_KEY);
974
+ if (current !== void 0 && current.alter <= alter) {
975
+ continue;
976
+ }
977
+ alterations.set(FIFTH_KEY, { degree, alter });
978
+ continue;
979
+ }
980
+ alterations.set(`${degree}:${alter}`, { degree, alter });
783
981
  }
784
982
  const additions = new Set(_nullishCoalesce(spec.additions, () => ( [])));
785
983
  const omissions = new Set(_nullishCoalesce(spec.omissions, () => ( [])));
786
- const fifth = alterations.get(5);
984
+ const fifth = _optionalChain([alterations, 'access', _2 => _2.get, 'call', _3 => _3(FIFTH_KEY), 'optionalAccess', _4 => _4.alter]);
787
985
  if (fifth !== void 0) {
788
986
  if (base === "aug") {
789
987
  base = "maj";
@@ -791,19 +989,19 @@ function normalizeChordSpec(spec) {
791
989
  base = "min";
792
990
  }
793
991
  if (fifth === 0) {
794
- alterations.delete(5);
992
+ alterations.delete(FIFTH_KEY);
795
993
  } else if (base === "maj" && fifth === 1) {
796
994
  base = "aug";
797
- alterations.delete(5);
995
+ alterations.delete(FIFTH_KEY);
798
996
  } else if (base === "min" && fifth === -1) {
799
997
  base = "dim";
800
- alterations.delete(5);
998
+ alterations.delete(FIFTH_KEY);
801
999
  }
802
1000
  }
803
1001
  if (spec.seventh === void 0) {
804
- for (const [degree, alter] of [...alterations]) {
1002
+ for (const [key, { degree, alter }] of [...alterations]) {
805
1003
  if (degree !== 5 && alter === 0) {
806
- alterations.delete(degree);
1004
+ alterations.delete(key);
807
1005
  additions.add(degree);
808
1006
  }
809
1007
  }
@@ -811,7 +1009,7 @@ function normalizeChordSpec(spec) {
811
1009
  const normalized = {
812
1010
  rootPc: spec.rootPc,
813
1011
  base,
814
- alterations: [...alterations].sort((a, b) => a[0] - b[0]).map(([degree, alter]) => ({ degree, alter })),
1012
+ alterations: [...alterations.values()].sort((a, b) => a.degree - b.degree || a.alter - b.alter),
815
1013
  additions: [...additions].sort((a, b) => a - b),
816
1014
  omissions: [...omissions].sort((a, b) => a - b)
817
1015
  };
@@ -851,10 +1049,7 @@ function chordSpecIntervals(spec) {
851
1049
  return specIntervals(assertChordSpec(spec));
852
1050
  }
853
1051
  var QUALITY_INTERVALS = Object.fromEntries(
854
- Object.keys(QUALITY_SPECS).map((quality) => [
855
- quality,
856
- specIntervals(normalizeChordSpec(QUALITY_SPECS[quality]))
857
- ])
1052
+ QUALITY_SPECS.map(([quality, spec]) => [quality, specIntervals(normalizeChordSpec(spec))])
858
1053
  );
859
1054
  function specKey(spec) {
860
1055
  const normalized = normalizeChordSpec(spec);
@@ -867,16 +1062,14 @@ function specKey(spec) {
867
1062
  ].join("|");
868
1063
  }
869
1064
  var QUALITY_BY_KEY = new Map(
870
- Object.keys(QUALITY_SPECS).map((quality) => [
871
- specKey(QUALITY_SPECS[quality]),
872
- quality
873
- ])
1065
+ QUALITY_SPECS.map(([quality, spec]) => [specKey(spec), quality])
874
1066
  );
875
1067
  function chordSpecForQuality(quality, rootPc, bassPc) {
876
- if (!Object.hasOwn(QUALITY_SPECS, quality)) {
877
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown chord quality: ${String(quality)}`);
1068
+ const named = SPEC_BY_QUALITY.get(quality);
1069
+ if (named === void 0) {
1070
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unknown chord quality: ${String(quality)}`);
878
1071
  }
879
- const spec = normalizeChordSpec(QUALITY_SPECS[quality]);
1072
+ const spec = normalizeChordSpec(named);
880
1073
  spec.rootPc = rootPc;
881
1074
  if (bassPc !== void 0) {
882
1075
  spec.bassPc = bassPc;
@@ -892,10 +1085,10 @@ function chordSpecQuality(spec) {
892
1085
  if (exact !== void 0) {
893
1086
  return exact;
894
1087
  }
895
- const offsets = new Set(specIntervals(checked));
1088
+ const offsets = new Set(specIntervals({ ...checked, omissions: [] }));
896
1089
  let nearest;
897
1090
  let size = 0;
898
- for (const quality of Object.keys(QUALITY_INTERVALS)) {
1091
+ for (const quality of QUALITY_ORDER) {
899
1092
  const template = QUALITY_INTERVALS[quality];
900
1093
  if (template.length > size && template.every((offset) => offsets.has(offset))) {
901
1094
  nearest = quality;
@@ -981,15 +1174,15 @@ function chordSpecFromIntervals(intervals) {
981
1174
  }
982
1175
  function assertPart(value, allowed, name) {
983
1176
  if (!allowed.includes(value)) {
984
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
985
- `${name} must be one of ${allowed.join(", ")}; received ${JSON.stringify(value)}`
1177
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1178
+ `${name} must be one of ${allowed.join(", ")}; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, value)}`
986
1179
  );
987
1180
  }
988
1181
  return value;
989
1182
  }
990
1183
  function assertDegreeIn(value, allowed, name) {
991
1184
  if (!allowed.includes(value)) {
992
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1185
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
993
1186
  `${name} must be one of ${allowed.join(", ")}; received ${String(value)}`
994
1187
  );
995
1188
  }
@@ -997,7 +1190,7 @@ function assertDegreeIn(value, allowed, name) {
997
1190
  }
998
1191
  function assertDegreeList(value, allowed, name) {
999
1192
  if (!Array.isArray(value)) {
1000
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must be an array; received ${typeof value}`);
1193
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must be an array; received ${typeof value}`);
1001
1194
  }
1002
1195
  for (const degree of value) {
1003
1196
  assertDegreeIn(degree, allowed, name);
@@ -1005,11 +1198,11 @@ function assertDegreeList(value, allowed, name) {
1005
1198
  }
1006
1199
  function assertChordSpec(spec) {
1007
1200
  if (spec === null || typeof spec !== "object") {
1008
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`chord spec must be an object; received ${typeof spec}`);
1201
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`chord spec must be an object; received ${typeof spec}`);
1009
1202
  }
1010
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, spec.rootPc, "chord spec rootPc");
1203
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, spec.rootPc, "chord spec rootPc");
1011
1204
  if (spec.bassPc !== void 0) {
1012
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, spec.bassPc, "chord spec bassPc");
1205
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, spec.bassPc, "chord spec bassPc");
1013
1206
  }
1014
1207
  assertPart(spec.base, Object.keys(BASE_TONES), "chord spec base");
1015
1208
  if (spec.seventh !== void 0) {
@@ -1020,10 +1213,10 @@ function assertChordSpec(spec) {
1020
1213
  );
1021
1214
  }
1022
1215
  if (!Array.isArray(spec.alterations)) {
1023
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("chord spec alterations must be an array");
1216
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("chord spec alterations must be an array");
1024
1217
  }
1025
1218
  for (const alteration of spec.alterations) {
1026
- assertDegreeIn(_optionalChain([alteration, 'optionalAccess', _ => _.degree]), [5, 9, 11, 13], "chord spec alteration degree");
1219
+ assertDegreeIn(_optionalChain([alteration, 'optionalAccess', _5 => _5.degree]), [5, 9, 11, 13], "chord spec alteration degree");
1027
1220
  assertDegreeIn(alteration.alter, [-1, 0, 1], "chord spec alteration");
1028
1221
  }
1029
1222
  assertDegreeList(spec.additions, [6, 9, 11, 13], "chord spec additions");
@@ -1034,54 +1227,63 @@ function assertChordSpec(spec) {
1034
1227
  // src/theory/chord/index.ts
1035
1228
  var MAX_CHORD_INTERVAL = 127;
1036
1229
  function chordQualities() {
1037
- return Object.keys(QUALITY_INTERVALS);
1230
+ return [...QUALITY_ORDER];
1038
1231
  }
1039
- function chordFromDegree(degree, ext, key) {
1040
- _chunkQ2WGKJNNcjs.assertDegree.call(void 0, degree, "chord degree");
1041
- if (!Object.hasOwn(QUALITY_INTERVALS, ext)) {
1042
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown chord quality: ${String(ext)}`);
1232
+ function assertChordQuality(quality) {
1233
+ if (!Object.hasOwn(QUALITY_INTERVALS, quality)) {
1234
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unknown chord quality: ${String(quality)}`);
1043
1235
  }
1236
+ return quality;
1237
+ }
1238
+ function assertChord(chord, name = "chord") {
1239
+ const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, chord, name);
1240
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, read.rootPc, `${name}.rootPc`);
1241
+ assertChordQuality(read.quality);
1242
+ return read;
1243
+ }
1244
+ function chordFromDegree(degree, ext, key) {
1245
+ _chunkS5BKZZGMcjs.assertDegree.call(void 0, degree, "chord degree");
1246
+ assertChordQuality(ext);
1044
1247
  const scale = toKeyScale(key);
1045
1248
  const tones = scaleTonesInDegreeOrder(scale);
1046
1249
  const length = tones.length;
1047
1250
  const index = length > 0 ? (degree - 1) % length : 0;
1048
- const rootPc = _nullishCoalesce(tones[index], () => ( _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, scale.rootPc)));
1251
+ const rootPc = _nullishCoalesce(tones[index], () => ( _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, scale.rootPc)));
1049
1252
  return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };
1050
1253
  }
1051
1254
  function makeChord(rootPc, quality, bassPc) {
1052
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, rootPc, "chord rootPc");
1255
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, rootPc, "chord rootPc");
1053
1256
  if (bassPc !== void 0) {
1054
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, bassPc, "chord bassPc");
1055
- }
1056
- if (!Object.hasOwn(QUALITY_INTERVALS, quality)) {
1057
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown chord quality: ${String(quality)}`);
1257
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, bassPc, "chord bassPc");
1058
1258
  }
1259
+ assertChordQuality(quality);
1059
1260
  const chord = {
1060
- rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rootPc),
1261
+ rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc),
1061
1262
  quality,
1062
1263
  intervals: [..._nullishCoalesce(QUALITY_INTERVALS[quality], () => ( []))]
1063
1264
  };
1064
1265
  if (bassPc !== void 0) {
1065
- chord.bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, bassPc);
1266
+ chord.bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, bassPc);
1066
1267
  }
1067
1268
  return chord;
1068
1269
  }
1069
1270
  function chordFromSpec(spec) {
1070
1271
  const checked = assertChordSpec(spec);
1071
1272
  const chord = {
1072
- rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, checked.rootPc),
1273
+ rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, checked.rootPc),
1073
1274
  quality: chordSpecQuality(checked),
1074
1275
  intervals: chordSpecIntervals(checked)
1075
1276
  };
1076
1277
  if (checked.bassPc !== void 0) {
1077
- chord.bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, checked.bassPc);
1278
+ chord.bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, checked.bassPc);
1078
1279
  }
1079
1280
  return chord;
1080
1281
  }
1081
1282
  function chordSpecOf(chord) {
1082
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, chord.rootPc, "chord rootPc");
1083
- const rootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc);
1084
- const bassPc = chord.bassPc === void 0 ? void 0 : _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.bassPc);
1283
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, chord, "chord");
1284
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, chord.rootPc, "chord rootPc");
1285
+ const rootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc);
1286
+ const bassPc = chord.bassPc === void 0 ? void 0 : _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.bassPc);
1085
1287
  const intervals = _nullishCoalesce(chord.intervals, () => ( []));
1086
1288
  const read = intervals.length === 0 ? void 0 : chordSpecFromIntervals(intervals);
1087
1289
  if (read === void 0) {
@@ -1096,21 +1298,21 @@ function chordSpecOf(chord) {
1096
1298
  function copySpanIntervals(intervals) {
1097
1299
  const name = "chord span intervals";
1098
1300
  if (!Array.isArray(intervals)) {
1099
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must be an array; received ${typeof intervals}`);
1301
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must be an array; received ${typeof intervals}`);
1100
1302
  }
1101
1303
  if (intervals.length === 0) {
1102
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`${name} must name at least one interval`);
1304
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name} must name at least one interval`);
1103
1305
  }
1104
1306
  const copy = [];
1105
1307
  for (let index = 0; index < intervals.length; index += 1) {
1106
1308
  const interval = intervals[index];
1107
1309
  if (interval === void 0) {
1108
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1310
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1109
1311
  `${name}[${index}] must be a semitone offset; received undefined`
1110
1312
  );
1111
1313
  }
1112
1314
  copy.push(
1113
- _chunkQ2WGKJNNcjs.assertInteger.call(void 0, interval, `${name}[${index}]`, -MAX_CHORD_INTERVAL, MAX_CHORD_INTERVAL)
1315
+ _chunkS5BKZZGMcjs.assertInteger.call(void 0, interval, `${name}[${index}]`, -MAX_CHORD_INTERVAL, MAX_CHORD_INTERVAL)
1114
1316
  );
1115
1317
  }
1116
1318
  return copy;
@@ -1119,6 +1321,7 @@ function hasCustomIntervals(chord) {
1119
1321
  return !matchesQualityIntervals(chord.quality, chord.intervals);
1120
1322
  }
1121
1323
  function chordFromSpan(span) {
1324
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, span, "chord span");
1122
1325
  const chord = makeChord(span.rootPc, span.quality, span.bassPc);
1123
1326
  if (span.intervals !== void 0) {
1124
1327
  chord.intervals = copySpanIntervals(span.intervals);
@@ -1126,10 +1329,8 @@ function chordFromSpan(span) {
1126
1329
  return chord;
1127
1330
  }
1128
1331
  function spanFromChord(chord, startBeat) {
1129
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, startBeat, "chord span startBeat");
1130
- if (!Object.hasOwn(QUALITY_INTERVALS, chord.quality)) {
1131
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown chord quality: ${String(chord.quality)}`);
1132
- }
1332
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, startBeat, "chord span startBeat");
1333
+ assertChord(chord);
1133
1334
  const span = { rootPc: chord.rootPc, quality: chord.quality, startBeat };
1134
1335
  if (chord.bassPc !== void 0) {
1135
1336
  span.bassPc = chord.bassPc;
@@ -1140,15 +1341,16 @@ function spanFromChord(chord, startBeat) {
1140
1341
  return span;
1141
1342
  }
1142
1343
  function transposeChord(chord, semitones) {
1143
- _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, semitones, "semitones");
1344
+ assertChord(chord);
1345
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, semitones, "semitones");
1144
1346
  const steps = Math.round(semitones);
1145
1347
  const moved = {
1146
- rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + steps),
1348
+ rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + steps),
1147
1349
  quality: chord.quality,
1148
1350
  intervals: [...chord.intervals]
1149
1351
  };
1150
1352
  if (chord.bassPc !== void 0) {
1151
- moved.bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.bassPc + steps);
1353
+ moved.bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.bassPc + steps);
1152
1354
  }
1153
1355
  return moved;
1154
1356
  }
@@ -1164,66 +1366,152 @@ function chordToneSpellings(chord) {
1164
1366
  if (hint === void 0 || interval === void 0) {
1165
1367
  return void 0;
1166
1368
  }
1167
- if (_chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, hint) !== _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + interval)) {
1369
+ if (_chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, hint) !== _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval)) {
1168
1370
  return void 0;
1169
1371
  }
1170
- usable.push({ letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, hint.letter), alter: hint.alter });
1372
+ usable.push({ letter: _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, hint.letter), alter: hint.alter });
1171
1373
  }
1172
1374
  return usable;
1173
1375
  }
1174
- function chordPitchClasses(chord, opts = {}) {
1175
- const set = /* @__PURE__ */ new Set();
1376
+ function chordToneLetterOffsets(chord) {
1377
+ const hints = chordToneSpellings(chord);
1378
+ const rootHint = _optionalChain([hints, 'optionalAccess', _6 => _6[chord.intervals.findIndex((interval) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, interval) === 0)]]);
1379
+ if (hints === void 0 || rootHint === void 0) {
1380
+ return void 0;
1381
+ }
1382
+ return hints.map((hint) => _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, hint.letter - rootHint.letter));
1383
+ }
1384
+ var ROLE_BY_LETTER_OFFSET = {
1385
+ 0: "root",
1386
+ 2: "third",
1387
+ 4: "fifth",
1388
+ 5: "sixth",
1389
+ 6: "seventh"
1390
+ };
1391
+ function spelledToneRole(pitch, chord) {
1392
+ const offsets = chordToneLetterOffsets(chord);
1393
+ if (offsets === void 0) {
1394
+ return void 0;
1395
+ }
1396
+ const index = chord.intervals.findIndex(
1397
+ (interval) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch)
1398
+ );
1399
+ const offset = offsets[index];
1400
+ return offset === void 0 ? void 0 : _nullishCoalesce(ROLE_BY_LETTER_OFFSET[offset], () => ( null));
1401
+ }
1402
+ function chordPcMask(chord, opts = {}) {
1403
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, chord, "chord");
1404
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, chord.intervals, "chord intervals");
1405
+ let mask = 0;
1176
1406
  for (const interval of chord.intervals) {
1177
- set.add(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + interval));
1407
+ mask |= 1 << _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval);
1178
1408
  }
1179
1409
  if (opts.includeBass !== false && chord.bassPc !== void 0) {
1180
- set.add(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.bassPc));
1410
+ mask |= 1 << _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.bassPc);
1411
+ }
1412
+ return mask;
1413
+ }
1414
+ function hasPitchClass(mask, pc) {
1415
+ return (mask >> pc & 1) === 1;
1416
+ }
1417
+ function chordIntervalMask(chord) {
1418
+ let mask = 0;
1419
+ for (const interval of chord.intervals) {
1420
+ mask |= 1 << (interval % 12 + 12) % 12;
1421
+ }
1422
+ return mask;
1423
+ }
1424
+ function chordPitchClasses(chord, opts = {}) {
1425
+ const mask = chordPcMask(assertChord(chord), _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts"));
1426
+ const pcs = [];
1427
+ for (let pc = 0; pc < 12; pc += 1) {
1428
+ if (hasPitchClass(mask, pc)) {
1429
+ pcs.push(pc);
1430
+ }
1181
1431
  }
1182
- return [...set].sort((a, b) => a - b);
1432
+ return pcs;
1183
1433
  }
1184
1434
  function intervalAboveRoot(pitch, chord) {
1185
- return ((_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitch) - _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc)) % 12 + 12) % 12;
1435
+ return ((_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch) - _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, assertChord(chord).rootPc)) % 12 + 12) % 12;
1186
1436
  }
1187
1437
  function isChordMember(pitch, chord) {
1188
- return chord === null ? false : chordPitchClasses(chord).includes(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitch));
1189
- }
1190
- function chordToneOffsets(chord) {
1191
- return new Set(chord.intervals.map((i) => (i % 12 + 12) % 12));
1438
+ return chord === null ? false : hasPitchClass(chordPcMask(chord), _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch));
1192
1439
  }
1193
1440
  function chordToneRole(pitch, chord) {
1194
- const interval = (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pitch) - _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc) + 12) % 12;
1195
- const tones = chordToneOffsets(chord);
1441
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, chord, "chord");
1442
+ const spelled2 = spelledToneRole(pitch, chord);
1443
+ if (spelled2 !== void 0) {
1444
+ return spelled2;
1445
+ }
1446
+ const interval = (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch) - _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc) + 12) % 12;
1447
+ const tones = chordIntervalMask(chord);
1448
+ const sounds = (ic) => hasPitchClass(tones, ic);
1196
1449
  if (interval === 0) {
1197
- return "root";
1450
+ return sounds(0) ? "root" : null;
1198
1451
  }
1199
1452
  if (interval === 3 || interval === 4) {
1200
- return tones.has(interval) && (interval !== 3 || !tones.has(4)) ? "third" : null;
1453
+ return sounds(interval) && (interval !== 3 || !sounds(4)) ? "third" : null;
1201
1454
  }
1202
1455
  if (interval === 7) {
1203
- return tones.has(7) ? "fifth" : null;
1456
+ return sounds(7) ? "fifth" : null;
1204
1457
  }
1205
1458
  if (interval === 6) {
1206
- return tones.has(6) && !tones.has(7) ? "fifth" : null;
1459
+ return sounds(6) && !sounds(7) ? "fifth" : null;
1207
1460
  }
1208
1461
  if (interval === 8) {
1209
- return tones.has(8) && !tones.has(7) ? "fifth" : null;
1462
+ return sounds(8) && !sounds(7) ? "fifth" : null;
1210
1463
  }
1211
1464
  if (interval === 9) {
1212
- const hasHigherSeventh = tones.has(10) || tones.has(11);
1213
- const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;
1214
- if (tones.has(9) && isDiminishedSeventh) {
1465
+ const hasHigherSeventh = sounds(10) || sounds(11);
1466
+ const isDiminishedSeventh = sounds(3) && sounds(6) && !hasHigherSeventh;
1467
+ if (sounds(9) && isDiminishedSeventh) {
1215
1468
  return "seventh";
1216
1469
  }
1217
- if (tones.has(9) && !hasHigherSeventh) {
1470
+ if (sounds(9) && !hasHigherSeventh) {
1218
1471
  return "sixth";
1219
1472
  }
1220
1473
  return null;
1221
1474
  }
1222
1475
  if (interval === 10 || interval === 11) {
1223
- return tones.has(interval) ? "seventh" : null;
1476
+ return sounds(interval) ? "seventh" : null;
1224
1477
  }
1225
1478
  return null;
1226
1479
  }
1480
+ var DEGREE_BY_ROLE = {
1481
+ root: 1,
1482
+ third: 3,
1483
+ fifth: 5,
1484
+ sixth: 6,
1485
+ seventh: 7
1486
+ };
1487
+ var TENSION_DEGREE = [1, 2, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7];
1488
+ function degreeOfInterval(interval, chord) {
1489
+ const role = chordToneRole(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval), chord);
1490
+ if (role !== null) {
1491
+ return DEGREE_BY_ROLE[role];
1492
+ }
1493
+ return TENSION_DEGREE[(interval % 12 + 12) % 12];
1494
+ }
1495
+ function thirdSlotOf(chord) {
1496
+ const tones = chordIntervalMask(chord);
1497
+ if (hasPitchClass(tones, 4)) {
1498
+ return { kind: "third", interval: 4 };
1499
+ }
1500
+ if (hasPitchClass(tones, 3)) {
1501
+ return { kind: "third", interval: 3 };
1502
+ }
1503
+ const spec = chordSpecOf(chord);
1504
+ if (spec.base === "sus2") {
1505
+ return hasPitchClass(tones, 2) ? { kind: "suspension", interval: 2 } : { kind: "none" };
1506
+ }
1507
+ if (spec.base === "sus4" || spec.omissions.includes(3)) {
1508
+ return hasPitchClass(tones, 5) ? { kind: "suspension", interval: 5 } : { kind: "none" };
1509
+ }
1510
+ return { kind: "none" };
1511
+ }
1512
+ function displacedThirds(chord) {
1513
+ return thirdSlotOf(chord).kind === "suspension" ? [_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + 3), _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + 4)] : [];
1514
+ }
1227
1515
  function classifyTriad(thirdIc, fifthIc) {
1228
1516
  if (thirdIc === 4 && fifthIc === 8) {
1229
1517
  return "aug";
@@ -1271,16 +1559,16 @@ function matchesQualityIntervals(quality, intervals) {
1271
1559
  return template.length === intervals.length && template.every((interval, index) => interval === intervals[index]);
1272
1560
  }
1273
1561
  function stackThirds(degree, key, size) {
1274
- _chunkQ2WGKJNNcjs.assertDegree.call(void 0, degree, "chord degree");
1562
+ _chunkS5BKZZGMcjs.assertDegree.call(void 0, degree, "chord degree");
1275
1563
  const tones = scaleTonesInDegreeOrder(key);
1276
1564
  const length = tones.length;
1277
1565
  if (length !== 7) {
1278
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1566
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1279
1567
  `diatonic chord stacking requires a heptatonic scale (received ${length} tones)`
1280
1568
  );
1281
1569
  }
1282
1570
  const idx = (step) => (degree - 1 + step) % length;
1283
- const rootPc = _nullishCoalesce(tones[idx(0)], () => ( _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc)));
1571
+ const rootPc = _nullishCoalesce(tones[idx(0)], () => ( _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc)));
1284
1572
  const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {
1285
1573
  const pc = _nullishCoalesce(tones[idx(step)], () => ( rootPc));
1286
1574
  return ((pc - rootPc) % 12 + 12) % 12;
@@ -1289,7 +1577,7 @@ function stackThirds(degree, key, size) {
1289
1577
  const fifthIc = _nullishCoalesce(offsets[2], () => ( 7));
1290
1578
  const quality = size === 3 ? classifyTriad(thirdIc, fifthIc) : classifySeventh(thirdIc, fifthIc, _nullishCoalesce(offsets[3], () => ( 11)));
1291
1579
  if (!matchesQualityIntervals(quality, offsets)) {
1292
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1580
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1293
1581
  `scale thirds at degree ${degree} produce unsupported intervals [${offsets.join(", ")}]`
1294
1582
  );
1295
1583
  }
@@ -1357,20 +1645,20 @@ var CHROMATIC_SPELLING_SHARP = {
1357
1645
  // M7
1358
1646
  };
1359
1647
  function notePitchClass(note) {
1360
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, note.letter) + note.alter);
1648
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, note.letter) + note.alter);
1361
1649
  }
1362
1650
  function assertTonicOf(tonic, key, source) {
1363
1651
  const tonicPc = notePitchClass(tonic);
1364
- const rootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
1652
+ const rootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
1365
1653
  if (tonicPc !== rootPc) {
1366
- const name = _chunkQ2WGKJNNcjs.formatNote.call(void 0, { letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, tonic.letter), alter: tonic.alter });
1367
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1654
+ const name = _chunkS5BKZZGMcjs.formatNote.call(void 0, { letter: _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter), alter: tonic.alter });
1655
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1368
1656
  `${source}: tonic ${name} sounds pitch class ${tonicPc}, not the key root ${rootPc}; pass a tonic that spells the key root, or take one from spelledKeyOf`
1369
1657
  );
1370
1658
  }
1371
1659
  }
1372
1660
  function alterFor(letter, pc) {
1373
- let d = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc - _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, letter));
1661
+ let d = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, letter));
1374
1662
  if (d > 6) {
1375
1663
  d -= 12;
1376
1664
  }
@@ -1380,7 +1668,7 @@ function letterFor(tonic, degreeOffset, pc) {
1380
1668
  if (degreeOffset === void 0) {
1381
1669
  return void 0;
1382
1670
  }
1383
- const letter = _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, tonic.letter + degreeOffset);
1671
+ const letter = _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter + degreeOffset);
1384
1672
  return { letter, alter: alterFor(letter, pc) };
1385
1673
  }
1386
1674
  function pickSpelling(flat, sharp, preferFlat) {
@@ -1396,16 +1684,32 @@ function capChromaticAccidentals(preferred, alternative) {
1396
1684
  return alternative !== void 0 && Math.abs(preferred.alter) - Math.abs(alternative.alter) >= 2 ? alternative : preferred;
1397
1685
  }
1398
1686
  var DIATONIC_LETTER_COUNT = 7;
1687
+ var MAX_JOINT_TONE_COUNT = DIATONIC_LETTER_COUNT + 1;
1688
+ var MAJOR_SEMITONES = [0, 2, 4, 5, 7, 9, 11];
1689
+ var IMPERFECT_DEGREES = /* @__PURE__ */ new Set([1, 2, 5, 6]);
1690
+ var DIATONIC_MODE_OFFSETS2 = MAJOR_SEMITONES.map(
1691
+ (tonic, mode) => MAJOR_SEMITONES.map((_, degree) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, (_nullishCoalesce(MAJOR_SEMITONES[(mode + degree) % 7], () => ( 0))) - tonic))
1692
+ );
1693
+ function degreeStrain(tonic, note) {
1694
+ const letterOffset = _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, note.letter - tonic.letter);
1695
+ const offset = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, notePitchClass(note) - notePitchClass(tonic));
1696
+ let deviation = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, offset - (_nullishCoalesce(MAJOR_SEMITONES[letterOffset], () => ( 0))));
1697
+ if (deviation > 6) {
1698
+ deviation -= 12;
1699
+ }
1700
+ const strain = IMPERFECT_DEGREES.has(letterOffset) && deviation < 0 ? Math.abs(deviation + 1) : Math.abs(deviation);
1701
+ return Math.min(strain, 2);
1702
+ }
1399
1703
  function isHeptatonic(key) {
1400
1704
  return scaleTonesInDegreeOrder(key).length === DIATONIC_LETTER_COUNT;
1401
1705
  }
1402
- function hasMinorThird2(key) {
1706
+ function hasMinorThird(key) {
1403
1707
  const tones = scaleTonesInDegreeOrder(key);
1404
- const root = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
1405
- return tones.includes(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, root + 3)) && !tones.includes(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, root + 4));
1708
+ const root = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
1709
+ return tones.includes(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, root + 3)) && !tones.includes(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, root + 4));
1406
1710
  }
1407
1711
  function leansFlat(tonic, key) {
1408
- return tonic.alter < 0 || hasMinorThird2(key);
1712
+ return tonic.alter < 0 || hasMinorThird(key);
1409
1713
  }
1410
1714
  function gappedCandidates(tonic, offset, pc, preferFlat) {
1411
1715
  const flat = letterFor(tonic, CHROMATIC_SPELLING_FLAT[offset], pc);
@@ -1417,22 +1721,64 @@ function gappedCandidates(tonic, offset, pc, preferFlat) {
1417
1721
  const alternative = preferred === flat ? sharp : flat;
1418
1722
  return alternative === void 0 || alternative.letter === preferred.letter ? [preferred] : [preferred, alternative];
1419
1723
  }
1420
- function assignDistinctLetters(candidates, reserved) {
1724
+ function spellByParentMode(tonic, pcs, preferFlat) {
1725
+ const tonicPc = notePitchClass(tonic);
1726
+ let best;
1727
+ let bestAlter = 0;
1728
+ let bestSigned = 0;
1729
+ for (const offsets of DIATONIC_MODE_OFFSETS2) {
1730
+ const notes = [];
1731
+ let alter = 0;
1732
+ let signed = 0;
1733
+ for (const pc of pcs) {
1734
+ const degree = offsets.indexOf(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - tonicPc));
1735
+ const note = letterFor(tonic, degree < 0 ? void 0 : degree, pc);
1736
+ if (note === void 0) {
1737
+ break;
1738
+ }
1739
+ notes.push(note);
1740
+ alter += Math.abs(note.alter);
1741
+ signed += note.alter;
1742
+ }
1743
+ if (notes.length !== pcs.length) {
1744
+ continue;
1745
+ }
1746
+ const evenlyLight = alter === bestAlter && (preferFlat ? signed < bestSigned : signed > bestSigned);
1747
+ const lighter = alter < bestAlter || evenlyLight;
1748
+ if (best === void 0 || lighter) {
1749
+ best = notes;
1750
+ bestAlter = alter;
1751
+ bestSigned = signed;
1752
+ }
1753
+ }
1754
+ return best;
1755
+ }
1756
+ function assignDistinctLetters(candidates, tonic, duplicates) {
1757
+ const reserved = 1 << _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter);
1421
1758
  let best;
1759
+ let bestStrain = 0;
1422
1760
  let bestAlter = 0;
1423
1761
  let bestDeviations = 0;
1424
1762
  for (let choice = 0; choice < 1 << candidates.length; choice += 1) {
1425
1763
  const picked = [];
1426
1764
  let letters = reserved;
1765
+ let spare = duplicates;
1766
+ let strain = 0;
1427
1767
  let alter = 0;
1428
1768
  let deviations = 0;
1429
1769
  for (const [index, options] of candidates.entries()) {
1430
1770
  const preferred = options[0];
1431
1771
  const note = _nullishCoalesce(options[choice >> index & 1], () => ( preferred));
1432
- if (note === void 0 || (letters & 1 << note.letter) !== 0) {
1772
+ if (note === void 0) {
1433
1773
  break;
1434
1774
  }
1775
+ const taken = (letters & 1 << note.letter) !== 0;
1776
+ if (taken && (spare === 0 || (reserved & 1 << note.letter) !== 0)) {
1777
+ break;
1778
+ }
1779
+ spare -= taken ? 1 : 0;
1435
1780
  letters |= 1 << note.letter;
1781
+ strain += degreeStrain(tonic, note);
1436
1782
  alter += Math.abs(note.alter);
1437
1783
  deviations += note === preferred ? 0 : 1;
1438
1784
  picked.push(note);
@@ -1440,8 +1786,10 @@ function assignDistinctLetters(candidates, reserved) {
1440
1786
  if (picked.length !== candidates.length) {
1441
1787
  continue;
1442
1788
  }
1443
- if (best === void 0 || alter < bestAlter || alter === bestAlter && deviations < bestDeviations) {
1789
+ const better = duplicates > 0 && strain !== bestStrain ? strain < bestStrain : alter !== bestAlter ? alter < bestAlter : deviations < bestDeviations;
1790
+ if (best === void 0 || better) {
1444
1791
  best = picked;
1792
+ bestStrain = strain;
1445
1793
  bestAlter = alter;
1446
1794
  bestDeviations = deviations;
1447
1795
  }
@@ -1449,18 +1797,33 @@ function assignDistinctLetters(candidates, reserved) {
1449
1797
  return best;
1450
1798
  }
1451
1799
  function spellGappedScale(tonic, key) {
1800
+ const cacheKey = `${_chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter)}:${tonic.alter}:${key.rootPc}:${key.modeMask12}`;
1801
+ const cached = JOINT_SPELLINGS.get(cacheKey);
1802
+ if (cached !== void 0) {
1803
+ return cached.spellings;
1804
+ }
1805
+ const answer = readGappedScale(tonic, key);
1806
+ if (JOINT_SPELLINGS.size >= MAX_REMEMBERED_SCALES) {
1807
+ JOINT_SPELLINGS.clear();
1808
+ }
1809
+ JOINT_SPELLINGS.set(cacheKey, { spellings: answer });
1810
+ return answer;
1811
+ }
1812
+ var MAX_REMEMBERED_SCALES = 512;
1813
+ var JOINT_SPELLINGS = /* @__PURE__ */ new Map();
1814
+ function readGappedScale(tonic, key) {
1452
1815
  const tones = scaleTonesInDegreeOrder(key);
1453
- if (tones.length > DIATONIC_LETTER_COUNT) {
1816
+ if (tones.length > MAX_JOINT_TONE_COUNT) {
1454
1817
  return void 0;
1455
1818
  }
1456
- const tonicPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, tonic.letter) + tonic.alter);
1457
- const tonicNote = { letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, tonic.letter), alter: tonic.alter };
1819
+ const tonicPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, tonic.letter) + tonic.alter);
1820
+ const tonicNote = { letter: _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter), alter: tonic.alter };
1458
1821
  const preferFlat = leansFlat(tonic, key);
1459
- const rest = tones.filter((pc) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc - tonicPc) !== 0);
1460
- const chosen = assignDistinctLetters(
1461
- rest.map((pc) => gappedCandidates(tonic, _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc - tonicPc), pc, preferFlat)),
1462
- 1 << tonicNote.letter
1463
- );
1822
+ const rest = tones.filter((pc) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - tonicPc) !== 0);
1823
+ const candidates = rest.map((pc) => gappedCandidates(tonic, _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - tonicPc), pc, preferFlat));
1824
+ const chosen = _nullishCoalesce(_nullishCoalesce(spellByParentMode(tonicNote, rest, preferFlat), () => ( // A scale that can hold a letter per tone does; only one that cannot writes
1825
+ // a letter twice, and then where the doubling falls is what is chosen.
1826
+ assignDistinctLetters(candidates, tonicNote, 0))), () => ( assignDistinctLetters(candidates, tonicNote, 1)));
1464
1827
  if (chosen === void 0) {
1465
1828
  return void 0;
1466
1829
  }
@@ -1468,22 +1831,22 @@ function spellGappedScale(tonic, key) {
1468
1831
  for (const [index, pc] of rest.entries()) {
1469
1832
  const note = chosen[index];
1470
1833
  if (note !== void 0) {
1471
- spellings.set(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc), note);
1834
+ spellings.set(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc), note);
1472
1835
  }
1473
1836
  }
1474
1837
  return spellings;
1475
1838
  }
1476
1839
  function spellByKey(pc, tonic, key) {
1477
- const tonicPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, tonic.letter) + tonic.alter);
1478
- const offset = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc - tonicPc);
1840
+ const tonicPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, tonic.letter) + tonic.alter);
1841
+ const offset = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - tonicPc);
1479
1842
  if (offset === 0) {
1480
- return { letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, tonic.letter), alter: tonic.alter };
1843
+ return { letter: _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter), alter: tonic.alter };
1481
1844
  }
1482
1845
  const heptatonic = isHeptatonic(key);
1483
1846
  if (heptatonic) {
1484
- const degree = scaleTonesInDegreeOrder(key).indexOf(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc));
1847
+ const degree = scaleTonesInDegreeOrder(key).indexOf(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc));
1485
1848
  if (degree >= 0) {
1486
- const letter = _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, tonic.letter + degree);
1849
+ const letter = _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, tonic.letter + degree);
1487
1850
  return { letter, alter: alterFor(letter, pc) };
1488
1851
  }
1489
1852
  const degreeOffset = CHROMATIC_SPELLING[offset];
@@ -1491,14 +1854,14 @@ function spellByKey(pc, tonic, key) {
1491
1854
  if (preferred !== void 0) {
1492
1855
  const flat = letterFor(tonic, CHROMATIC_SPELLING_FLAT[offset], pc);
1493
1856
  const sharp = letterFor(tonic, CHROMATIC_SPELLING_SHARP[offset], pc);
1494
- const alternative = _optionalChain([flat, 'optionalAccess', _2 => _2.letter]) === preferred.letter ? sharp : _optionalChain([sharp, 'optionalAccess', _3 => _3.letter]) === preferred.letter ? flat : void 0;
1857
+ const alternative = _optionalChain([flat, 'optionalAccess', _7 => _7.letter]) === preferred.letter ? sharp : _optionalChain([sharp, 'optionalAccess', _8 => _8.letter]) === preferred.letter ? flat : void 0;
1495
1858
  return capChromaticAccidentals(preferred, alternative);
1496
1859
  }
1497
1860
  } else {
1498
1861
  if (isScaleTone(pc, key)) {
1499
- const assigned = _optionalChain([spellGappedScale, 'call', _4 => _4(tonic, key), 'optionalAccess', _5 => _5.get, 'call', _6 => _6(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc))]);
1862
+ const assigned = _optionalChain([spellGappedScale, 'call', _9 => _9(tonic, key), 'optionalAccess', _10 => _10.get, 'call', _11 => _11(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc))]);
1500
1863
  if (assigned !== void 0) {
1501
- return assigned;
1864
+ return { letter: assigned.letter, alter: assigned.alter };
1502
1865
  }
1503
1866
  }
1504
1867
  const [preferred] = gappedCandidates(tonic, offset, pc, leansFlat(tonic, key));
@@ -1506,7 +1869,7 @@ function spellByKey(pc, tonic, key) {
1506
1869
  return preferred;
1507
1870
  }
1508
1871
  }
1509
- const belowLetter = _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, _nullishCoalesce([0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6][_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc)], () => ( 0)));
1872
+ const belowLetter = _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, _nullishCoalesce([0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6][_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc)], () => ( 0)));
1510
1873
  return { letter: belowLetter, alter: alterFor(belowLetter, pc) };
1511
1874
  }
1512
1875
  var CHORD_TONE_LETTER_STEPS = {
@@ -1523,29 +1886,55 @@ var CHORD_TONE_LETTER_STEPS = {
1523
1886
  11: 6
1524
1887
  // major seventh
1525
1888
  };
1889
+ var LETTER_STEP_BY_ROLE = {
1890
+ root: 0,
1891
+ third: 2,
1892
+ fifth: 4,
1893
+ sixth: 5,
1894
+ seventh: 6
1895
+ };
1526
1896
  var MAX_CONTEXT_ALTER = 2;
1527
- function chordToneSpelling(pc, chordRoot, tonic, key) {
1897
+ function chordToneLetterStep(pc, rootPc, chord) {
1898
+ if (chord !== void 0) {
1899
+ const offsets = chordToneLetterOffsets(chord);
1900
+ const index = chord.intervals.findIndex(
1901
+ (interval) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc)
1902
+ );
1903
+ const offset = _optionalChain([offsets, 'optionalAccess', _12 => _12[index]]);
1904
+ if (offset !== void 0) {
1905
+ return offset;
1906
+ }
1907
+ const role = chordToneRole(pc, chord);
1908
+ if (role !== null) {
1909
+ return LETTER_STEP_BY_ROLE[role];
1910
+ }
1911
+ }
1912
+ return CHORD_TONE_LETTER_STEPS[_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - rootPc)];
1913
+ }
1914
+ function spellChordTone(pc, context, tonic, key) {
1915
+ const { chord } = context;
1916
+ const chordRoot = _nullishCoalesce(_optionalChain([chord, 'optionalAccess', _13 => _13.rootPc]), () => ( context.chordRoot));
1528
1917
  if (chordRoot === void 0) {
1529
1918
  return void 0;
1530
1919
  }
1531
- const root = spellByKey(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chordRoot), tonic, key);
1532
- return letterFor(root, CHORD_TONE_LETTER_STEPS[_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc - chordRoot)], pc);
1920
+ const root = chord === void 0 ? spellByKey(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chordRoot), tonic, key) : chordRootSpelling(chord, tonic, key);
1921
+ return letterFor(root, chordToneLetterStep(pc, chordRoot, chord), pc);
1533
1922
  }
1534
1923
  function neighbourSpelling(neighbour, gap, letterStep, pc, tonic, key) {
1535
- if (neighbour === void 0 || _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, neighbour - pc) !== _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, gap)) {
1924
+ if (neighbour === void 0 || _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, neighbour - pc) !== _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, gap)) {
1536
1925
  return void 0;
1537
1926
  }
1538
- return letterFor(spellByKey(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, neighbour), tonic, key), letterStep, pc);
1927
+ return letterFor(spellByKey(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, neighbour), tonic, key), letterStep, pc);
1539
1928
  }
1540
1929
  function refineByContext(pc, tonic, key, context, keySpelling) {
1541
- const tonicPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, tonic.letter) + tonic.alter);
1542
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, pc) === tonicPc || isScaleTone(pc, key)) {
1930
+ const tonicPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, tonic.letter) + tonic.alter);
1931
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc) === tonicPc || isScaleTone(pc, key)) {
1543
1932
  return keySpelling;
1544
1933
  }
1545
- const { chordRoot, previous, next } = context;
1934
+ const { previous, next } = context;
1546
1935
  const candidates = [
1547
- // The sounding chord names the pitch by its interval above the root.
1548
- chordToneSpelling(pc, chordRoot, tonic, key),
1936
+ // The sounding chord names the pitch by the degree it writes it as.
1937
+ spellChordTone(pc, context, tonic, key),
1549
1938
  // A semitone below what follows: the pitch is its leading tone, a letter down.
1550
1939
  neighbourSpelling(next, 1, -1, pc, tonic, key),
1551
1940
  // A semitone above what preceded: the pitch is that note inflected upward,
@@ -1566,9 +1955,10 @@ function refineByContext(pc, tonic, key, context, keySpelling) {
1566
1955
  return keySpelling;
1567
1956
  }
1568
1957
  function spellPitchClass(pc, tonic, key, context) {
1569
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
1958
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
1570
1959
  const scale = toKeyScale(key);
1571
1960
  assertTonicOf(note, scale, "spellPitchClass");
1961
+ _chunkS5BKZZGMcjs.assertOptions.call(void 0, context, "context");
1572
1962
  return spelledPitchClass(pc, note, scale, context);
1573
1963
  }
1574
1964
  function spelledPitchClass(pc, tonic, key, context) {
@@ -1576,16 +1966,19 @@ function spelledPitchClass(pc, tonic, key, context) {
1576
1966
  return context === void 0 ? keySpelling : refineByContext(pc, tonic, key, context, keySpelling);
1577
1967
  }
1578
1968
  function spellScale(tonic, key) {
1579
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
1580
- const scale = key;
1969
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
1970
+ const scale = toKeyScale(key);
1581
1971
  assertTonicOf(note, scale, "spellScale");
1582
1972
  return scaleTonesInDegreeOrder(scale).map((pc) => spelledPitchClass(pc, note, scale));
1583
1973
  }
1584
1974
  function spellPitchClasses(pcs, tonic, key) {
1585
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
1975
+ const classes = _chunkS5BKZZGMcjs.assertArray.call(void 0, pcs, "pcs");
1976
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
1586
1977
  const scale = toKeyScale(key);
1587
1978
  assertTonicOf(note, scale, "spellPitchClasses");
1588
- return pcs.map((pc) => spelledPitchClass(pc, note, scale));
1979
+ return classes.map(
1980
+ (pc, index) => spelledPitchClass(_chunkS5BKZZGMcjs.assertPitchClass.call(void 0, pc, `pcs[${index}]`), note, scale)
1981
+ );
1589
1982
  }
1590
1983
  function chordLetterOffset(interval, chord) {
1591
1984
  switch (interval) {
@@ -1599,11 +1992,11 @@ function chordLetterOffset(interval, chord) {
1599
1992
  return 1;
1600
1993
  case 3:
1601
1994
  case 4:
1602
- case 16:
1603
1995
  return 2;
1604
1996
  case 15:
1605
1997
  return chord.intervals.some((i) => i === 3 || i === 4) ? 1 : 2;
1606
1998
  case 5:
1999
+ case 16:
1607
2000
  case 17:
1608
2001
  case 18:
1609
2002
  return 3;
@@ -1616,78 +2009,83 @@ function chordLetterOffset(interval, chord) {
1616
2009
  return chord.quality === "dim7" ? 6 : 5;
1617
2010
  case 20:
1618
2011
  case 21:
2012
+ case 22:
1619
2013
  return 5;
1620
2014
  case 10:
1621
2015
  case 11:
1622
- case 22:
1623
2016
  case 23:
1624
2017
  return 6;
1625
2018
  default: {
1626
2019
  const octaves = Math.floor(Math.max(0, interval) / 12);
1627
- const simple = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, interval);
2020
+ const simple = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, interval);
1628
2021
  const simpleOffset = _nullishCoalesce([0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 6, 6][simple], () => ( 0));
1629
2022
  return octaves * 7 + simpleOffset;
1630
2023
  }
1631
2024
  }
1632
2025
  }
1633
- function hintedLetterOffsets(chord) {
1634
- const hints = chordToneSpellings(chord);
1635
- const rootHint = _optionalChain([hints, 'optionalAccess', _7 => _7[chord.intervals.findIndex((interval) => _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, interval) === 0)]]);
1636
- if (hints === void 0 || rootHint === void 0) {
1637
- return void 0;
1638
- }
1639
- return hints.map((hint) => _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, hint.letter - rootHint.letter));
1640
- }
1641
2026
  function spellChordFromRoot(chord, root) {
1642
- return chordTonesFromRoot(chord, _chunkQ2WGKJNNcjs.toNoteData.call(void 0, root));
2027
+ return chordTonesFromRoot(assertChord(chord), _chunkS5BKZZGMcjs.toNoteData.call(void 0, root));
1643
2028
  }
1644
2029
  function chordTonesFromRoot(chord, root) {
1645
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, root.letter) + root.alter) !== _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc)) {
1646
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("chord root spelling must match chord.rootPc");
2030
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, root.letter) + root.alter) !== _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc)) {
2031
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("chord root spelling must match chord.rootPc");
1647
2032
  }
1648
- const hinted = hintedLetterOffsets(chord);
2033
+ const hinted = chordToneLetterOffsets(chord);
1649
2034
  return chord.intervals.map((interval, index) => {
1650
- const letter = _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, root.letter + (_nullishCoalesce(_optionalChain([hinted, 'optionalAccess', _8 => _8[index]]), () => ( chordLetterOffset(interval, chord)))));
1651
- const pc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + interval);
2035
+ const letter = _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, root.letter + (_nullishCoalesce(_optionalChain([hinted, 'optionalAccess', _14 => _14[index]]), () => ( chordLetterOffset(interval, chord)))));
2036
+ const pc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval);
1652
2037
  return { letter, alter: alterFor(letter, pc) };
1653
2038
  });
1654
2039
  }
1655
2040
  function spellChord(chord, tonic, key) {
1656
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
2041
+ assertChord(chord);
2042
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
1657
2043
  const scale = toKeyScale(key);
1658
2044
  assertTonicOf(note, scale, "spellChord");
1659
2045
  return chordTonesFromRoot(chord, chordRootSpelling(chord, note, scale));
1660
2046
  }
1661
2047
  function chordRootSpelling(chord, tonic, key) {
1662
2048
  const hint = chord.rootSpelling;
1663
- if (hint !== void 0 && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, hint.letter) + hint.alter) === _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc)) {
1664
- return { letter: _chunkQ2WGKJNNcjs.diatonicLetterOf.call(void 0, hint.letter), alter: hint.alter };
2049
+ if (hint !== void 0 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, hint.letter) + hint.alter) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc)) {
2050
+ return { letter: _chunkS5BKZZGMcjs.diatonicLetterOf.call(void 0, hint.letter), alter: hint.alter };
1665
2051
  }
1666
2052
  return spelledPitchClass(chord.rootPc, tonic, key);
1667
2053
  }
1668
2054
  function spellPitch(pitch, tonic, key, context) {
1669
- const note = _chunkQ2WGKJNNcjs.toNoteData.call(void 0, tonic);
2055
+ const note = _chunkS5BKZZGMcjs.toNoteData.call(void 0, tonic);
1670
2056
  const scale = toKeyScale(key);
1671
2057
  assertTonicOf(note, scale, "spellPitch");
1672
- const rounded = Math.round(pitch);
1673
- const spelled = spelledPitchClass(_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, rounded), note, scale, context);
1674
- const octave = (rounded - _chunkQ2WGKJNNcjs.naturalPitchClassOf.call(void 0, spelled.letter) - spelled.alter) / 12 - 1;
1675
- return { letter: spelled.letter, alter: spelled.alter, octave };
2058
+ _chunkS5BKZZGMcjs.assertOptions.call(void 0, context, "context");
2059
+ const rounded = Math.round(_chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, pitch, "pitch"));
2060
+ const spelled2 = spelledPitchClass(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rounded), note, scale, context);
2061
+ const octave = (rounded - _chunkS5BKZZGMcjs.naturalPitchClassOf.call(void 0, spelled2.letter) - spelled2.alter) / 12 - 1;
2062
+ return { letter: spelled2.letter, alter: spelled2.alter, octave };
1676
2063
  }
1677
2064
  function noteNames(notes) {
1678
- return notes.map((note) => _chunkQ2WGKJNNcjs.formatNote.call(void 0, _chunkQ2WGKJNNcjs.toNoteData.call(void 0, note)));
2065
+ _chunkS5BKZZGMcjs.assertArray.call(void 0, notes, "notes");
2066
+ return notes.map((note) => _chunkS5BKZZGMcjs.formatNote.call(void 0, _chunkS5BKZZGMcjs.toNoteData.call(void 0, note)));
1679
2067
  }
1680
2068
 
1681
2069
  // src/theory/counterpoint/internal.ts
2070
+ function assertQualityLabel(quality) {
2071
+ if (typeof quality !== "string" || !/^(P|M|m|A+|d+)$/.test(quality)) {
2072
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
2073
+ `interval.quality must be an interval quality; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, quality)}`
2074
+ );
2075
+ }
2076
+ return quality;
2077
+ }
1682
2078
  function pitchOf(value) {
1683
- return typeof value === "number" ? value : _chunkQ2WGKJNNcjs.noteToMidi.call(void 0, value);
2079
+ return typeof value === "number" ? value : _chunkS5BKZZGMcjs.noteToMidi.call(void 0, value);
1684
2080
  }
1685
2081
  function simpleIntervalNumber(numberValue) {
1686
2082
  return (Math.abs(numberValue) - 1) % 7 + 1;
1687
2083
  }
1688
2084
  function classifySpelledInterval(interval, twoVoice = true) {
1689
- const simple = simpleIntervalNumber(interval.number);
1690
- const quality = interval.quality;
2085
+ _chunkS5BKZZGMcjs.assertFlag.call(void 0, twoVoice, "twoVoice");
2086
+ const read = _chunkS5BKZZGMcjs.assertRecord.call(void 0, interval, "interval");
2087
+ const simple = simpleIntervalNumber(_chunkS5BKZZGMcjs.assertDegree.call(void 0, read.number, "interval.number"));
2088
+ const quality = assertQualityLabel(read.quality);
1691
2089
  if (quality === "P") {
1692
2090
  if (simple === 1 || simple === 5) {
1693
2091
  return 0 /* PerfectConsonance */;
@@ -1733,28 +2131,31 @@ function motionBetween(from, to) {
1733
2131
  if (leadMove > 0 !== counterMove > 0) {
1734
2132
  return "contrary";
1735
2133
  }
1736
- const before = simpleIntervalNumber(_chunkQ2WGKJNNcjs.spelledInterval.call(void 0, from.counter, from.lead).number);
1737
- const after = simpleIntervalNumber(_chunkQ2WGKJNNcjs.spelledInterval.call(void 0, to.counter, to.lead).number);
2134
+ const before = simpleIntervalNumber(_chunkS5BKZZGMcjs.spelledInterval.call(void 0, from.counter, from.lead).number);
2135
+ const after = simpleIntervalNumber(_chunkS5BKZZGMcjs.spelledInterval.call(void 0, to.counter, to.lead).number);
1738
2136
  return before === after ? "parallel" : "similar";
1739
2137
  }
1740
2138
  function voiceIndependence(lead, counter, opts) {
1741
- if (lead.length !== counter.length) {
1742
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1743
- `voiceIndependence needs the two lines aligned slot for slot; received ${lead.length} and ${counter.length}`
2139
+ _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
2140
+ const upper = _chunkS5BKZZGMcjs.assertArray.call(void 0, lead, "lead");
2141
+ const lower = _chunkS5BKZZGMcjs.assertArray.call(void 0, counter, "counter");
2142
+ if (upper.length !== lower.length) {
2143
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
2144
+ `voiceIndependence needs the two lines aligned slot for slot; received ${upper.length} and ${lower.length}`
1744
2145
  );
1745
2146
  }
1746
2147
  for (const [name, flags] of [
1747
- ["leadAttacks", _optionalChain([opts, 'optionalAccess', _9 => _9.leadAttacks])],
1748
- ["counterAttacks", _optionalChain([opts, 'optionalAccess', _10 => _10.counterAttacks])]
2148
+ ["leadAttacks", _optionalChain([opts, 'optionalAccess', _15 => _15.leadAttacks])],
2149
+ ["counterAttacks", _optionalChain([opts, 'optionalAccess', _16 => _16.counterAttacks])]
1749
2150
  ]) {
1750
- if (flags !== void 0 && flags.length !== lead.length) {
1751
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
1752
- `voiceIndependence needs ${name} aligned slot for slot with the lines; received ${flags.length} and ${lead.length}`
2151
+ if (flags !== void 0 && flags.length !== upper.length) {
2152
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
2153
+ `voiceIndependence needs ${name} aligned slot for slot with the lines; received ${flags.length} and ${upper.length}`
1753
2154
  );
1754
2155
  }
1755
2156
  }
1756
- const slots = lead.map((leadNote, index) => {
1757
- const counterNote = _nullishCoalesce(counter[index], () => ( null));
2157
+ const slots = upper.map((leadNote, index) => {
2158
+ const counterNote = _nullishCoalesce(lower[index], () => ( null));
1758
2159
  return {
1759
2160
  lead: leadNote,
1760
2161
  counter: counterNote,
@@ -1780,12 +2181,12 @@ function voiceIndependence(lead, counter, opts) {
1780
2181
  let filled = 0;
1781
2182
  let gaps = 0;
1782
2183
  for (let index = 0; index < slots.length; index += 1) {
1783
- const leadHolds = !attacksAt(index, lead, _optionalChain([opts, 'optionalAccess', _11 => _11.leadAttacks]));
2184
+ const leadHolds = !attacksAt(index, lead, _optionalChain([opts, 'optionalAccess', _17 => _17.leadAttacks]));
1784
2185
  if (!leadHolds) {
1785
2186
  continue;
1786
2187
  }
1787
2188
  gaps += 1;
1788
- if (attacksAt(index, counter, _optionalChain([opts, 'optionalAccess', _12 => _12.counterAttacks]))) {
2189
+ if (attacksAt(index, counter, _optionalChain([opts, 'optionalAccess', _18 => _18.counterAttacks]))) {
1789
2190
  filled += 1;
1790
2191
  }
1791
2192
  }
@@ -1805,8 +2206,8 @@ function voiceIndependence(lead, counter, opts) {
1805
2206
  totalGap += Math.abs(gap);
1806
2207
  minGap = Math.min(minGap, Math.abs(gap));
1807
2208
  orderSum += Math.sign(gap);
1808
- const interval = _chunkQ2WGKJNNcjs.spelledInterval.call(void 0, slot.counter, slot.lead);
1809
- const perfect = classifySpelledInterval(interval) === 0 /* PerfectConsonance */ || (_nullishCoalesce(_optionalChain([opts, 'optionalAccess', _13 => _13.countFourths]), () => ( false))) && interval.quality === "P" && simpleIntervalNumber(interval.number) === 4;
2209
+ const interval = _chunkS5BKZZGMcjs.spelledInterval.call(void 0, slot.counter, slot.lead);
2210
+ const perfect = classifySpelledInterval(interval) === 0 /* PerfectConsonance */ || (_nullishCoalesce(_optionalChain([opts, 'optionalAccess', _19 => _19.countFourths]), () => ( false))) && interval.quality === "P" && simpleIntervalNumber(interval.number) === 4;
1810
2211
  perfectRun = perfect ? perfectRun + 1 : 0;
1811
2212
  longestPerfectRun = Math.max(longestPerfectRun, perfectRun);
1812
2213
  }
@@ -1840,18 +2241,19 @@ function voiceIndependence(lead, counter, opts) {
1840
2241
  }
1841
2242
 
1842
2243
  // src/theory/counterpoint/index.ts
2244
+ var HIDDEN_PARALLEL_READINGS = Object.freeze(["fourPart", "twoVoice"]);
1843
2245
  function createsVoiceCrossing(upper, lower) {
1844
2246
  return pitchOf(upper) < pitchOf(lower);
1845
2247
  }
1846
2248
  function createsVerticalDissonance(a, b, twoVoice) {
1847
2249
  if (typeof a !== "number" && typeof b !== "number") {
1848
- return classifySpelledInterval(_chunkQ2WGKJNNcjs.spelledInterval.call(void 0, a, b), twoVoice) === 2 /* Dissonance */;
2250
+ return classifySpelledInterval(_chunkS5BKZZGMcjs.spelledInterval.call(void 0, a, b), twoVoice) === 2 /* Dissonance */;
1849
2251
  }
1850
- return !_chunkQ2WGKJNNcjs.isConsonantInterval.call(void 0, pitchOf(a) - pitchOf(b), twoVoice);
2252
+ return !_chunkS5BKZZGMcjs.isConsonantInterval.call(void 0, pitchOf(a) - pitchOf(b), twoVoice);
1851
2253
  }
1852
2254
  function isForbiddenMelodicLeap(prev, cur) {
1853
2255
  if (typeof prev !== "number" && typeof cur !== "number") {
1854
- const interval = _chunkQ2WGKJNNcjs.spelledInterval.call(void 0, prev, cur);
2256
+ const interval = _chunkS5BKZZGMcjs.spelledInterval.call(void 0, prev, cur);
1855
2257
  if (Math.abs(interval.semitones) > 12 || interval.number > 8) {
1856
2258
  return true;
1857
2259
  }
@@ -1870,11 +2272,11 @@ function isAugmentedMelodicInterval(prev, cur) {
1870
2272
  if (typeof prev === "number" || typeof cur === "number") {
1871
2273
  return false;
1872
2274
  }
1873
- const interval = _chunkQ2WGKJNNcjs.spelledInterval.call(void 0, prev, cur);
2275
+ const interval = _chunkS5BKZZGMcjs.spelledInterval.call(void 0, prev, cur);
1874
2276
  return interval.number >= 2 && interval.quality.startsWith("A");
1875
2277
  }
1876
2278
  function simpleClass(semitones) {
1877
- return _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, Math.abs(semitones));
2279
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, Math.abs(semitones));
1878
2280
  }
1879
2281
  function isPerfectClass(cls) {
1880
2282
  return cls === 0 || cls === 7;
@@ -1917,7 +2319,8 @@ function createsParallelUnison(aPrev, aCur, bPrev, bCur) {
1917
2319
  }
1918
2320
  return a1 === b1 && a0 === b0;
1919
2321
  }
1920
- function createsHiddenParallelPerfect(aPrev, aCur, bPrev, bCur) {
2322
+ function createsHiddenParallelPerfect(aPrev, aCur, bPrev, bCur, strictness = "fourPart") {
2323
+ _chunkS5BKZZGMcjs.assertOneOf.call(void 0, strictness, HIDDEN_PARALLEL_READINGS, "strictness");
1921
2324
  const a0 = pitchOf(aPrev);
1922
2325
  const a1 = pitchOf(aCur);
1923
2326
  const b0 = pitchOf(bPrev);
@@ -1933,7 +2336,7 @@ function createsHiddenParallelPerfect(aPrev, aCur, bPrev, bCur) {
1933
2336
  return false;
1934
2337
  }
1935
2338
  const upperMove = a1 >= b1 ? aMove : bMove;
1936
- if (Math.abs(upperMove) <= 2) {
2339
+ if (strictness === "fourPart" && Math.abs(upperMove) <= 2) {
1937
2340
  return false;
1938
2341
  }
1939
2342
  return true;
@@ -1948,7 +2351,12 @@ function createsBattuta(aPrev, aCur, bPrev, bCur) {
1948
2351
  if (!bothVoicesMove(aMove, bMove) || similarMotion(aMove, bMove)) {
1949
2352
  return false;
1950
2353
  }
1951
- if (simpleClass(a1 - b1) !== 0) {
2354
+ const nowClass = simpleClass(a1 - b1);
2355
+ if (nowClass !== 0) {
2356
+ return false;
2357
+ }
2358
+ const prevClass = simpleClass(a0 - b0);
2359
+ if (prevClass === nowClass) {
1952
2360
  return false;
1953
2361
  }
1954
2362
  const upperMove = a1 >= b1 ? aMove : bMove;
@@ -1962,14 +2370,15 @@ function createsVoiceOverlap(upperPrev, upperCur, lowerPrev, lowerCur) {
1962
2370
  return u1 < l0 || l1 > u0;
1963
2371
  }
1964
2372
  function exceedsSpacing(upper, lower, maxSemitones = 12) {
2373
+ _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, maxSemitones, "maxSemitones");
1965
2374
  return Math.abs(pitchOf(upper) - pitchOf(lower)) > maxSemitones;
1966
2375
  }
1967
2376
  function isLeadingToneResolution(prev, cur, key) {
1968
2377
  const prevPitch = pitchOf(prev);
1969
2378
  const curPitch = pitchOf(cur);
1970
- const tonic = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, key.rootPc);
2379
+ const tonic = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, toKeyScale(key).rootPc);
1971
2380
  const leading = (tonic + 11) % 12;
1972
- if (_chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, prevPitch) !== leading || _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, curPitch) !== tonic) {
2381
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, prevPitch) !== leading || _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, curPitch) !== tonic) {
1973
2382
  return false;
1974
2383
  }
1975
2384
  return curPitch > prevPitch && curPitch - prevPitch <= 2;
@@ -2008,8 +2417,19 @@ var PROFILE_WEIGHTS = Object.freeze({
2008
2417
  });
2009
2418
  var SAFETY_PROFILES = Object.keys(PROFILE_WEIGHTS);
2010
2419
  function profileWeights(profile, overrides) {
2011
- const base = PROFILE_WEIGHTS[_chunkQ2WGKJNNcjs.assertOneOf.call(void 0, profile, SAFETY_PROFILES, "safety profile")];
2012
- return { ...base, ...overrides };
2420
+ const base = PROFILE_WEIGHTS[_chunkS5BKZZGMcjs.assertOneOf.call(void 0, profile, SAFETY_PROFILES, "safety profile")];
2421
+ if (overrides === void 0) {
2422
+ return { ...base };
2423
+ }
2424
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, overrides, "overrides");
2425
+ const weights = { ...base };
2426
+ for (const [field, value] of Object.entries(overrides)) {
2427
+ if (value === void 0) {
2428
+ continue;
2429
+ }
2430
+ weights[field] = _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, value, `weight ${field}`);
2431
+ }
2432
+ return weights;
2013
2433
  }
2014
2434
  var NoteSafety = /* @__PURE__ */ ((NoteSafety2) => {
2015
2435
  NoteSafety2[NoteSafety2["Safe"] = 0] = "Safe";
@@ -2017,6 +2437,7 @@ var NoteSafety = /* @__PURE__ */ ((NoteSafety2) => {
2017
2437
  NoteSafety2[NoteSafety2["Dissonant"] = 2] = "Dissonant";
2018
2438
  return NoteSafety2;
2019
2439
  })(NoteSafety || {});
2440
+ Object.freeze(NoteSafety);
2020
2441
  var ReasonFlag = /* @__PURE__ */ ((ReasonFlag2) => {
2021
2442
  ReasonFlag2[ReasonFlag2["ChordTone"] = 1] = "ChordTone";
2022
2443
  ReasonFlag2[ReasonFlag2["Tension"] = 2] = "Tension";
@@ -2037,6 +2458,7 @@ var ReasonFlag = /* @__PURE__ */ ((ReasonFlag2) => {
2037
2458
  ReasonFlag2[ReasonFlag2["MelodicTritone"] = 65536] = "MelodicTritone";
2038
2459
  return ReasonFlag2;
2039
2460
  })(ReasonFlag || {});
2461
+ Object.freeze(ReasonFlag);
2040
2462
  var SUGGESTION_WINDOW = 12;
2041
2463
  var MAX_SUGGESTIONS = 3;
2042
2464
  var REASON_SEVERITY = [
@@ -2072,7 +2494,7 @@ function avoidsMajorSeventh(chord) {
2072
2494
  return chord.intervals.includes(4) && chord.intervals.includes(10);
2073
2495
  }
2074
2496
  function isStructuralChordDissonance(a, b, chord) {
2075
- const rawClass = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, Math.abs(a - b));
2497
+ const rawClass = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, Math.abs(a - b));
2076
2498
  const aOffset = intervalAboveRoot(a, chord);
2077
2499
  const bOffset = intervalAboveRoot(b, chord);
2078
2500
  const rootSeventh = aOffset === 0 && (bOffset === 10 || bOffset === 11) || bOffset === 0 && (aOffset === 10 || aOffset === 11);
@@ -2091,31 +2513,33 @@ function stepResolution(pitch, chord) {
2091
2513
  }
2092
2514
  function evaluateSafety(q, opts = {}) {
2093
2515
  assertSafetyContext(q);
2094
- _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, q.candidatePitch, "candidatePitch");
2095
- return evaluateInternal(q, _nullishCoalesce(opts.suggestions, () => ( true)));
2516
+ _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, q.candidatePitch, "candidatePitch");
2517
+ const options = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
2518
+ return evaluateInternal(q, _nullishCoalesce(options.suggestions, () => ( true)), _nullishCoalesce(options.partners, () => ( false)));
2096
2519
  }
2097
2520
  function assertSafetyContext(q) {
2098
- _chunkQ2WGKJNNcjs.assertOneOf.call(void 0, q.profile, SAFETY_PROFILES, "safety profile");
2099
- if (q.prevPitch !== void 0) _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, q.prevPitch, "prevPitch");
2100
- if (q.vocalLow !== void 0) _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, q.vocalLow, "vocalLow");
2101
- if (q.vocalHigh !== void 0) _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, q.vocalHigh, "vocalHigh");
2521
+ _chunkS5BKZZGMcjs.assertRecord.call(void 0, q, "safety query");
2522
+ _chunkS5BKZZGMcjs.assertOneOf.call(void 0, q.profile, SAFETY_PROFILES, "safety profile");
2523
+ if (q.prevPitch !== void 0) _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, q.prevPitch, "prevPitch");
2524
+ if (q.vocalLow !== void 0) _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, q.vocalLow, "vocalLow");
2525
+ if (q.vocalHigh !== void 0) _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, q.vocalHigh, "vocalHigh");
2102
2526
  if (q.vocalLow !== void 0 && q.vocalHigh !== void 0 && q.vocalLow > q.vocalHigh) {
2103
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("vocalLow must not exceed vocalHigh");
2527
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("vocalLow must not exceed vocalHigh");
2104
2528
  }
2105
2529
  if (!Array.isArray(q.otherVoices)) {
2106
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`otherVoices must be an array; received ${typeof q.otherVoices}`);
2530
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`otherVoices must be an array; received ${typeof q.otherVoices}`);
2107
2531
  }
2108
- _chunkQ2WGKJNNcjs.assertGenerationBudget.call(void 0, q.otherVoices.length, "other voices");
2532
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, q.otherVoices.length, "other voices");
2109
2533
  for (let index = 0; index < q.otherVoices.length; index += 1) {
2110
2534
  const voice = q.otherVoices[index];
2111
2535
  if (!voice) continue;
2112
- _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, voice.pitch, `otherVoices[${index}].pitch`);
2536
+ _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, voice.pitch, `otherVoices[${index}].pitch`);
2113
2537
  if (voice.prevPitch !== void 0) {
2114
- _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, voice.prevPitch, `otherVoices[${index}].prevPitch`);
2538
+ _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, voice.prevPitch, `otherVoices[${index}].prevPitch`);
2115
2539
  }
2116
2540
  }
2117
2541
  }
2118
- function evaluateInternal(q, collectSuggestions) {
2542
+ function evaluateInternal(q, collectSuggestions, collectPartners) {
2119
2543
  let reasons = 0;
2120
2544
  let level = 0 /* Safe */;
2121
2545
  let resolveTo;
@@ -2186,41 +2610,58 @@ function evaluateInternal(q, collectSuggestions) {
2186
2610
  reasons |= 512 /* MajorSeventh */;
2187
2611
  }
2188
2612
  }
2613
+ let bass = pitch;
2614
+ let top = pitch;
2615
+ for (const ov of q.otherVoices) {
2616
+ bass = Math.min(bass, ov.pitch);
2617
+ top = Math.max(top, ov.pitch);
2618
+ }
2619
+ const pairReasons = collectPartners ? new Array(q.otherVoices.length).fill(0) : void 0;
2620
+ const flagPair = (index, flag) => {
2621
+ reasons |= flag;
2622
+ if (pairReasons !== void 0) {
2623
+ pairReasons[index] = (_nullishCoalesce(pairReasons[index], () => ( 0))) | flag;
2624
+ }
2625
+ };
2189
2626
  if (q.strongBeat) {
2190
- const twoVoice = q.otherVoices.length === 1;
2191
2627
  const prev = q.prevPitch;
2192
- for (const ov of q.otherVoices) {
2193
- if (!createsVerticalDissonance(pitch, ov.pitch, twoVoice)) {
2628
+ for (const [index, ov] of q.otherVoices.entries()) {
2629
+ const againstBass = pitch === bass || ov.pitch === bass;
2630
+ if (!createsVerticalDissonance(pitch, ov.pitch, againstBass)) {
2194
2631
  continue;
2195
2632
  }
2196
2633
  if (chord && isStructuralChordDissonance(pitch, ov.pitch, chord)) {
2197
2634
  continue;
2198
2635
  }
2199
- reasons |= 1024 /* VerticalDissonance */;
2636
+ if (chord && q.profile === "pop" && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, Math.abs(pitch - ov.pitch)) === 5 && isChordMember(pitch, chord) && isChordMember(ov.pitch, chord)) {
2637
+ continue;
2638
+ }
2639
+ flagPair(index, 1024 /* VerticalDissonance */);
2200
2640
  raise(2 /* Dissonant */);
2201
- if (prev !== void 0 && pitch === prev && ov.prevPitch !== void 0 && !createsVerticalDissonance(prev, ov.prevPitch, twoVoice)) {
2202
- reasons |= 16384 /* Suspension */;
2641
+ if (prev !== void 0 && pitch === prev && ov.prevPitch !== void 0 && !createsVerticalDissonance(prev, ov.prevPitch, againstBass)) {
2642
+ flagPair(index, 16384 /* Suspension */);
2203
2643
  }
2204
2644
  }
2205
2645
  }
2206
2646
  if (q.prevPitch !== void 0) {
2207
2647
  const prev = q.prevPitch;
2208
- for (const ov of q.otherVoices) {
2648
+ for (const [index, ov] of q.otherVoices.entries()) {
2209
2649
  if (ov.prevPitch === void 0) {
2210
2650
  continue;
2211
2651
  }
2212
2652
  if (createsParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
2213
- reasons |= 2048 /* ParallelPerfect */;
2653
+ flagPair(index, 2048 /* ParallelPerfect */);
2214
2654
  raise(parallelSeverity);
2215
2655
  }
2216
- if (createsHiddenParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
2217
- reasons |= 4096 /* HiddenParallel */;
2656
+ const outerPair = pitch === top && ov.pitch === bass || pitch === bass && ov.pitch === top;
2657
+ if (outerPair && createsHiddenParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {
2658
+ flagPair(index, 4096 /* HiddenParallel */);
2218
2659
  raise(parallelSeverity);
2219
2660
  }
2220
2661
  const crossedNow = pitch - ov.pitch;
2221
2662
  const crossedPrev = prev - ov.prevPitch;
2222
2663
  if (crossedNow !== 0 && crossedPrev !== 0 && crossedNow > 0 !== crossedPrev > 0) {
2223
- reasons |= 8192 /* VoiceCrossing */;
2664
+ flagPair(index, 8192 /* VoiceCrossing */);
2224
2665
  raise(parallelSeverity);
2225
2666
  }
2226
2667
  }
@@ -2236,6 +2677,17 @@ function evaluateInternal(q, collectSuggestions) {
2236
2677
  result.suggestions = suggestions;
2237
2678
  }
2238
2679
  }
2680
+ if (pairReasons !== void 0) {
2681
+ const partners = [];
2682
+ pairReasons.forEach((pair, index) => {
2683
+ if (pair !== 0) {
2684
+ partners.push({ index, reasons: pair });
2685
+ }
2686
+ });
2687
+ if (partners.length > 0) {
2688
+ result.partners = partners;
2689
+ }
2690
+ }
2239
2691
  return result;
2240
2692
  }
2241
2693
  function findSafeNearby(q, candidate) {
@@ -2245,7 +2697,7 @@ function findSafeNearby(q, candidate) {
2245
2697
  if (p < 0 || p > 127) {
2246
2698
  continue;
2247
2699
  }
2248
- if (evaluateInternal({ ...q, candidatePitch: p }, false).safety === 0 /* Safe */) {
2700
+ if (evaluateInternal({ ...q, candidatePitch: p }, false, false).safety === 0 /* Safe */) {
2249
2701
  out.push(p);
2250
2702
  if (out.length >= MAX_SUGGESTIONS) {
2251
2703
  return out;
@@ -2269,7 +2721,7 @@ var REASON_TEXT = [
2269
2721
  [2048 /* ParallelPerfect */, "Parallel perfect interval with another voice"],
2270
2722
  [4096 /* HiddenParallel */, "Hidden perfect interval by similar motion"],
2271
2723
  [8192 /* VoiceCrossing */, "Crosses another voice"],
2272
- [128 /* LargeLeap */, "Leap wider than the profile allows"],
2724
+ [128 /* LargeLeap */, "Forbidden melodic leap \u2014 a seventh or wider than an octave"],
2273
2725
  [32768 /* NeedsResolution */, "Dissonance awaiting a stepwise resolution"],
2274
2726
  [2 /* Tension */, "Chord tension"],
2275
2727
  [8 /* ScaleTone */, "Scale tone, not in the chord"],
@@ -2303,18 +2755,18 @@ function enumerateSafePitches(q, pitchLow, pitchHigh) {
2303
2755
  assertSafetyContext(q);
2304
2756
  const chordTones = [];
2305
2757
  const others = [];
2306
- _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, pitchLow, "pitchLow");
2307
- _chunkQ2WGKJNNcjs.assertMidiPitch.call(void 0, pitchHigh, "pitchHigh");
2758
+ _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, pitchLow, "pitchLow");
2759
+ _chunkS5BKZZGMcjs.assertMidiPitch.call(void 0, pitchHigh, "pitchHigh");
2308
2760
  if (pitchLow > pitchHigh) {
2309
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
2761
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
2310
2762
  `pitchLow must not exceed pitchHigh; received ${pitchLow} > ${pitchHigh}`
2311
2763
  );
2312
2764
  }
2313
- _chunkQ2WGKJNNcjs.assertGenerationBudget.call(void 0, pitchHigh - pitchLow + 1, "safe pitch candidates");
2765
+ _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, pitchHigh - pitchLow + 1, "safe pitch candidates");
2314
2766
  const low = Math.max(pitchLow, _nullishCoalesce(q.vocalLow, () => ( pitchLow)));
2315
2767
  const high = Math.min(pitchHigh, _nullishCoalesce(q.vocalHigh, () => ( pitchHigh)));
2316
2768
  for (let pitch = high; pitch >= low; pitch -= 1) {
2317
- const result = evaluateInternal({ ...q, candidatePitch: pitch }, false);
2769
+ const result = evaluateInternal({ ...q, candidatePitch: pitch }, false, false);
2318
2770
  if (result.safety === 2 /* Dissonant */) {
2319
2771
  continue;
2320
2772
  }
@@ -2327,7 +2779,7 @@ function enumerateSafePitches(q, pitchLow, pitchHigh) {
2327
2779
  return [...chordTones, ...others];
2328
2780
  }
2329
2781
 
2330
- // src/theory/symbol/index.ts
2782
+ // src/theory/symbol/grammar.ts
2331
2783
  var ROOT_RE = /^[A-Ga-g](?:#{1,2}|b{1,2}|x)?/;
2332
2784
  var BASS_RE = /^[A-Ga-g](?:#{1,2}|b{1,2}|x)?$/;
2333
2785
  var DEFAULT_SYSTEM = "english";
@@ -2430,51 +2882,6 @@ var QUALITY_MAP = {
2430
2882
  h: "m7b5",
2431
2883
  h7: "m7b5"
2432
2884
  };
2433
- var CANONICAL_SUFFIX = {
2434
- maj: "",
2435
- min: "m",
2436
- dim: "dim",
2437
- aug: "aug",
2438
- maj7: "maj7",
2439
- min7: "m7",
2440
- dom7: "7",
2441
- dim7: "dim7",
2442
- m7b5: "m7b5",
2443
- minMaj7: "mMaj7",
2444
- minMaj9: "mMaj9",
2445
- minMaj11: "mMaj11",
2446
- minMaj13: "mMaj13",
2447
- aug7: "aug7",
2448
- augMaj7: "augMaj7",
2449
- majb5: "maj(b5)",
2450
- "6": "6",
2451
- min6: "m6",
2452
- "6/9": "6/9",
2453
- sus2: "sus2",
2454
- sus4: "sus4",
2455
- add9: "add9",
2456
- add11: "add11",
2457
- maj9: "maj9",
2458
- min9: "m9",
2459
- dom9: "9",
2460
- "7b9": "7b9",
2461
- "7#9": "7#9",
2462
- "7#11": "7#11",
2463
- "7b13": "7b13",
2464
- "11": "11",
2465
- "13": "13",
2466
- "5": "5",
2467
- "7sus4": "7sus4",
2468
- "7b5": "7b5",
2469
- "7alt": "7alt",
2470
- "13b9": "13b9",
2471
- maj13: "maj13",
2472
- "maj7#11": "maj7#11",
2473
- min11: "m11",
2474
- min13: "m13",
2475
- minAdd9: "madd9",
2476
- "min6/9": "m6/9"
2477
- };
2478
2885
  function knownQuality(suffix) {
2479
2886
  return Object.hasOwn(QUALITY_MAP, suffix) ? QUALITY_MAP[suffix] : void 0;
2480
2887
  }
@@ -2486,6 +2893,7 @@ var BASE_TOKENS = [
2486
2893
  ["dim", "dim"],
2487
2894
  ["aug", "aug"],
2488
2895
  ["maj", "major"],
2896
+ ["Maj", "major"],
2489
2897
  ["m", "min"],
2490
2898
  ["-", "min"],
2491
2899
  ["o", "dim"],
@@ -2495,6 +2903,10 @@ var BASE_TOKENS = [
2495
2903
  ["\u0394", "major"],
2496
2904
  ["^", "major"]
2497
2905
  ];
2906
+ var COMPOUND_TOKENS = [
2907
+ ["\xF8", { base: "dim", seventh: "min7" }],
2908
+ ["h", { base: "dim", seventh: "min7" }]
2909
+ ];
2498
2910
  var MAJOR_MARKERS = [
2499
2911
  ["maj", "word"],
2500
2912
  ["Maj", "word"],
@@ -2513,6 +2925,27 @@ var ADDED_DEGREES = {
2513
2925
  };
2514
2926
  var ALTERABLE = { "5": 5, "9": 9, "11": 11, "13": 13 };
2515
2927
  var OMITTABLE = { "1": 1, "3": 3, "5": 5 };
2928
+ function alterationKey(degree, alter) {
2929
+ return `${degree}:${alter}`;
2930
+ }
2931
+ function writeAlteration(draft, degree, alter) {
2932
+ if (draft.implied.delete(degree)) {
2933
+ draft.alterations.delete(alterationKey(degree, 0));
2934
+ }
2935
+ draft.alterations.set(alterationKey(degree, alter), { degree, alter });
2936
+ }
2937
+ function implyAlteration(draft, degree) {
2938
+ draft.implied.add(degree);
2939
+ draft.alterations.set(alterationKey(degree, 0), { degree, alter: 0 });
2940
+ }
2941
+ function altersDegree(draft, degree) {
2942
+ for (const alteration of draft.alterations.values()) {
2943
+ if (alteration.degree === degree) {
2944
+ return true;
2945
+ }
2946
+ }
2947
+ return false;
2948
+ }
2516
2949
  function matchToken(text, at, table) {
2517
2950
  let found;
2518
2951
  for (const [token, value] of table) {
@@ -2536,15 +2969,18 @@ function stackTo(draft, top) {
2536
2969
  draft.additions.add(6);
2537
2970
  return;
2538
2971
  }
2539
- draft.seventh = draft.major ? "maj7" : draft.base === "dim" ? "dim7" : "min7";
2972
+ draft.seventh = draft.major ? "maj7" : _nullishCoalesce(draft.seventhOverBase, () => ( (draft.base === "dim" ? "dim7" : "min7")));
2540
2973
  if (top >= 9) {
2541
- draft.alterations.set(9, 0);
2974
+ implyAlteration(draft, 9);
2542
2975
  }
2543
2976
  if (top === 11) {
2544
- draft.alterations.set(11, 0);
2977
+ implyAlteration(draft, 11);
2978
+ if (!draft.major && draft.base === "maj") {
2979
+ draft.omissions.add(3);
2980
+ }
2545
2981
  }
2546
2982
  if (top === 13) {
2547
- draft.alterations.set(13, 0);
2983
+ implyAlteration(draft, 13);
2548
2984
  }
2549
2985
  }
2550
2986
  function applyFigure(draft, text, at) {
@@ -2560,7 +2996,7 @@ function applyFigure(draft, text, at) {
2560
2996
  if (text.startsWith("alt", at)) {
2561
2997
  draft.base = "aug";
2562
2998
  draft.seventh = "min7";
2563
- draft.alterations.set(9, 1);
2999
+ writeAlteration(draft, 9, 1);
2564
3000
  return at + 3;
2565
3001
  }
2566
3002
  if (text.startsWith("add", at)) {
@@ -2587,18 +3023,42 @@ function applyFigure(draft, text, at) {
2587
3023
  ["b", -1],
2588
3024
  ["#", 1]
2589
3025
  ]);
2590
- const from = _nullishCoalesce(_optionalChain([accidental, 'optionalAccess', _14 => _14.next]), () => ( at));
3026
+ const from = _nullishCoalesce(_optionalChain([accidental, 'optionalAccess', _20 => _20.next]), () => ( at));
2591
3027
  const degree = matchNumberToken(text, from, ALTERABLE);
2592
3028
  if (degree === void 0) {
2593
3029
  return void 0;
2594
3030
  }
2595
- draft.alterations.set(degree.value, _nullishCoalesce(_optionalChain([accidental, 'optionalAccess', _15 => _15.value]), () => ( 0)));
3031
+ if (degree.value === 5 && (accidental === void 0 || writesFifth(draft))) {
3032
+ return void 0;
3033
+ }
3034
+ writeAlteration(draft, degree.value, _nullishCoalesce(_optionalChain([accidental, 'optionalAccess', _21 => _21.value]), () => ( 0)));
2596
3035
  return degree.next;
2597
3036
  }
3037
+ function writesFifth(draft) {
3038
+ return draft.base === "dim" || draft.base === "aug" || altersDegree(draft, 5);
3039
+ }
2598
3040
  function matchNumberToken(text, at, table) {
2599
3041
  const entries = Object.entries(table).map(([token, value]) => [token, value]);
2600
3042
  return matchToken(text, at, entries);
2601
3043
  }
3044
+ function openings(core) {
3045
+ const found = [];
3046
+ for (const [token, value] of COMPOUND_TOKENS) {
3047
+ if (core.startsWith(token)) {
3048
+ found.push({ length: token.length, base: value.base, seventh: value.seventh });
3049
+ }
3050
+ }
3051
+ for (const [token, value] of BASE_TOKENS) {
3052
+ if (!core.startsWith(token)) {
3053
+ continue;
3054
+ }
3055
+ found.push(
3056
+ value === "major" ? { length: token.length, major: true, glyph: token === "\u0394" || token === "^" } : { length: token.length, base: value }
3057
+ );
3058
+ }
3059
+ found.sort((a, b) => b.length - a.length);
3060
+ return [...found, void 0];
3061
+ }
2602
3062
  function structuralSpec(text) {
2603
3063
  let core = "";
2604
3064
  const groups = [];
@@ -2626,24 +3086,37 @@ function structuralSpec(text) {
2626
3086
  if (inside) {
2627
3087
  return void 0;
2628
3088
  }
3089
+ for (const opening of openings(core)) {
3090
+ const spec = readCore(core, groups, opening);
3091
+ if (spec !== void 0) {
3092
+ return spec;
3093
+ }
3094
+ }
3095
+ return void 0;
3096
+ }
3097
+ function readCore(core, groups, opening) {
2629
3098
  const draft = {
2630
3099
  base: "maj",
2631
3100
  alterations: /* @__PURE__ */ new Map(),
2632
3101
  additions: /* @__PURE__ */ new Set(),
2633
3102
  omissions: /* @__PURE__ */ new Set(),
3103
+ implied: /* @__PURE__ */ new Set(),
2634
3104
  major: false
2635
3105
  };
2636
3106
  let at = 0;
2637
- const opening = matchToken(core, at, BASE_TOKENS);
2638
3107
  let markerGlyph = false;
2639
3108
  let markerAfterBase = false;
2640
3109
  if (opening !== void 0) {
2641
- at = opening.next;
2642
- if (opening.value === "major") {
3110
+ at = opening.length;
3111
+ if (opening.major === true) {
2643
3112
  draft.major = true;
2644
- markerGlyph = core.startsWith("\u0394") || core.startsWith("^");
2645
- } else {
2646
- draft.base = opening.value;
3113
+ markerGlyph = opening.glyph === true;
3114
+ } else if (opening.base !== void 0) {
3115
+ draft.base = opening.base;
3116
+ if (opening.seventh !== void 0) {
3117
+ draft.seventh = opening.seventh;
3118
+ draft.seventhOverBase = opening.seventh;
3119
+ }
2647
3120
  }
2648
3121
  }
2649
3122
  if (!draft.major) {
@@ -2688,7 +3161,7 @@ function structuralSpec(text) {
2688
3161
  rootPc: 0,
2689
3162
  base: draft.base,
2690
3163
  ...draft.seventh === void 0 ? {} : { seventh: draft.seventh },
2691
- alterations: [...draft.alterations].map(([degree, alter]) => ({ degree, alter })),
3164
+ alterations: [...draft.alterations.values()],
2692
3165
  additions: [...draft.additions],
2693
3166
  omissions: [...draft.omissions]
2694
3167
  };
@@ -2698,10 +3171,10 @@ function suffixSpec(suffix) {
2698
3171
  return quality === void 0 ? structuralSpec(suffix) : chordSpecForQuality(quality, 0);
2699
3172
  }
2700
3173
  function assertNamingSystem(system) {
2701
- _chunkQ2WGKJNNcjs.formatNote.call(void 0, SYSTEM_PROBE, { system });
3174
+ _chunkS5BKZZGMcjs.formatNote.call(void 0, SYSTEM_PROBE, { system });
2702
3175
  }
2703
3176
  function bareNote(text, system) {
2704
- const read = _chunkQ2WGKJNNcjs.tryParseNote.call(void 0, text, { system });
3177
+ const read = _chunkS5BKZZGMcjs.tryParseNote.call(void 0, text, { system });
2705
3178
  if (!read.ok) {
2706
3179
  return void 0;
2707
3180
  }
@@ -2710,12 +3183,13 @@ function bareNote(text, system) {
2710
3183
  function rootSplits(text, system) {
2711
3184
  if (system === "english") {
2712
3185
  const match = text.match(ROOT_RE);
2713
- const token = _optionalChain([match, 'optionalAccess', _16 => _16[0]]);
2714
- return token === void 0 ? [] : [{ root: _chunkQ2WGKJNNcjs.parseNote.call(void 0, token, { system }), rest: text.slice(token.length) }];
3186
+ const token = _optionalChain([match, 'optionalAccess', _22 => _22[0]]);
3187
+ return token === void 0 ? [] : [{ root: _chunkS5BKZZGMcjs.parseNote.call(void 0, token, { system }), rest: text.slice(token.length) }];
2715
3188
  }
2716
3189
  assertNamingSystem(system);
2717
3190
  const splits = [];
2718
- for (let end = text.length; end > 0; end -= 1) {
3191
+ const longest = Math.min(text.length, _chunkS5BKZZGMcjs.maxNoteNameLength.call(void 0, system));
3192
+ for (let end = longest; end > 0; end -= 1) {
2719
3193
  const root = bareNote(text.slice(0, end), system);
2720
3194
  if (root !== void 0) {
2721
3195
  splits.push({ root, rest: text.slice(end) });
@@ -2725,14 +3199,14 @@ function rootSplits(text, system) {
2725
3199
  }
2726
3200
  function bassNote(text, system) {
2727
3201
  if (system === "english") {
2728
- return BASS_RE.test(text) ? _chunkQ2WGKJNNcjs.parseNote.call(void 0, text, { system }) : void 0;
3202
+ return BASS_RE.test(text) ? _chunkS5BKZZGMcjs.parseNote.call(void 0, text, { system }) : void 0;
2729
3203
  }
2730
3204
  return bareNote(text, system);
2731
3205
  }
2732
3206
  function makeSpelledChord(root, spec, bass) {
2733
- const rooted = { ...spec, rootPc: _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, root) };
3207
+ const rooted = { ...spec, rootPc: _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, root) };
2734
3208
  if (bass !== void 0) {
2735
- rooted.bassPc = _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, bass);
3209
+ rooted.bassPc = _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, bass);
2736
3210
  }
2737
3211
  const chord = chordFromSpec(rooted);
2738
3212
  chord.rootSpelling = { letter: root.letter, alter: root.alter };
@@ -2753,82 +3227,53 @@ function chordAfterRoot(root, rest, system) {
2753
3227
  const spec = suffixSpec(rest);
2754
3228
  return spec === void 0 ? void 0 : makeSpelledChord(root, spec);
2755
3229
  }
2756
- function parseChordSymbol(text, opts) {
2757
- return _chunkQ2WGKJNNcjs.unwrapParse.call(void 0, tryParseChordSymbol(text, opts));
2758
- }
2759
- function tryParseChordSymbol(text, opts) {
2760
- try {
2761
- if (typeof text !== "string") {
2762
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`chord symbol must be a string; received ${typeof text}`);
2763
- }
2764
- const system = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _17 => _17.system]), () => ( DEFAULT_SYSTEM));
2765
- const trimmed = text.trim().replaceAll("\u266F", "#").replaceAll("\u266D", "b").replaceAll("\u{1D12A}", "x").replaceAll("\u{1D12B}", "bb").replaceAll("\u2206", "\u0394");
2766
- const splits = rootSplits(trimmed, system);
2767
- if (splits.length === 0) {
2768
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Invalid chord symbol: ${text}`);
2769
- }
2770
- for (const { root, rest } of splits) {
2771
- const chord = chordAfterRoot(root, rest, system);
2772
- if (chord !== void 0) {
2773
- return { ok: true, value: chord };
2774
- }
2775
- }
2776
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unrecognized chord quality: ${text}`);
2777
- } catch (error) {
2778
- return _chunkQ2WGKJNNcjs.parseFailure.call(void 0, error);
2779
- }
2780
- }
2781
- function normalizedChord(data) {
2782
- if (!Array.isArray(data.intervals)) {
2783
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)("chord.intervals must be an array of semitone offsets");
2784
- }
2785
- const copy = {
2786
- rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, data.rootPc, "chord.rootPc")),
2787
- quality: data.quality,
2788
- // Each offset is checked rather than copied blind: a chord holding a NaN
2789
- // interval voices, spells and formats as a chord that looks real, and the
2790
- // failure surfaces wherever the number is finally used.
2791
- intervals: data.intervals.map(
2792
- (interval, index) => _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, interval, `chord.intervals[${index}]`)
2793
- )
2794
- };
2795
- if (data.bassPc !== void 0) {
2796
- copy.bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, _chunkQ2WGKJNNcjs.assertFiniteNumber.call(void 0, data.bassPc, "chord.bassPc"));
2797
- }
2798
- if (data.rootSpelling !== void 0) {
2799
- copy.rootSpelling = data.rootSpelling;
2800
- }
2801
- if (data.bassSpelling !== void 0) {
2802
- copy.bassSpelling = data.bassSpelling;
2803
- }
2804
- if (data.toneSpellings !== void 0) {
2805
- copy.toneSpellings = data.toneSpellings;
2806
- }
2807
- return copy;
2808
- }
2809
- function toChordData(value) {
2810
- if (typeof value === "string") {
2811
- return parseChordSymbol(value);
2812
- }
2813
- if (typeof value === "object" && value !== null) {
2814
- const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
2815
- return normalizedChord(data);
2816
- }
2817
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(
2818
- `chord must be a chord symbol or chord data; received ${typeof value}`
2819
- );
2820
- }
2821
- function symbolName(note, system) {
2822
- const name = _chunkQ2WGKJNNcjs.formatNote.call(void 0, note, { system });
2823
- return system === "german" ? `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` : name;
2824
- }
2825
- function pitchClassName(pc, hint, system, flats, inheritFlats) {
2826
- if (flats === void 0 && hint !== void 0 && _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, hint) === pc) {
2827
- return symbolName(hint, system);
2828
- }
2829
- const note = _chunkQ2WGKJNNcjs.midiToNote.call(void 0, 60 + pc, _nullishCoalesce(flats, () => ( inheritFlats)) ? "flat" : "sharp");
2830
- return symbolName({ letter: note.letter, alter: note.alter }, system);
2831
- }
3230
+
3231
+ // src/theory/symbol/suffix.ts
3232
+ var CANONICAL_SUFFIX = {
3233
+ maj: "",
3234
+ min: "m",
3235
+ dim: "dim",
3236
+ aug: "aug",
3237
+ maj7: "maj7",
3238
+ min7: "m7",
3239
+ dom7: "7",
3240
+ dim7: "dim7",
3241
+ m7b5: "m7b5",
3242
+ minMaj7: "mMaj7",
3243
+ minMaj9: "mMaj9",
3244
+ minMaj11: "mMaj11",
3245
+ minMaj13: "mMaj13",
3246
+ aug7: "aug7",
3247
+ augMaj7: "augMaj7",
3248
+ majb5: "maj(b5)",
3249
+ "6": "6",
3250
+ min6: "m6",
3251
+ "6/9": "6/9",
3252
+ sus2: "sus2",
3253
+ sus4: "sus4",
3254
+ add9: "add9",
3255
+ add11: "add11",
3256
+ maj9: "maj9",
3257
+ min9: "m9",
3258
+ dom9: "9",
3259
+ "7b9": "7b9",
3260
+ "7#9": "7#9",
3261
+ "7#11": "7#11",
3262
+ "7b13": "7b13",
3263
+ "11": "11",
3264
+ "13": "13",
3265
+ "5": "5",
3266
+ "7sus4": "7sus4",
3267
+ "7b5": "7b5",
3268
+ "7alt": "7alt",
3269
+ "13b9": "13b9",
3270
+ maj13: "maj13",
3271
+ "maj7#11": "maj7#11",
3272
+ min11: "m11",
3273
+ min13: "m13",
3274
+ minAdd9: "madd9",
3275
+ "min6/9": "m6/9"
3276
+ };
2832
3277
  var CORE_CANDIDATES = chordQualities().map((quality) => [quality, chordSpecForQuality(quality, 0)]).filter(([, spec]) => spec.alterations.every(({ degree, alter }) => degree === 5 || alter === 0));
2833
3278
  function carriesAlteration(spec, part) {
2834
3279
  return spec.alterations.some(
@@ -2883,7 +3328,7 @@ function figureText(spec, core) {
2883
3328
  figures.push({ degree, text: `${alter < 0 ? "b" : alter > 0 ? "#" : ""}${degree}` });
2884
3329
  }
2885
3330
  for (const degree of spec.additions) {
2886
- if (_optionalChain([core, 'optionalAccess', _18 => _18.additions, 'access', _19 => _19.includes, 'call', _20 => _20(degree)]) !== true) {
3331
+ if (_optionalChain([core, 'optionalAccess', _23 => _23.additions, 'access', _24 => _24.includes, 'call', _25 => _25(degree)]) !== true) {
2887
3332
  figures.push({ degree, text: `add${degree}` });
2888
3333
  }
2889
3334
  }
@@ -2900,42 +3345,370 @@ function specSuffix(spec) {
2900
3345
  const figures = figureText(spec, coreSpec);
2901
3346
  return figures.length === 0 ? text : `${text}(${figures.join(",")})`;
2902
3347
  }
3348
+
3349
+ // src/theory/symbol/spelling.ts
3350
+ function bareSpelling(note) {
3351
+ return { letter: note.letter, alter: note.alter };
3352
+ }
3353
+ function plainSpelling(pc, asFlat) {
3354
+ return bareSpelling(_chunkS5BKZZGMcjs.midiToNote.call(void 0, 60 + pc, asFlat ? "flat" : "sharp"));
3355
+ }
3356
+ function symbolName(note, system) {
3357
+ const name = _chunkS5BKZZGMcjs.formatNote.call(void 0, note, { system });
3358
+ return system === "german" ? `${name.slice(0, 1).toUpperCase()}${name.slice(1)}` : name;
3359
+ }
3360
+ function isWrittenSpelling(spelling) {
3361
+ const written = _chunkS5BKZZGMcjs.midiToNote.call(void 0,
3362
+ 60 + _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, spelling),
3363
+ spelling.alter < 0 ? "flat" : "sharp"
3364
+ );
3365
+ return written.letter === spelling.letter && written.alter === spelling.alter;
3366
+ }
3367
+ function pitchClassName(pc, hint, system, flats, inheritFlats) {
3368
+ if (flats === void 0 && hint !== void 0 && _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, hint) === pc) {
3369
+ return symbolName(hint, system);
3370
+ }
3371
+ return symbolName(plainSpelling(pc, _nullishCoalesce(_nullishCoalesce(flats, () => ( inheritFlats)), () => ( false))), system);
3372
+ }
3373
+ function onAccidentalSide(spelling, flats) {
3374
+ return flats === void 0 || spelling.alter === 0 || spelling.alter < 0 === flats;
3375
+ }
3376
+ function bySameStep(spelling, from, to) {
3377
+ return bareSpelling(_chunkS5BKZZGMcjs.transposeByInterval.call(void 0, spelling, _chunkS5BKZZGMcjs.spelledInterval.call(void 0, from, to)));
3378
+ }
3379
+ function spellingAbove(root, letters, pc) {
3380
+ const letter = ((root.letter + letters) % 7 + 7) % 7;
3381
+ const natural = _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, { letter, alter: 0 });
3382
+ return { letter, alter: ((pc - natural) % 12 + 18) % 12 - 6 };
3383
+ }
3384
+ function carriedBass(spelling, from, to, asFlat, isOwnSpelling) {
3385
+ const carried = bySameStep(spelling, from, to);
3386
+ return isOwnSpelling(carried) || isWrittenSpelling(carried) ? carried : plainSpelling(_chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, carried), asFlat);
3387
+ }
3388
+ function hintFor(hint, pc) {
3389
+ return hint !== void 0 && _chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, hint) === pc ? hint : void 0;
3390
+ }
3391
+ function rootSpellingFor(pc, hint, flats) {
3392
+ return hint !== void 0 && onAccidentalSide(hint, flats) ? hint : plainSpelling(pc, _nullishCoalesce(flats, () => ( false)));
3393
+ }
3394
+
3395
+ // src/theory/symbol/index.ts
3396
+ function parseChordSymbol(text, opts) {
3397
+ return _chunkS5BKZZGMcjs.unwrapParse.call(void 0, tryParseChordSymbol(text, opts));
3398
+ }
3399
+ function tryParseChordSymbol(text, opts) {
3400
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
3401
+ try {
3402
+ if (typeof text !== "string") {
3403
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`chord symbol must be a string; received ${typeof text}`);
3404
+ }
3405
+ const system = _nullishCoalesce(asked.system, () => ( DEFAULT_SYSTEM));
3406
+ const trimmed = text.trim().replaceAll("\u266F", "#").replaceAll("\u266D", "b").replaceAll("\u{1D12A}", "x").replaceAll("\u{1D12B}", "bb").replaceAll("\u2206", "\u0394");
3407
+ const splits = rootSplits(trimmed, system);
3408
+ if (splits.length === 0) {
3409
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Invalid chord symbol: ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, text)}`);
3410
+ }
3411
+ for (const { root, rest } of splits) {
3412
+ const chord = chordAfterRoot(root, rest, system);
3413
+ if (chord !== void 0) {
3414
+ return { ok: true, value: chord };
3415
+ }
3416
+ }
3417
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unrecognized chord quality: ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, text)}`);
3418
+ } catch (error) {
3419
+ return _chunkS5BKZZGMcjs.parseFailure.call(void 0, error);
3420
+ }
3421
+ }
2903
3422
  function formatChordSymbol(chord, opts) {
2904
3423
  const data = toChordData(chord);
2905
3424
  if (!Object.hasOwn(CANONICAL_SUFFIX, data.quality)) {
2906
- throw new (0, _chunkQ2WGKJNNcjs.InvalidInputError)(`Unknown chord quality: ${String(data.quality)}`);
2907
- }
2908
- const system = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _21 => _21.system]), () => ( DEFAULT_SYSTEM));
2909
- const rootPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, data.rootPc);
2910
- const rootHint = data.rootSpelling;
2911
- const rootName = pitchClassName(rootPc, rootHint, system, _optionalChain([opts, 'optionalAccess', _22 => _22.flats]));
3425
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`Unknown chord quality: ${String(data.quality)}`);
3426
+ }
3427
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
3428
+ const system = _nullishCoalesce(asked.system, () => ( DEFAULT_SYSTEM));
3429
+ const flats = asked.flats;
3430
+ const rootPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, data.rootPc);
3431
+ const rootHint = hintFor(data.rootSpelling, rootPc);
3432
+ const rootSpelling = rootSpellingFor(rootPc, rootHint, flats);
2912
3433
  const suffix = specSuffix(chordSpecOf(data));
2913
- let symbol = `${rootName}${suffix}`;
2914
- if (data.bassPc !== void 0 && _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, data.bassPc) !== rootPc) {
2915
- const inheritFlats = rootHint !== void 0 && _chunkQ2WGKJNNcjs.noteToPitchClass.call(void 0, rootHint) === rootPc ? rootHint.alter < 0 : void 0;
2916
- const bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, data.bassPc);
2917
- symbol += `/${pitchClassName(bassPc, data.bassSpelling, system, _optionalChain([opts, 'optionalAccess', _23 => _23.flats]), inheritFlats)}`;
3434
+ let symbol = `${symbolName(rootSpelling, system)}${suffix}`;
3435
+ if (data.bassPc !== void 0 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, data.bassPc) !== rootPc) {
3436
+ const bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, data.bassPc);
3437
+ const bassHint = hintFor(data.bassSpelling, bassPc);
3438
+ const tonePcs = new Set(chordPitchClasses(data, { includeBass: false }));
3439
+ const ownSpelling = tonePcs.has(bassPc) ? spellingAbove(
3440
+ rootSpelling,
3441
+ (_nullishCoalesce(degreeOfInterval(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, bassPc - rootPc), data), () => ( 1))) - 1,
3442
+ bassPc
3443
+ ) : void 0;
3444
+ const bassName = rootHint !== void 0 && bassHint !== void 0 ? symbolName(
3445
+ carriedBass(
3446
+ bassHint,
3447
+ rootHint,
3448
+ rootSpelling,
3449
+ _nullishCoalesce(flats, () => ( rootSpelling.alter < 0)),
3450
+ (spelling) => ownSpelling !== void 0 && spelling.letter === ownSpelling.letter && spelling.alter === ownSpelling.alter
3451
+ ),
3452
+ system
3453
+ ) : pitchClassName(bassPc, bassHint, system, flats, rootSpelling.alter < 0);
3454
+ symbol += `/${bassName}`;
2918
3455
  }
2919
3456
  return symbol;
2920
3457
  }
3458
+ function transposeChordSpellings(spellings, semitones, flats) {
3459
+ const byLetter = (spelling) => spelling === void 0 ? void 0 : bareSpelling(_chunkS5BKZZGMcjs.transposeNote.call(void 0, spelling, semitones));
3460
+ const movedRoot = byLetter(spellings.rootSpelling);
3461
+ const movedBass = byLetter(spellings.bassSpelling);
3462
+ const side = [movedRoot, spellings.rootSpelling, movedBass, spellings.bassSpelling].find(
3463
+ (spelling) => spelling !== void 0 && spelling.alter !== 0
3464
+ );
3465
+ const preferFlats = _nullishCoalesce(flats, () => ( (side !== void 0 && side.alter < 0)));
3466
+ const octaveOnly = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, semitones) === 0;
3467
+ const plainName = (spelling, asFlat) => plainSpelling(_chunkS5BKZZGMcjs.noteToPitchClass.call(void 0, spelling), asFlat);
3468
+ const writable = (spelling) => octaveOnly || isWrittenSpelling(spelling) ? spelling : plainName(spelling, preferFlats);
3469
+ const root = spellings.rootSpelling;
3470
+ const bass = spellings.bassSpelling;
3471
+ const carriesBass = (candidate) => {
3472
+ if (root === void 0 || bass === void 0 || octaveOnly || !isWrittenSpelling(bass)) {
3473
+ return true;
3474
+ }
3475
+ const carried = bySameStep(bass, root, candidate);
3476
+ return isWrittenSpelling(carried) && onAccidentalSide(carried, flats);
3477
+ };
3478
+ const moved = {};
3479
+ if (movedRoot !== void 0) {
3480
+ const asked = flats !== void 0 && !octaveOnly && !isWrittenSpelling(movedRoot);
3481
+ const candidates = [
3482
+ ...octaveOnly || isWrittenSpelling(movedRoot) ? [movedRoot] : [],
3483
+ plainName(movedRoot, preferFlats),
3484
+ ...asked ? [movedRoot] : [],
3485
+ plainName(movedRoot, !preferFlats)
3486
+ ];
3487
+ moved.rootSpelling = _nullishCoalesce(candidates.find(carriesBass), () => ( plainName(movedRoot, preferFlats)));
3488
+ }
3489
+ const step = root !== void 0 && moved.rootSpelling !== void 0 ? _chunkS5BKZZGMcjs.spelledInterval.call(void 0, root, moved.rootSpelling) : void 0;
3490
+ const byStep = (spelling) => bareSpelling(
3491
+ step === void 0 ? _chunkS5BKZZGMcjs.transposeNote.call(void 0, spelling, semitones) : _chunkS5BKZZGMcjs.transposeByInterval.call(void 0, spelling, step)
3492
+ );
3493
+ if (bass !== void 0) {
3494
+ moved.bassSpelling = writable(byStep(bass));
3495
+ }
3496
+ if (spellings.toneSpellings !== void 0) {
3497
+ moved.toneSpellings = spellings.toneSpellings.map(byStep);
3498
+ }
3499
+ return moved;
3500
+ }
2921
3501
  function transposeChordSymbol(text, semitones, opts) {
2922
- _chunkQ2WGKJNNcjs.assertFiniteSemitones.call(void 0, semitones);
3502
+ _chunkS5BKZZGMcjs.assertFiniteSemitones.call(void 0, semitones);
2923
3503
  const chord = parseChordSymbol(text, opts);
2924
- const transposed = { ...chord, rootPc: _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.rootPc + semitones) };
2925
- if (chord.rootSpelling !== void 0) {
2926
- const hint = _chunkQ2WGKJNNcjs.transposeNote.call(void 0, chord.rootSpelling, semitones);
2927
- transposed.rootSpelling = { letter: hint.letter, alter: hint.alter };
2928
- }
3504
+ const transposed = { ...chord, rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + semitones) };
2929
3505
  if (chord.bassPc !== void 0) {
2930
- transposed.bassPc = _chunkQ2WGKJNNcjs.pitchClassOf.call(void 0, chord.bassPc + semitones);
3506
+ transposed.bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.bassPc + semitones);
2931
3507
  }
2932
- if (chord.bassSpelling !== void 0) {
2933
- const hint = _chunkQ2WGKJNNcjs.transposeNote.call(void 0, chord.bassSpelling, semitones);
2934
- transposed.bassSpelling = { letter: hint.letter, alter: hint.alter };
3508
+ const spellings = transposeChordSpellings(chord, semitones, _optionalChain([opts, 'optionalAccess', _26 => _26.flats]));
3509
+ if (spellings.rootSpelling !== void 0) {
3510
+ transposed.rootSpelling = spellings.rootSpelling;
3511
+ }
3512
+ if (spellings.bassSpelling !== void 0) {
3513
+ transposed.bassSpelling = spellings.bassSpelling;
3514
+ }
3515
+ if (spellings.toneSpellings !== void 0) {
3516
+ transposed.toneSpellings = spellings.toneSpellings;
2935
3517
  }
2936
3518
  return formatChordSymbol(transposed, opts);
2937
3519
  }
2938
3520
 
3521
+ // src/theory/symbol/coerce.ts
3522
+ function normalizedChord(data) {
3523
+ if (!Array.isArray(data.intervals)) {
3524
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)("chord.intervals must be an array of semitone offsets");
3525
+ }
3526
+ const copy = {
3527
+ rootPc: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, data.rootPc, "chord.rootPc")),
3528
+ quality: data.quality,
3529
+ // Each offset is checked rather than copied blind: a chord holding a NaN
3530
+ // interval voices, spells and formats as a chord that looks real, and the
3531
+ // failure surfaces wherever the number is finally used.
3532
+ intervals: data.intervals.map(
3533
+ (interval, index) => _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, interval, `chord.intervals[${index}]`)
3534
+ )
3535
+ };
3536
+ if (data.bassPc !== void 0) {
3537
+ copy.bassPc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, data.bassPc, "chord.bassPc"));
3538
+ }
3539
+ if (data.rootSpelling !== void 0) {
3540
+ copy.rootSpelling = data.rootSpelling;
3541
+ }
3542
+ if (data.bassSpelling !== void 0) {
3543
+ copy.bassSpelling = data.bassSpelling;
3544
+ }
3545
+ if (data.toneSpellings !== void 0) {
3546
+ copy.toneSpellings = data.toneSpellings;
3547
+ }
3548
+ return copy;
3549
+ }
3550
+ function toChordData(value) {
3551
+ if (typeof value === "string") {
3552
+ return parseChordSymbol(value);
3553
+ }
3554
+ if (typeof value === "object" && value !== null) {
3555
+ const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
3556
+ return normalizedChord(data);
3557
+ }
3558
+ throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
3559
+ `chord must be a chord symbol or chord data; received ${typeof value}`
3560
+ );
3561
+ }
3562
+
3563
+ // src/theory/tendency/index.ts
3564
+ function hasInterval(chord, semitones) {
3565
+ return chord.intervals.some((interval) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, interval) === semitones);
3566
+ }
3567
+ function isDiatonicChord(chord, key) {
3568
+ return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
3569
+ }
3570
+ function hasMajorThird(chord) {
3571
+ return hasInterval(chord, 4);
3572
+ }
3573
+ function soundsDominantSeventh(chord) {
3574
+ return hasMajorThird(chord) && hasInterval(chord, 10);
3575
+ }
3576
+ function soundsMajorTriad(chord) {
3577
+ return hasMajorThird(chord) && !hasInterval(chord, 10) && !hasInterval(chord, 11);
3578
+ }
3579
+ function hasDominantSonority(chord) {
3580
+ return soundsMajorTriad(chord) || soundsDominantSeventh(chord);
3581
+ }
3582
+ function isDominantChordOf(chord, key) {
3583
+ if (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) !== 7) {
3584
+ return false;
3585
+ }
3586
+ if (hasMajorThird(chord)) {
3587
+ return true;
3588
+ }
3589
+ const suspended = !hasInterval(chord, 3) && (hasInterval(chord, 5) || hasInterval(chord, 2));
3590
+ return suspended && hasInterval(chord, 10);
3591
+ }
3592
+ function isNeapolitanChordOf(chord, key) {
3593
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 1 && soundsMajorTriad(chord) && !isDiatonicChord(chord, key);
3594
+ }
3595
+ var DOMINANT_DEGREE = 5;
3596
+ function heptatonicFrameOf(key) {
3597
+ return scaleTonesInDegreeOrder(key).length === 7 ? key : majorKey(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc));
3598
+ }
3599
+ function degreeTriad(tones, index) {
3600
+ const root = _nullishCoalesce(tones[index], () => ( 0));
3601
+ return {
3602
+ third: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[(index + 2) % tones.length], () => ( 0))) - root),
3603
+ fifth: _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, (_nullishCoalesce(tones[(index + 4) % tones.length], () => ( 0))) - root)
3604
+ };
3605
+ }
3606
+ function tonicizableDegrees(key) {
3607
+ const tones = scaleTonesInDegreeOrder(heptatonicFrameOf(key));
3608
+ const minor = isMinorMask(key.modeMask12);
3609
+ const out = [];
3610
+ for (let index = 0; index < tones.length; index += 1) {
3611
+ const rootPc = tones[index];
3612
+ if (rootPc === void 0) {
3613
+ continue;
3614
+ }
3615
+ const triad = degreeTriad(tones, index);
3616
+ if (triad.fifth !== 7) {
3617
+ continue;
3618
+ }
3619
+ const dominantDegree = minor && index + 1 === DOMINANT_DEGREE;
3620
+ out.push({ degreeNumber: index + 1, rootPc, lower: triad.third === 3 && !dominantDegree });
3621
+ }
3622
+ return out;
3623
+ }
3624
+ function appliedDominantTarget(rootPc, key) {
3625
+ const target = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, rootPc);
3626
+ return _nullishCoalesce(tonicizableDegrees(key).find(
3627
+ (degree) => degree.degreeNumber !== 1 && degree.rootPc === target
3628
+ ), () => ( null));
3629
+ }
3630
+ function leadingTonePcOf(key) {
3631
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc - 1);
3632
+ }
3633
+ var LEADING_TONE_QUALITIES = /* @__PURE__ */ new Set(["dim", "dim7", "m7b5"]);
3634
+ function soundsDiminishedTriad(chord) {
3635
+ return hasInterval(chord, 3) && hasInterval(chord, 6) && !hasInterval(chord, 7);
3636
+ }
3637
+ function isLeadingToneChordOf(chord, key) {
3638
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc) === leadingTonePcOf(key) && soundsDiminishedTriad(chord);
3639
+ }
3640
+ function isFunctioningLeadingTone(chord, key) {
3641
+ const role = chordToneRole(leadingTonePcOf(key), chord);
3642
+ if (role === "third") {
3643
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc + 7);
3644
+ }
3645
+ if (role === "root") {
3646
+ return soundsDiminishedTriad(chord);
3647
+ }
3648
+ return false;
3649
+ }
3650
+ function seventhPcOf(chord) {
3651
+ for (const interval of chord.intervals) {
3652
+ const pc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval);
3653
+ if (chordToneRole(pc, chord) === "seventh") {
3654
+ return pc;
3655
+ }
3656
+ }
3657
+ return void 0;
3658
+ }
3659
+ function fifthPcOf(chord) {
3660
+ for (const interval of chord.intervals) {
3661
+ const pc = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc + interval);
3662
+ if (chordToneRole(pc, chord) === "fifth") {
3663
+ return pc;
3664
+ }
3665
+ }
3666
+ return void 0;
3667
+ }
3668
+ function isDescendingStep(interval) {
3669
+ return interval.number === 2 && (interval.semitones === -1 || interval.semitones === -2);
3670
+ }
3671
+ var THIRD_SEMITONES = [3, 4];
3672
+ function isFrustratedLeadingTone(fromPitch, toPitch, nextChord) {
3673
+ if (!THIRD_SEMITONES.includes(fromPitch - toPitch)) {
3674
+ return false;
3675
+ }
3676
+ const fifthPc = fifthPcOf(nextChord);
3677
+ return fifthPc !== void 0 && _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, toPitch) === fifthPc;
3678
+ }
3679
+
3680
+
3681
+
3682
+
3683
+
3684
+
3685
+
3686
+
3687
+
3688
+
3689
+
3690
+
3691
+
3692
+
3693
+
3694
+
3695
+
3696
+
3697
+
3698
+
3699
+
3700
+
3701
+
3702
+
3703
+
3704
+
3705
+
3706
+
3707
+
3708
+
3709
+
3710
+
3711
+
2939
3712
 
2940
3713
 
2941
3714
 
@@ -3049,5 +3822,5 @@ function transposeChordSymbol(text, semitones, opts) {
3049
3822
 
3050
3823
 
3051
3824
 
3052
- exports.maskFromOffsets = maskFromOffsets; exports.MAJOR_MASK = MAJOR_MASK; exports.NATURAL_MINOR_MASK = NATURAL_MINOR_MASK; exports.HARMONIC_MINOR_MASK = HARMONIC_MINOR_MASK; exports.MELODIC_MINOR_MASK = MELODIC_MINOR_MASK; exports.DORIAN_MASK = DORIAN_MASK; exports.PHRYGIAN_MASK = PHRYGIAN_MASK; exports.LYDIAN_MASK = LYDIAN_MASK; exports.MIXOLYDIAN_MASK = MIXOLYDIAN_MASK; exports.LOCRIAN_MASK = LOCRIAN_MASK; exports.MAJOR_PENTATONIC_MASK = MAJOR_PENTATONIC_MASK; exports.MINOR_PENTATONIC_MASK = MINOR_PENTATONIC_MASK; exports.BLUES_MASK = BLUES_MASK; exports.WHOLE_TONE_MASK = WHOLE_TONE_MASK; exports.OCTATONIC_HALF_WHOLE_MASK = OCTATONIC_HALF_WHOLE_MASK; exports.OCTATONIC_WHOLE_HALF_MASK = OCTATONIC_WHOLE_HALF_MASK; exports.CHROMATIC_MASK = CHROMATIC_MASK; exports.LYDIAN_DOMINANT_MASK = LYDIAN_DOMINANT_MASK; exports.MIXOLYDIAN_B13_MASK = MIXOLYDIAN_B13_MASK; exports.LOCRIAN_NATURAL2_MASK = LOCRIAN_NATURAL2_MASK; exports.ALTERED_MASK = ALTERED_MASK; exports.PHRYGIAN_DOMINANT_MASK = PHRYGIAN_DOMINANT_MASK; exports.MIYAKO_BUSHI_MASK = MIYAKO_BUSHI_MASK; exports.RITSU_MASK = RITSU_MASK; exports.RYUKYU_MASK = RYUKYU_MASK; exports.DOUBLE_HARMONIC_MASK = DOUBLE_HARMONIC_MASK; exports.TODI_MASK = TODI_MASK; exports.MARWA_MASK = MARWA_MASK; exports.PURVI_MASK = PURVI_MASK; exports.NAMED_SCALES = NAMED_SCALES; exports.WORLD_SCALES = WORLD_SCALES; exports.SCALE_ALIASES = SCALE_ALIASES; exports.resolveScaleName = resolveScaleName; exports.namedScaleMask = namedScaleMask; exports.requireScaleMask = requireScaleMask; exports.majorKey = majorKey; exports.minorKey = minorKey; exports.scaleByName = scaleByName; exports.toKeyScale = toKeyScale; exports.isScaleTone = isScaleTone; exports.nearestScaleTone = nearestScaleTone; exports.scaleLadderPosition = scaleLadderPosition; exports.scaleLadderPitch = scaleLadderPitch; exports.shiftByScaleDegrees = shiftByScaleDegrees; exports.pitchToScaleDegree = pitchToScaleDegree; exports.diatonicPitchClasses = diatonicPitchClasses; exports.scaleTonesInDegreeOrder = scaleTonesInDegreeOrder; exports.assertTonicOf = assertTonicOf; exports.spellPitchClass = spellPitchClass; exports.spellScale = spellScale; exports.spellPitchClasses = spellPitchClasses; exports.spellChordFromRoot = spellChordFromRoot; exports.spellChord = spellChord; exports.spellPitch = spellPitch; exports.noteNames = noteNames; exports.isSignatureKey = isSignatureKey; exports.keySignatureFifths = keySignatureFifths; exports.keyFromFifths = keyFromFifths; exports.spelledKeyOf = spelledKeyOf; exports.relativeKeyOf = relativeKeyOf; exports.parallelKeyOf = parallelKeyOf; exports.dominantKeyOf = dominantKeyOf; exports.subdominantKeyOf = subdominantKeyOf; exports.enharmonicKeyOf = enharmonicKeyOf; exports.relatedKeysOf = relatedKeysOf; exports.keyRelationBetween = keyRelationBetween; exports.SCALE_SYSTEMS = SCALE_SYSTEMS; exports.scaleSystemOf = scaleSystemOf; exports.supportsFunctionalHarmony = supportsFunctionalHarmony; exports.chordSpecIntervals = chordSpecIntervals; exports.chordSpecQuality = chordSpecQuality; exports.chordQualities = chordQualities; exports.chordFromDegree = chordFromDegree; exports.makeChord = makeChord; exports.chordFromSpec = chordFromSpec; exports.chordSpecOf = chordSpecOf; exports.chordFromSpan = chordFromSpan; exports.spanFromChord = spanFromChord; exports.transposeChord = transposeChord; exports.chordToneSpellings = chordToneSpellings; exports.chordPitchClasses = chordPitchClasses; exports.intervalAboveRoot = intervalAboveRoot; exports.isChordMember = isChordMember; exports.chordToneRole = chordToneRole; exports.diatonicTriad = diatonicTriad; exports.diatonicSeventh = diatonicSeventh; exports.simpleIntervalNumber = simpleIntervalNumber; exports.classifySpelledInterval = classifySpelledInterval; exports.voiceIndependence = voiceIndependence; exports.createsVoiceCrossing = createsVoiceCrossing; exports.createsVerticalDissonance = createsVerticalDissonance; exports.isForbiddenMelodicLeap = isForbiddenMelodicLeap; exports.isAugmentedMelodicInterval = isAugmentedMelodicInterval; exports.createsParallelPerfect = createsParallelPerfect; exports.createsParallelOctave = createsParallelOctave; exports.createsParallelUnison = createsParallelUnison; exports.createsHiddenParallelPerfect = createsHiddenParallelPerfect; exports.createsBattuta = createsBattuta; exports.createsVoiceOverlap = createsVoiceOverlap; exports.exceedsSpacing = exceedsSpacing; exports.isLeadingToneResolution = isLeadingToneResolution; exports.PROFILE_WEIGHTS = PROFILE_WEIGHTS; exports.profileWeights = profileWeights; exports.NoteSafety = NoteSafety; exports.ReasonFlag = ReasonFlag; exports.evaluateSafety = evaluateSafety; exports.enumerateSafePitches = enumerateSafePitches; exports.parseChordSymbol = parseChordSymbol; exports.tryParseChordSymbol = tryParseChordSymbol; exports.toChordData = toChordData; exports.formatChordSymbol = formatChordSymbol; exports.transposeChordSymbol = transposeChordSymbol;
3053
- //# sourceMappingURL=chunk-UADYAYAT.cjs.map
3825
+ exports.maskFromOffsets = maskFromOffsets; exports.MAJOR_MASK = MAJOR_MASK; exports.NATURAL_MINOR_MASK = NATURAL_MINOR_MASK; exports.HARMONIC_MINOR_MASK = HARMONIC_MINOR_MASK; exports.MELODIC_MINOR_MASK = MELODIC_MINOR_MASK; exports.DORIAN_MASK = DORIAN_MASK; exports.PHRYGIAN_MASK = PHRYGIAN_MASK; exports.LYDIAN_MASK = LYDIAN_MASK; exports.MIXOLYDIAN_MASK = MIXOLYDIAN_MASK; exports.LOCRIAN_MASK = LOCRIAN_MASK; exports.MAJOR_PENTATONIC_MASK = MAJOR_PENTATONIC_MASK; exports.MINOR_PENTATONIC_MASK = MINOR_PENTATONIC_MASK; exports.BLUES_MASK = BLUES_MASK; exports.WHOLE_TONE_MASK = WHOLE_TONE_MASK; exports.OCTATONIC_HALF_WHOLE_MASK = OCTATONIC_HALF_WHOLE_MASK; exports.OCTATONIC_WHOLE_HALF_MASK = OCTATONIC_WHOLE_HALF_MASK; exports.CHROMATIC_MASK = CHROMATIC_MASK; exports.assertModeMask = assertModeMask; exports.LYDIAN_DOMINANT_MASK = LYDIAN_DOMINANT_MASK; exports.MIXOLYDIAN_B13_MASK = MIXOLYDIAN_B13_MASK; exports.LOCRIAN_NATURAL2_MASK = LOCRIAN_NATURAL2_MASK; exports.ALTERED_MASK = ALTERED_MASK; exports.PHRYGIAN_DOMINANT_MASK = PHRYGIAN_DOMINANT_MASK; exports.MIYAKO_BUSHI_MASK = MIYAKO_BUSHI_MASK; exports.RITSU_MASK = RITSU_MASK; exports.RYUKYU_MASK = RYUKYU_MASK; exports.DOUBLE_HARMONIC_MASK = DOUBLE_HARMONIC_MASK; exports.TODI_MASK = TODI_MASK; exports.MARWA_MASK = MARWA_MASK; exports.PURVI_MASK = PURVI_MASK; exports.NAMED_SCALES = NAMED_SCALES; exports.WORLD_SCALES = WORLD_SCALES; exports.SCALE_ALIASES = SCALE_ALIASES; exports.resolveScaleName = resolveScaleName; exports.namedScaleMask = namedScaleMask; exports.requireScaleMask = requireScaleMask; exports.isMinorMask = isMinorMask; exports.variantOfMask = variantOfMask; exports.assertKeyVariant = assertKeyVariant; exports.scaleNameOfMask = scaleNameOfMask; exports.tryResolveKeyName = tryResolveKeyName; exports.toKeyScale = toKeyScale; exports.isScaleTone = isScaleTone; exports.nearestScaleTone = nearestScaleTone; exports.scaleLadderPosition = scaleLadderPosition; exports.scaleLadderPitch = scaleLadderPitch; exports.shiftByScaleDegrees = shiftByScaleDegrees; exports.pitchToScaleDegree = pitchToScaleDegree; exports.diatonicPitchClasses = diatonicPitchClasses; exports.scaleTonesInDegreeOrder = scaleTonesInDegreeOrder; exports.assertTonicOf = assertTonicOf; exports.spellPitchClass = spellPitchClass; exports.spellScale = spellScale; exports.spellPitchClasses = spellPitchClasses; exports.spellChordFromRoot = spellChordFromRoot; exports.spellChord = spellChord; exports.spellPitch = spellPitch; exports.noteNames = noteNames; exports.isSignatureKey = isSignatureKey; exports.keySignatureFifths = keySignatureFifths; exports.keyFromFifths = keyFromFifths; exports.spelledKeyOf = spelledKeyOf; exports.relativeKeyOf = relativeKeyOf; exports.parallelKeyOf = parallelKeyOf; exports.isWrittenTonic = isWrittenTonic; exports.dominantKeyOf = dominantKeyOf; exports.subdominantKeyOf = subdominantKeyOf; exports.enharmonicKeyOf = enharmonicKeyOf; exports.relatedKeysOf = relatedKeysOf; exports.keyRelationBetween = keyRelationBetween; exports.resolveKey = resolveKey; exports.scaleOf = scaleOf; exports.majorKey = majorKey; exports.minorKey = minorKey; exports.scaleByName = scaleByName; exports.SCALE_SYSTEMS = SCALE_SYSTEMS; exports.scaleSystemOf = scaleSystemOf; exports.supportsFunctionalHarmony = supportsFunctionalHarmony; exports.chordSpecIntervals = chordSpecIntervals; exports.chordSpecQuality = chordSpecQuality; exports.chordQualities = chordQualities; exports.assertChordQuality = assertChordQuality; exports.assertChord = assertChord; exports.chordFromDegree = chordFromDegree; exports.makeChord = makeChord; exports.chordFromSpec = chordFromSpec; exports.chordSpecOf = chordSpecOf; exports.chordFromSpan = chordFromSpan; exports.spanFromChord = spanFromChord; exports.transposeChord = transposeChord; exports.chordToneSpellings = chordToneSpellings; exports.chordPitchClasses = chordPitchClasses; exports.intervalAboveRoot = intervalAboveRoot; exports.isChordMember = isChordMember; exports.chordToneRole = chordToneRole; exports.degreeOfInterval = degreeOfInterval; exports.thirdSlotOf = thirdSlotOf; exports.displacedThirds = displacedThirds; exports.diatonicTriad = diatonicTriad; exports.diatonicSeventh = diatonicSeventh; exports.simpleIntervalNumber = simpleIntervalNumber; exports.classifySpelledInterval = classifySpelledInterval; exports.voiceIndependence = voiceIndependence; exports.createsVoiceCrossing = createsVoiceCrossing; exports.createsVerticalDissonance = createsVerticalDissonance; exports.isForbiddenMelodicLeap = isForbiddenMelodicLeap; exports.isAugmentedMelodicInterval = isAugmentedMelodicInterval; exports.createsParallelPerfect = createsParallelPerfect; exports.createsParallelOctave = createsParallelOctave; exports.createsParallelUnison = createsParallelUnison; exports.createsHiddenParallelPerfect = createsHiddenParallelPerfect; exports.createsBattuta = createsBattuta; exports.createsVoiceOverlap = createsVoiceOverlap; exports.exceedsSpacing = exceedsSpacing; exports.isLeadingToneResolution = isLeadingToneResolution; exports.PROFILE_WEIGHTS = PROFILE_WEIGHTS; exports.profileWeights = profileWeights; exports.NoteSafety = NoteSafety; exports.ReasonFlag = ReasonFlag; exports.evaluateSafety = evaluateSafety; exports.enumerateSafePitches = enumerateSafePitches; exports.toChordData = toChordData; exports.parseChordSymbol = parseChordSymbol; exports.tryParseChordSymbol = tryParseChordSymbol; exports.formatChordSymbol = formatChordSymbol; exports.transposeChordSpellings = transposeChordSpellings; exports.transposeChordSymbol = transposeChordSymbol; exports.isDiatonicChord = isDiatonicChord; exports.hasMajorThird = hasMajorThird; exports.soundsDominantSeventh = soundsDominantSeventh; exports.hasDominantSonority = hasDominantSonority; exports.isDominantChordOf = isDominantChordOf; exports.isNeapolitanChordOf = isNeapolitanChordOf; exports.heptatonicFrameOf = heptatonicFrameOf; exports.tonicizableDegrees = tonicizableDegrees; exports.appliedDominantTarget = appliedDominantTarget; exports.leadingTonePcOf = leadingTonePcOf; exports.LEADING_TONE_QUALITIES = LEADING_TONE_QUALITIES; exports.isLeadingToneChordOf = isLeadingToneChordOf; exports.isFunctioningLeadingTone = isFunctioningLeadingTone; exports.seventhPcOf = seventhPcOf; exports.fifthPcOf = fifthPcOf; exports.isDescendingStep = isDescendingStep; exports.isFrustratedLeadingTone = isFrustratedLeadingTone;
3826
+ //# sourceMappingURL=chunk-3R3QVKY5.cjs.map