@libraz/libcantus 1.3.0 → 1.4.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 (130) hide show
  1. package/NOTICE +63 -0
  2. package/README.md +3 -1
  3. package/README_ja.md +13 -11
  4. package/dist/analyze/index.cjs +7 -7
  5. package/dist/analyze/index.cjs.map +1 -1
  6. package/dist/analyze/index.d.cts +14 -12
  7. package/dist/analyze/index.d.ts +14 -12
  8. package/dist/analyze/index.js +6 -6
  9. package/dist/{chunk-NB25P3EH.js → chunk-2G5MCGYU.js} +33 -11
  10. package/dist/chunk-2G5MCGYU.js.map +1 -0
  11. package/dist/{chunk-NM7UAMC3.cjs → chunk-3FDBMIPR.cjs} +64 -64
  12. package/dist/chunk-3FDBMIPR.cjs.map +1 -0
  13. package/dist/{chunk-YFA7X2F6.cjs → chunk-3MDOEFIQ.cjs} +411 -327
  14. package/dist/chunk-3MDOEFIQ.cjs.map +1 -0
  15. package/dist/{chunk-IPEF6NWB.js → chunk-5MWRBYAT.js} +278 -154
  16. package/dist/chunk-5MWRBYAT.js.map +1 -0
  17. package/dist/{chunk-B2PW6EHN.cjs → chunk-6TTBEON3.cjs} +1253 -889
  18. package/dist/chunk-6TTBEON3.cjs.map +1 -0
  19. package/dist/{chunk-UMWZ4ZET.js → chunk-7NQ6AL3H.js} +4 -4
  20. package/dist/chunk-7NQ6AL3H.js.map +1 -0
  21. package/dist/{chunk-PWC4CRWT.cjs → chunk-E3QO3UET.cjs} +31 -23
  22. package/dist/chunk-E3QO3UET.cjs.map +1 -0
  23. package/dist/{chunk-GNVN4PPJ.js → chunk-FJFULJZE.js} +531 -213
  24. package/dist/chunk-FJFULJZE.js.map +1 -0
  25. package/dist/{chunk-CPMB7P75.js → chunk-FK3DGAIZ.js} +608 -244
  26. package/dist/chunk-FK3DGAIZ.js.map +1 -0
  27. package/dist/chunk-GEDTJHAC.cjs.map +1 -1
  28. package/dist/{chunk-RFIBF2RR.js → chunk-H7FVMBUC.js} +138 -65
  29. package/dist/chunk-H7FVMBUC.js.map +1 -0
  30. package/dist/{chunk-4R2LWV7E.cjs → chunk-HYSVSFNI.cjs} +664 -540
  31. package/dist/chunk-HYSVSFNI.cjs.map +1 -0
  32. package/dist/{chunk-35NBV2FT.cjs → chunk-KMYGRFZ6.cjs} +47 -24
  33. package/dist/chunk-KMYGRFZ6.cjs.map +1 -0
  34. package/dist/{chunk-IFZDWIKM.cjs → chunk-LQZQDBCD.cjs} +1048 -730
  35. package/dist/chunk-LQZQDBCD.cjs.map +1 -0
  36. package/dist/{chunk-PM4XJE2Y.cjs → chunk-P3CPXAJ5.cjs} +76 -54
  37. package/dist/chunk-P3CPXAJ5.cjs.map +1 -0
  38. package/dist/{chunk-S5BKZZGM.cjs → chunk-S3QELJUE.cjs} +72 -56
  39. package/dist/chunk-S3QELJUE.cjs.map +1 -0
  40. package/dist/{chunk-PUKJP2Z5.js → chunk-TXFWSU5D.js} +31 -8
  41. package/dist/chunk-TXFWSU5D.js.map +1 -0
  42. package/dist/{chunk-76ZBTRG2.cjs → chunk-U4EQTDGN.cjs} +258 -218
  43. package/dist/chunk-U4EQTDGN.cjs.map +1 -0
  44. package/dist/chunk-UGLFDG7L.cjs.map +1 -1
  45. package/dist/{chunk-MLBPXA4Z.js → chunk-UTE5A36R.js} +198 -114
  46. package/dist/chunk-UTE5A36R.js.map +1 -0
  47. package/dist/{chunk-ZBUROTJQ.js → chunk-UZKQISRF.js} +17 -9
  48. package/dist/chunk-UZKQISRF.js.map +1 -0
  49. package/dist/{chunk-7KG3RTYS.js → chunk-VL5NXVCN.js} +281 -155
  50. package/dist/chunk-VL5NXVCN.js.map +1 -0
  51. package/dist/{chunk-CGBADIO5.cjs → chunk-WOMCXN2W.cjs} +328 -202
  52. package/dist/chunk-WOMCXN2W.cjs.map +1 -0
  53. package/dist/{chunk-3R3QVKY5.cjs → chunk-YDGMWH43.cjs} +397 -324
  54. package/dist/chunk-YDGMWH43.cjs.map +1 -0
  55. package/dist/{chunk-AGOVW7U6.js → chunk-YYVXRV3V.js} +72 -56
  56. package/dist/chunk-YYVXRV3V.js.map +1 -0
  57. package/dist/{chunk-M5RYMAFI.js → chunk-ZLNSH4UL.js} +71 -31
  58. package/dist/chunk-ZLNSH4UL.js.map +1 -0
  59. package/dist/{coerce-DRERzdve.d.cts → coerce-CUhHc3Tu.d.ts} +16 -8
  60. package/dist/{coerce-DP0vYu-x.d.ts → coerce-Ytt2Eiq9.d.cts} +16 -8
  61. package/dist/core/index.cjs +5 -5
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +9 -8
  64. package/dist/core/index.d.ts +9 -8
  65. package/dist/core/index.js +4 -4
  66. package/dist/generate/index.cjs +8 -8
  67. package/dist/generate/index.cjs.map +1 -1
  68. package/dist/generate/index.d.cts +28 -18
  69. package/dist/generate/index.d.ts +28 -18
  70. package/dist/generate/index.js +7 -7
  71. package/dist/{index-BMweUlm5.d.cts → index-5VJgKz_Q.d.cts} +6 -2
  72. package/dist/{index-BHIUyjhZ.d.ts → index-BLuDesVG.d.ts} +20 -13
  73. package/dist/{index-B9QLziDe.d.cts → index-BV6APVa6.d.cts} +20 -13
  74. package/dist/{index-CL4b4gUQ.d.ts → index-Bf0Sv8a9.d.ts} +6 -2
  75. package/dist/{index-Dw2DP7vm.d.ts → index-BkT21EMv.d.cts} +3 -2
  76. package/dist/{index-BL5N2Oyz.d.cts → index-C7KCaTRz.d.ts} +3 -2
  77. package/dist/{index-Cy15oTvh.d.ts → index-C9MYbuKS.d.cts} +7 -3
  78. package/dist/{index-Cy15oTvh.d.cts → index-C9MYbuKS.d.ts} +7 -3
  79. package/dist/{index-CY-3NVfy.d.cts → index-DVInzCDS.d.cts} +53 -29
  80. package/dist/{index-9eoQs2C1.d.ts → index-Dg-SAfhz.d.ts} +6 -4
  81. package/dist/{index-zaZdmnU4.d.ts → index-jgeug9EA.d.ts} +53 -29
  82. package/dist/{index-cHvbYgJI.d.cts → index-pQihL4z7.d.cts} +6 -4
  83. package/dist/index.cjs +13 -13
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.d.cts +11 -11
  86. package/dist/index.d.ts +11 -11
  87. package/dist/index.js +12 -12
  88. package/dist/{masks-Cc7-MzzY.d.ts → masks-BnzXnBj0.d.cts} +3 -2
  89. package/dist/{masks-pADfTOiD.d.cts → masks-tycmkrgs.d.ts} +3 -2
  90. package/dist/model/index.cjs +12 -12
  91. package/dist/model/index.cjs.map +1 -1
  92. package/dist/model/index.d.cts +63 -48
  93. package/dist/model/index.d.ts +63 -48
  94. package/dist/model/index.js +11 -11
  95. package/dist/{positional-DJgs6ltD.d.ts → positional-CscpxtlE.d.cts} +3 -2
  96. package/dist/{positional-DMZer-FM.d.cts → positional-DLnfUgqG.d.ts} +3 -2
  97. package/dist/{profile-DLdlIpS9.d.ts → profile-B4WqcM4H.d.ts} +25 -17
  98. package/dist/{profile-CQMEqdH9.d.cts → profile-BJd1XkPB.d.cts} +25 -17
  99. package/dist/theory/index.cjs +5 -5
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +18 -14
  102. package/dist/theory/index.d.ts +18 -14
  103. package/dist/theory/index.js +4 -4
  104. package/dist/{types-D-XgxGvp.d.cts → types-7mGFwvMm.d.ts} +45 -22
  105. package/dist/{types-Dh7C064C.d.ts → types-_ofpb2be.d.cts} +45 -22
  106. package/package.json +13 -13
  107. package/dist/chunk-35NBV2FT.cjs.map +0 -1
  108. package/dist/chunk-3R3QVKY5.cjs.map +0 -1
  109. package/dist/chunk-4R2LWV7E.cjs.map +0 -1
  110. package/dist/chunk-76ZBTRG2.cjs.map +0 -1
  111. package/dist/chunk-7KG3RTYS.js.map +0 -1
  112. package/dist/chunk-AGOVW7U6.js.map +0 -1
  113. package/dist/chunk-B2PW6EHN.cjs.map +0 -1
  114. package/dist/chunk-CGBADIO5.cjs.map +0 -1
  115. package/dist/chunk-CPMB7P75.js.map +0 -1
  116. package/dist/chunk-GNVN4PPJ.js.map +0 -1
  117. package/dist/chunk-IFZDWIKM.cjs.map +0 -1
  118. package/dist/chunk-IPEF6NWB.js.map +0 -1
  119. package/dist/chunk-M5RYMAFI.js.map +0 -1
  120. package/dist/chunk-MLBPXA4Z.js.map +0 -1
  121. package/dist/chunk-NB25P3EH.js.map +0 -1
  122. package/dist/chunk-NM7UAMC3.cjs.map +0 -1
  123. package/dist/chunk-PM4XJE2Y.cjs.map +0 -1
  124. package/dist/chunk-PUKJP2Z5.js.map +0 -1
  125. package/dist/chunk-PWC4CRWT.cjs.map +0 -1
  126. package/dist/chunk-RFIBF2RR.js.map +0 -1
  127. package/dist/chunk-S5BKZZGM.cjs.map +0 -1
  128. package/dist/chunk-UMWZ4ZET.js.map +0 -1
  129. package/dist/chunk-YFA7X2F6.cjs.map +0 -1
  130. package/dist/chunk-ZBUROTJQ.js.map +0 -1
@@ -2,15 +2,21 @@ import {
2
2
  analyzeArrangement,
3
3
  analyzePolyphony,
4
4
  analyzeReference,
5
+ arrangementProfile,
6
+ assertTrackNotes,
7
+ boundaryCadences,
5
8
  createArrangementSession,
9
+ harmonyTrackSet,
6
10
  hypermeter,
11
+ hypermeterOver,
12
+ keyContextFor,
7
13
  phrasesFromTimeline,
8
14
  referenceFromReadings,
9
15
  sectionsFromNotes,
10
16
  structuralCadences,
11
17
  tensionCurveFrom,
12
18
  trackRoleOf
13
- } from "./chunk-MLBPXA4Z.js";
19
+ } from "./chunk-UTE5A36R.js";
14
20
  import {
15
21
  TWELVE_TET,
16
22
  beatsToDuration,
@@ -34,15 +40,15 @@ import {
34
40
  tempoAt,
35
41
  tempoReader,
36
42
  ticksToBeats
37
- } from "./chunk-UMWZ4ZET.js";
43
+ } from "./chunk-7NQ6AL3H.js";
38
44
  import {
39
45
  STEP_BEATS,
40
46
  applyGrooveTemplate,
41
47
  cellSpan,
42
- deform,
48
+ deformFigure,
43
49
  deriveCompositionPlan,
44
50
  developMotif,
45
- doubleTime,
51
+ doubleTimeFigure,
46
52
  evaluateComposition,
47
53
  extractGrooveTemplate,
48
54
  generateBassLine,
@@ -52,7 +58,7 @@ import {
52
58
  generateMotif,
53
59
  generateProgression,
54
60
  generateRhythm,
55
- halfTime,
61
+ halfTimeFigure,
56
62
  harmonizeMelody,
57
63
  humanize,
58
64
  modalInterchangePalette,
@@ -61,6 +67,7 @@ import {
61
67
  ornament,
62
68
  ornamentBy,
63
69
  resolveContext,
70
+ resolveDials,
64
71
  rhythmDensity,
65
72
  rhythmToNoteEvents,
66
73
  substituteChord,
@@ -68,10 +75,11 @@ import {
68
75
  thin,
69
76
  transformMotif,
70
77
  withinCeiling
71
- } from "./chunk-CPMB7P75.js";
78
+ } from "./chunk-FK3DGAIZ.js";
72
79
  import {
73
80
  analyzeChord,
74
81
  analyzeRhythm,
82
+ assertSpansInOrder,
75
83
  augmentedSixthChord,
76
84
  borrowedSource,
77
85
  chordAtBeat,
@@ -103,8 +111,9 @@ import {
103
111
  relateMotifs,
104
112
  romanToChord,
105
113
  secondaryDominantOf,
114
+ statedKeyRegions,
106
115
  toVoiceNotes
107
- } from "./chunk-GNVN4PPJ.js";
116
+ } from "./chunk-FJFULJZE.js";
108
117
  import {
109
118
  BASS_4_STRING,
110
119
  BASS_5_STRING,
@@ -117,10 +126,10 @@ import {
117
126
  soundsIn,
118
127
  toInstrumentProfile,
119
128
  toStringedProfile
120
- } from "./chunk-7KG3RTYS.js";
129
+ } from "./chunk-VL5NXVCN.js";
121
130
  import {
122
131
  createNoteEventIndex
123
- } from "./chunk-PUKJP2Z5.js";
132
+ } from "./chunk-TXFWSU5D.js";
124
133
  import {
125
134
  SATB_RANGES,
126
135
  checkPartWriting,
@@ -135,14 +144,14 @@ import {
135
144
  voiceChordStyled,
136
145
  voiceLeadingCost,
137
146
  voiceProgression
138
- } from "./chunk-M5RYMAFI.js";
147
+ } from "./chunk-ZLNSH4UL.js";
139
148
  import {
140
149
  availableTensions,
141
150
  avoidNotes,
142
151
  chordScaleReport,
143
152
  chordScales,
144
153
  scalesForChanges
145
- } from "./chunk-NB25P3EH.js";
154
+ } from "./chunk-2G5MCGYU.js";
146
155
  import {
147
156
  NAMED_SCALES,
148
157
  assertChordQuality,
@@ -153,6 +162,7 @@ import {
153
162
  chordSpecOf,
154
163
  chordToneRole,
155
164
  chordToneSpellings,
165
+ classifySpelledInterval,
156
166
  diatonicPitchClasses,
157
167
  diatonicSeventh,
158
168
  diatonicTriad,
@@ -198,10 +208,11 @@ import {
198
208
  tryResolveKeyName,
199
209
  variantOfMask,
200
210
  voiceIndependence
201
- } from "./chunk-RFIBF2RR.js";
211
+ } from "./chunk-H7FVMBUC.js";
202
212
  import {
203
213
  InvalidInputError,
204
214
  MAX_NAME_ACCIDENTALS,
215
+ assertArray,
205
216
  assertFiniteNumber,
206
217
  assertInteger,
207
218
  assertMidiPitch,
@@ -209,9 +220,11 @@ import {
209
220
  assertOneOf,
210
221
  assertPositiveInt,
211
222
  assertRange,
223
+ assertRecord,
212
224
  assertTimeSignature,
213
225
  beatToBarPosition,
214
226
  beatsPerBar,
227
+ beatsPerBarAt,
215
228
  describeRejected,
216
229
  formatBarPosition,
217
230
  formatKeyName,
@@ -219,7 +232,6 @@ import {
219
232
  formatTimeSignature,
220
233
  intervalSemitones,
221
234
  isCompound,
222
- isConsonantInterval,
223
235
  isStrongBeat,
224
236
  meterAt,
225
237
  metricWeight,
@@ -242,7 +254,7 @@ import {
242
254
  tryParseTimeSignature,
243
255
  tuplet,
244
256
  unwrapParse
245
- } from "./chunk-AGOVW7U6.js";
257
+ } from "./chunk-YYVXRV3V.js";
246
258
 
247
259
  // src/model/interval.ts
248
260
  var INVERSION_SUM = 9;
@@ -251,6 +263,14 @@ function simpleDegree(numberValue) {
251
263
  const reduced = (numberValue - 1) % 7 + 1;
252
264
  return reduced === 1 && numberValue > 1 ? OCTAVE_DEGREES : reduced;
253
265
  }
266
+ function checkedDirection(descending) {
267
+ if (typeof descending !== "boolean") {
268
+ throw new InvalidInputError(
269
+ `interval.descending must be a boolean; received ${describeRejected(descending)}`
270
+ );
271
+ }
272
+ return descending;
273
+ }
254
274
  function invertQuality(quality) {
255
275
  if (quality === "P") {
256
276
  return "P";
@@ -282,6 +302,12 @@ var Interval = class _Interval {
282
302
  * @returns The interval from `a` to `b`.
283
303
  */
284
304
  static between(a, b) {
305
+ if (typeof a !== "object" || a === null || typeof a.intervalTo !== "function") {
306
+ throw new InvalidInputError(`interval a must be a Note; received ${describeRejected(a)}`);
307
+ }
308
+ if (typeof b !== "object" || b === null || typeof b.data !== "object" || b.data === null) {
309
+ throw new InvalidInputError(`interval b must be a Note; received ${describeRejected(b)}`);
310
+ }
285
311
  return a.intervalTo(b);
286
312
  }
287
313
  /**
@@ -312,7 +338,12 @@ var Interval = class _Interval {
312
338
  */
313
339
  static fromData(data) {
314
340
  const checked = toSpelledInterval(data);
315
- return new _Interval(checked.number, checked.quality, checked.semitones, checked.descending);
341
+ return new _Interval(
342
+ checked.number,
343
+ checked.quality,
344
+ checked.semitones,
345
+ checkedDirection(checked.descending)
346
+ );
316
347
  }
317
348
  /**
318
349
  * Rebuild an interval from its {@link Interval.toJSON} output.
@@ -437,12 +468,15 @@ var Interval = class _Interval {
437
468
  /**
438
469
  * Whether the interval is consonant.
439
470
  *
471
+ * The spelling decides, not the span: a diminished fourth is a dissonance
472
+ * though it sounds like a major third.
473
+ *
440
474
  * @param twoVoice When true, the perfect fourth counts as a dissonance,
441
475
  * matching two-voice counterpoint.
442
476
  * @returns True if the interval is consonant in that context.
443
477
  */
444
478
  isConsonant(twoVoice = true) {
445
- return isConsonantInterval(this.#semitones, twoVoice);
479
+ return classifySpelledInterval(this.toJSON(), twoVoice) !== 2 /* Dissonance */;
446
480
  }
447
481
  /**
448
482
  * Whether another interval is spelled identically.
@@ -489,7 +523,6 @@ var Interval = class _Interval {
489
523
  };
490
524
 
491
525
  // src/model/shared.ts
492
- var STATED_KEY_CONFIDENCE = 1;
493
526
  function describeShape(value) {
494
527
  if (value === null) {
495
528
  return "null";
@@ -1072,6 +1105,22 @@ var Note = class _Note {
1072
1105
  };
1073
1106
 
1074
1107
  // src/model/timeline.ts
1108
+ var KEY_RELATIONS = [
1109
+ "same",
1110
+ "enharmonic",
1111
+ "relative",
1112
+ "parallel",
1113
+ "dominant",
1114
+ "subdominant",
1115
+ "relativeOfDominant",
1116
+ "relativeOfSubdominant"
1117
+ ];
1118
+ function assertNumeral(numeral, name) {
1119
+ if (typeof numeral !== "string") {
1120
+ throw new InvalidInputError(`${name} must be a string; received ${describeRejected(numeral)}`);
1121
+ }
1122
+ return numeral;
1123
+ }
1075
1124
  function copySegment(segment) {
1076
1125
  assertDataObject(segment, "timeline segment");
1077
1126
  return {
@@ -1094,14 +1143,14 @@ function copyKeyRegion(region) {
1094
1143
  confidence: assertFiniteNumber(region.confidence, "key region confidence")
1095
1144
  };
1096
1145
  if (region.modulation !== void 0) {
1097
- copy.modulation = region.modulation;
1146
+ copy.modulation = assertOneOf(region.modulation, KEY_RELATIONS, "key region modulation");
1098
1147
  }
1099
1148
  if (region.pivot !== void 0) {
1100
1149
  assertDataObject(region.pivot, "key region pivot");
1101
1150
  copy.pivot = {
1102
1151
  chord: new Chord(region.pivot.chord).toJSON(),
1103
- romanFrom: region.pivot.romanFrom,
1104
- romanTo: region.pivot.romanTo
1152
+ romanFrom: assertNumeral(region.pivot.romanFrom, "key region pivot romanFrom"),
1153
+ romanTo: assertNumeral(region.pivot.romanTo, "key region pivot romanTo")
1105
1154
  };
1106
1155
  }
1107
1156
  return copy;
@@ -1120,6 +1169,10 @@ function copyConfidence(confidence, segments) {
1120
1169
  (value, index) => assertRange(value, 0, 1, `timeline segmentConfidence[${index}]`)
1121
1170
  );
1122
1171
  }
1172
+ function validateTimeline(segments, keys) {
1173
+ assertSpansInOrder(segments, "timeline segments");
1174
+ assertSpansInOrder(keys, "timeline keys");
1175
+ }
1123
1176
  function clipSpan(span, fromBeat, toBeat) {
1124
1177
  const startBeat = Math.max(span.startBeat, fromBeat);
1125
1178
  const endBeat = Math.min(span.endBeat, toBeat);
@@ -1135,8 +1188,9 @@ var Timeline = class _Timeline {
1135
1188
  *
1136
1189
  * @param data The segments and the span they cover; copied, never retained.
1137
1190
  * @throws If the span, a segment, or a key region carries a number it cannot
1138
- * hold — a beat that is not finite, a mode mask that names no scale — or if
1139
- * the confidences do not run one per segment.
1191
+ * hold — a beat that is not finite, a mode mask that names no scale — if
1192
+ * the segments or the key regions are not disjoint, in beat order and of
1193
+ * positive length, or if the confidences do not run one per segment.
1140
1194
  */
1141
1195
  constructor(data) {
1142
1196
  assertDataObject(data, "timeline data");
@@ -1155,6 +1209,7 @@ var Timeline = class _Timeline {
1155
1209
  this.#segmentConfidence = Object.freeze(
1156
1210
  copyConfidence(data.segmentConfidence, this.#segments.length)
1157
1211
  );
1212
+ validateTimeline(this.#segments, this.#keys);
1158
1213
  }
1159
1214
  /**
1160
1215
  * Build a timeline from placed chords.
@@ -1162,7 +1217,8 @@ var Timeline = class _Timeline {
1162
1217
  * @param spans The chords with their onsets, in any order.
1163
1218
  * @param totalBeats Where the last chord stops sounding.
1164
1219
  * @param key The key the chords are read in, when it is already known. It
1165
- * becomes the one key region under the whole span; without it the timeline
1220
+ * becomes the one key region from the first chord to the end of the span;
1221
+ * without it, or without a chord for it to be in force over, the timeline
1166
1222
  * carries no key, and the members that need one say so.
1167
1223
  * @returns The timeline.
1168
1224
  */
@@ -1170,16 +1226,9 @@ var Timeline = class _Timeline {
1170
1226
  const timeline = chordTimelineFromChords(spans, totalBeats);
1171
1227
  const data = { segments: timeline.segments, totalBeats };
1172
1228
  if (key !== void 0) {
1173
- data.keys = [
1174
- {
1175
- // The region starts where the music does, as an inferred one does,
1176
- // so a chord placed in a pickup is covered by the key it sounds in.
1177
- startBeat: timeline.segments[0]?.startBeat ?? 0,
1178
- endBeat: totalBeats,
1179
- key: keyIdentity(toKey(key)),
1180
- confidence: STATED_KEY_CONFIDENCE
1181
- }
1182
- ];
1229
+ const stated = keyIdentity(toKey(key));
1230
+ const first = timeline.segments[0];
1231
+ data.keys = first === void 0 ? [] : statedKeyRegions(stated, first.startBeat, totalBeats);
1183
1232
  }
1184
1233
  return new _Timeline(data);
1185
1234
  }
@@ -1210,11 +1259,25 @@ var Timeline = class _Timeline {
1210
1259
  * @throws If `beatsEach` is not a positive finite number of beats.
1211
1260
  */
1212
1261
  static fromProgression(progression, beatsEach) {
1262
+ const read = progression;
1263
+ if (typeof read !== "object" || read === null || !Array.isArray(read.chords)) {
1264
+ throw new InvalidInputError(
1265
+ `timeline progression must be a Progression; received ${describeRejected(progression)}`
1266
+ );
1267
+ }
1213
1268
  assertRange(beatsEach, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "timeline beatsEach");
1214
- const chords = progression.chords;
1215
- const spans = chords.map((chord, index) => spanFromChord(chord.data, index * beatsEach));
1269
+ const chords = read.chords;
1270
+ const spans = chords.map((chord, index) => {
1271
+ const data = chord?.data;
1272
+ if (typeof data !== "object" || data === null) {
1273
+ throw new InvalidInputError(
1274
+ `timeline progression chords[${index}] must be a Chord; received ${describeRejected(chord)}`
1275
+ );
1276
+ }
1277
+ return spanFromChord(data, index * beatsEach);
1278
+ });
1216
1279
  const totalBeats = chords.length * beatsEach;
1217
- const key = progression.key;
1280
+ const key = read.key;
1218
1281
  return key === void 0 ? _Timeline.fromChords(spans, totalBeats) : _Timeline.fromChords(spans, totalBeats, key);
1219
1282
  }
1220
1283
  /** Rebuild a timeline from the plain data {@link Timeline.data} hands out. */
@@ -1606,7 +1669,14 @@ var Progression = class _Progression {
1606
1669
  */
1607
1670
  static #attachKey(chords, key) {
1608
1671
  const given = assertDataObjects(chords, "progression chords");
1609
- return key === void 0 ? [...given] : given.map((chord) => chord.withKey(key));
1672
+ return given.map((chord, index) => {
1673
+ if (typeof chord.toJSON !== "function") {
1674
+ throw new InvalidInputError(
1675
+ `progression chords[${index}] must be a Chord; received ${describeRejected(chord)}`
1676
+ );
1677
+ }
1678
+ return new Chord(chord.toJSON(), key ?? chord.key);
1679
+ });
1610
1680
  }
1611
1681
  /**
1612
1682
  * Start from a chord sequence, matching the `of` factory on the other
@@ -1654,19 +1724,34 @@ var Progression = class _Progression {
1654
1724
  return new _Progression(chords, key);
1655
1725
  }
1656
1726
  /**
1657
- * Rebuild a progression from its {@link Progression.toJSON} output.
1727
+ * Rebuild a progression from its {@link Progression.toJSON} output, with the
1728
+ * carried key or, without one, each chord's own key.
1658
1729
  *
1659
- * @param data The serialized chords and key.
1730
+ * @param data The serialized chords and keys.
1660
1731
  * @returns The progression.
1732
+ * @throws If `chordKeys` does not hold one entry per chord.
1661
1733
  */
1662
1734
  static fromJSON(data) {
1663
1735
  assertDataObject(data, "progression data");
1664
1736
  const key = data.key === void 0 ? void 0 : Key.fromJSON(data.key);
1665
- const chords = assertDataArray(data.chords, "progression chords").map(
1666
- (chord) => Chord.fromJSON(chord)
1667
- );
1737
+ const given = assertDataArray(data.chords, "progression chords");
1738
+ const chordKeys = key === void 0 && data.chordKeys !== void 0 ? _Progression.#readChordKeys(data.chordKeys, given.length) : void 0;
1739
+ const chords = given.map((chord, index) => {
1740
+ const own = chordKeys?.[index];
1741
+ return own === void 0 ? Chord.fromJSON(chord) : Chord.fromJSON(chord).withKey(own);
1742
+ });
1668
1743
  return key === void 0 ? new _Progression(chords) : new _Progression(chords, key);
1669
1744
  }
1745
+ /** Read the per-chord keys of {@link ProgressionData}, one entry per chord. */
1746
+ static #readChordKeys(value, count) {
1747
+ const entries = assertDataArray(value, "progression chord keys");
1748
+ if (entries.length !== count) {
1749
+ throw new InvalidInputError(
1750
+ `progression chord keys must hold one entry per chord (${count}); received ${entries.length}`
1751
+ );
1752
+ }
1753
+ return entries.map((entry) => entry === null ? void 0 : Key.fromJSON(entry));
1754
+ }
1670
1755
  /**
1671
1756
  * Wrap plain progression data, matching the `fromData` factory on the other
1672
1757
  * classes.
@@ -1877,10 +1962,8 @@ var Progression = class _Progression {
1877
1962
  */
1878
1963
  voice(opts) {
1879
1964
  const key = opts?.key ?? this.#key;
1880
- return voiceProgression(
1881
- this.#chords.map((chord) => chord.data),
1882
- key === void 0 ? opts : { ...opts, key }
1883
- );
1965
+ const views = opts?.key === void 0 ? this.#chords.map((chord) => chord.data) : this.#viewsIn(toKey(opts.key));
1966
+ return voiceProgression(views, key === void 0 ? opts : { ...opts, key });
1884
1967
  }
1885
1968
  /**
1886
1969
  * The Roman numeral of each chord in a key.
@@ -1941,12 +2024,13 @@ var Progression = class _Progression {
1941
2024
  const resolved = this.#resolveKey(key);
1942
2025
  const chords = this.#chords.map((chord) => chord.analyze(resolved, opts));
1943
2026
  const { voicing, ...cadenceOpts } = opts ?? {};
1944
- const from = this.#chords[this.#chords.length - 2];
1945
- const to = this.#chords[this.#chords.length - 1];
1946
- const approach = this.#chords[this.#chords.length - 3];
1947
- const cadence = from !== void 0 && to !== void 0 ? detectCadence(from.data, to.data, resolved, {
2027
+ const views = this.#viewsIn(resolved);
2028
+ const from = views[views.length - 2];
2029
+ const to = views[views.length - 1];
2030
+ const approach = views[views.length - 3];
2031
+ const cadence = from !== void 0 && to !== void 0 ? detectCadence(from, to, resolved, {
1948
2032
  ...cadenceOpts,
1949
- ...approach === void 0 ? {} : { approach: approach.data },
2033
+ ...approach === void 0 ? {} : { approach },
1950
2034
  ...voicing === void 0 ? {} : { voicing: this.#closingVoicing(voicing) }
1951
2035
  }) : null;
1952
2036
  return { chords, cadence };
@@ -2011,17 +2095,18 @@ var Progression = class _Progression {
2011
2095
  */
2012
2096
  cadences(key, opts) {
2013
2097
  const resolved = this.#resolveKey(key);
2098
+ const views = this.#viewsIn(resolved);
2014
2099
  const found = [];
2015
- for (let index = 1; index < this.#chords.length; index += 1) {
2016
- const from = this.#chords[index - 1];
2017
- const to = this.#chords[index];
2100
+ for (let index = 1; index < views.length; index += 1) {
2101
+ const from = views[index - 1];
2102
+ const to = views[index];
2018
2103
  if (from === void 0 || to === void 0) {
2019
2104
  continue;
2020
2105
  }
2021
- const approach = index >= 2 ? this.#chords[index - 2]?.data : opts?.approach;
2106
+ const approach = index >= 2 ? views[index - 2] : opts?.approach;
2022
2107
  const pair = opts?.voicing;
2023
2108
  found.push(
2024
- detectCadence(from.data, to.data, resolved, {
2109
+ detectCadence(from, to, resolved, {
2025
2110
  ...opts?.alternatives === void 0 ? {} : { alternatives: opts.alternatives },
2026
2111
  ...approach === void 0 ? {} : { approach },
2027
2112
  ...pair === void 0 ? {} : { voicing: [pair[index - 1] ?? [], pair[index] ?? []] }
@@ -2182,16 +2267,26 @@ var Progression = class _Progression {
2182
2267
  /**
2183
2268
  * The plain progression data, for JSON serialization.
2184
2269
  *
2185
- * Private class fields do not serialize, so this preserves both the chord
2186
- * data and any carried key in `JSON.stringify(progression)`.
2270
+ * Private class fields do not serialize, so this preserves the chord data,
2271
+ * any carried key, and — for a progression with no key of its own — each
2272
+ * chord's own key in `JSON.stringify(progression)`.
2187
2273
  *
2188
- * @returns The chord data sequence and the carried key, if any.
2274
+ * @returns The chord data sequence, the carried key, if any, and the
2275
+ * per-chord keys when they are what spells the chords.
2189
2276
  */
2190
2277
  toJSON() {
2191
- return {
2278
+ const out = {
2192
2279
  chords: this.#chords.map((chord) => chord.toJSON()),
2193
2280
  key: this.#key?.toJSON()
2194
2281
  };
2282
+ if (this.#key === void 0 && this.#chords.some((chord) => chord.key !== void 0)) {
2283
+ out.chordKeys = this.#chords.map((chord) => chord.key?.toJSON() ?? null);
2284
+ }
2285
+ return out;
2286
+ }
2287
+ /** The members read in `key`: spellings the caller gave, the rest derived from it. */
2288
+ #viewsIn(key) {
2289
+ return this.#chords.map((chord) => chord.withKey(key).data);
2195
2290
  }
2196
2291
  /**
2197
2292
  * Resolve the key for an analysis method: explicit first, then carried.
@@ -2624,7 +2719,8 @@ var Chord = class _Chord {
2624
2719
  * @throws If no key is given and none is carried.
2625
2720
  */
2626
2721
  roman(key, opts) {
2627
- return chordToRoman(this.#data, this.#resolveKey(key), opts);
2722
+ const resolved = this.#resolveKey(key);
2723
+ return chordToRoman(this.#dataWithKey(resolved), resolved, opts);
2628
2724
  }
2629
2725
  /**
2630
2726
  * The chord's Roman numeral together with the reasoning behind it: the degree
@@ -2652,7 +2748,8 @@ var Chord = class _Chord {
2652
2748
  * ```
2653
2749
  */
2654
2750
  explain(key, opts) {
2655
- return explainRoman(this.#data, this.#resolveKey(key), opts);
2751
+ const resolved = this.#resolveKey(key);
2752
+ return explainRoman(this.#dataWithKey(resolved), resolved, opts);
2656
2753
  }
2657
2754
  /**
2658
2755
  * The chord's harmonic function (tonic / subdominant / dominant) in a key.
@@ -2663,7 +2760,8 @@ var Chord = class _Chord {
2663
2760
  * @throws If no key is given and none is carried.
2664
2761
  */
2665
2762
  function(key) {
2666
- return functionOf(this.#data, this.#resolveKey(key));
2763
+ const resolved = this.#resolveKey(key);
2764
+ return functionOf(this.#dataWithKey(resolved), resolved);
2667
2765
  }
2668
2766
  /**
2669
2767
  * Full functional analysis: function, borrowing, Roman numeral, and the
@@ -2683,7 +2781,8 @@ var Chord = class _Chord {
2683
2781
  * ```
2684
2782
  */
2685
2783
  analyze(key, opts) {
2686
- return analyzeChord(this.#data, this.#resolveKey(key), opts);
2784
+ const resolved = this.#resolveKey(key);
2785
+ return analyzeChord(this.#dataWithKey(resolved), resolved, opts);
2687
2786
  }
2688
2787
  /**
2689
2788
  * Whether the chord is borrowed from the parallel mode (modal interchange).
@@ -2694,7 +2793,8 @@ var Chord = class _Chord {
2694
2793
  * @throws If no key is given and none is carried.
2695
2794
  */
2696
2795
  isBorrowed(key) {
2697
- return isBorrowedChord(this.#data, this.#resolveKey(key));
2796
+ const resolved = this.#resolveKey(key);
2797
+ return isBorrowedChord(this.#dataWithKey(resolved), resolved);
2698
2798
  }
2699
2799
  /**
2700
2800
  * The origin of a non-diatonic chord (parallel mode or Neapolitan), or null.
@@ -2705,7 +2805,8 @@ var Chord = class _Chord {
2705
2805
  * @throws If no key is given and none is carried.
2706
2806
  */
2707
2807
  borrowedSource(key) {
2708
- return borrowedSource(this.#data, this.#resolveKey(key));
2808
+ const resolved = this.#resolveKey(key);
2809
+ return borrowedSource(this.#dataWithKey(resolved), resolved);
2709
2810
  }
2710
2811
  /**
2711
2812
  * The chord written as the figures a bass would carry under it: `6`, `64`,
@@ -2730,7 +2831,8 @@ var Chord = class _Chord {
2730
2831
  * ```
2731
2832
  */
2732
2833
  figuredBass(key) {
2733
- return figuredBassOf(this.#data, this.#resolveKey(key));
2834
+ const resolved = this.#resolveKey(key);
2835
+ return figuredBassOf(this.#dataWithKey(resolved), resolved);
2734
2836
  }
2735
2837
  /**
2736
2838
  * The chords that can stand in for this one in a key: its tritone substitute,
@@ -2757,7 +2859,8 @@ var Chord = class _Chord {
2757
2859
  * ```
2758
2860
  */
2759
2861
  substitutions(key, opts) {
2760
- return substituteChord(this.#data, this.#resolveKey(key), opts);
2862
+ const resolved = this.#resolveKey(key);
2863
+ return substituteChord(this.#dataWithKey(resolved), resolved, opts);
2761
2864
  }
2762
2865
  /**
2763
2866
  * The modal-interchange palette of the chord's key: the parallel mode's
@@ -2811,7 +2914,8 @@ var Chord = class _Chord {
2811
2914
  */
2812
2915
  voice(opts) {
2813
2916
  const key = opts?.key ?? this.#key;
2814
- return voiceChord(this.#data, key === void 0 ? opts : { ...opts, key });
2917
+ const data = opts?.key === void 0 ? this.#data : this.#dataWithKey(toKey(opts.key));
2918
+ return voiceChord(data, key === void 0 ? opts : { ...opts, key });
2815
2919
  }
2816
2920
  /**
2817
2921
  * Realize the chord as a single styled voicing (`close`, `drop2`, `drop3`,
@@ -2834,7 +2938,7 @@ var Chord = class _Chord {
2834
2938
  */
2835
2939
  negativeHarmony(key) {
2836
2940
  const resolved = this.#resolveKey(key);
2837
- return new _Chord(negativeHarmonyMirror(this.#data, resolved), resolved);
2941
+ return new _Chord(negativeHarmonyMirror(this.#dataWithKey(resolved), resolved), resolved);
2838
2942
  }
2839
2943
  /**
2840
2944
  * The V7 that tonicizes this chord: a dominant seventh a perfect fifth above
@@ -3224,17 +3328,18 @@ var Key = class _Key {
3224
3328
  `tonic must be the class API's Note; received ${describeTonic(tonic)}; wrap plain note data with Note.fromData, or omit the tonic to have one chosen`
3225
3329
  );
3226
3330
  }
3331
+ const note = tonic instanceof Note ? tonic : new Note(tonic.data);
3227
3332
  const rootPc = pitchClassOf(scale.rootPc);
3228
- if (tonic.pitchClass !== rootPc) {
3333
+ if (note.pitchClass !== rootPc) {
3229
3334
  throw new InvalidInputError(
3230
- `tonic ${tonic.name} does not match the scale root pitch class ${rootPc}; pass a tonic that spells the scale root, or omit it to have one chosen`
3335
+ `tonic ${note.name} does not match the scale root pitch class ${rootPc}; pass a tonic that spells the scale root, or omit it to have one chosen`
3231
3336
  );
3232
3337
  }
3233
3338
  if (variant !== void 0) {
3234
3339
  assertKeyVariant(variant, scale.modeMask12);
3235
3340
  }
3236
3341
  this.#scale = { rootPc, modeMask12: scale.modeMask12 };
3237
- this.#tonic = tonic;
3342
+ this.#tonic = note;
3238
3343
  this.#variant = variant ?? variantOfMask(scale.modeMask12);
3239
3344
  }
3240
3345
  /** Preserve detection metadata while replacing its plain key with this API's Key. */
@@ -3540,7 +3645,7 @@ var Key = class _Key {
3540
3645
  get data() {
3541
3646
  return this.toJSON();
3542
3647
  }
3543
- /** The detected scale form, when this key came from key detection. */
3648
+ /** The scale form this key stands in: natural, harmonic or melodic, read from the mask unless named. */
3544
3649
  get variant() {
3545
3650
  return this.#variant;
3546
3651
  }
@@ -3785,7 +3890,7 @@ var Key = class _Key {
3785
3890
  */
3786
3891
  pivotsTo(other) {
3787
3892
  assertKeyArgument(other, "key");
3788
- return pivotChords(this.#scale, other).map(({ chord, romanFrom, romanTo }) => ({
3893
+ return pivotChords(this, resolveKey(other)).map(({ chord, romanFrom, romanTo }) => ({
3789
3894
  chord: new Chord(chord, this),
3790
3895
  romanFrom,
3791
3896
  romanTo
@@ -3894,7 +3999,7 @@ var Key = class _Key {
3894
3999
  if (this.pitchClasses().length !== HEPTATONIC_DEGREES) {
3895
4000
  return this.isMinor ? "minor" : "major";
3896
4001
  }
3897
- const { quality } = diatonicTriad(n, this.#scale);
4002
+ const { quality } = diatonicTriad(n, this);
3898
4003
  return quality === "min" || quality === "dim" ? "minor" : "major";
3899
4004
  }
3900
4005
  /**
@@ -3903,7 +4008,7 @@ var Key = class _Key {
3903
4008
  *
3904
4009
  * Unlike {@link Key.transpose}, which picks a letter from the semitone count,
3905
4010
  * the interval's diatonic number decides the tonic's letter: G# minor down an
3906
- * augmented fourth is D minor, while down a diminished fifth it is Dbb minor.
4011
+ * augmented fourth is D minor, while down a diminished fifth it is C## minor.
3907
4012
  * The mode mask is unchanged, so the scale keeps its shape.
3908
4013
  *
3909
4014
  * @param interval The interval to apply, as a name (`'A4'`, `'-A4'`), plain
@@ -4116,6 +4221,15 @@ var Key = class _Key {
4116
4221
  const anchor = tonic === void 0 ? this.#tonic.data : toNoteData(tonic);
4117
4222
  return spellPitchClasses([...pcs], anchor, this.#scale).map((note) => new Note(note));
4118
4223
  }
4224
+ /**
4225
+ * A degree chord without the spelling its builder attached.
4226
+ *
4227
+ * The chord carries this key, which spells its root on read, so a chord
4228
+ * re-keyed later follows its new key instead of keeping these letters.
4229
+ */
4230
+ #withoutSpelling(chord) {
4231
+ return makeChord(chord.rootPc, chord.quality);
4232
+ }
4119
4233
  /**
4120
4234
  * Build a chord on a scale degree, carrying this key as context.
4121
4235
  *
@@ -4133,8 +4247,8 @@ var Key = class _Key {
4133
4247
  * an explicit `quality` for those.
4134
4248
  */
4135
4249
  chord(degree, quality) {
4136
- const data = quality === void 0 ? diatonicTriad(degree, this.#scale) : chordFromDegree(degree, quality, this.#scale);
4137
- return new Chord(data, this);
4250
+ const data = quality === void 0 ? diatonicTriad(degree, this) : chordFromDegree(degree, quality, this);
4251
+ return new Chord(this.#withoutSpelling(data), this);
4138
4252
  }
4139
4253
  /**
4140
4254
  * The diatonic triad on a scale degree, carrying this key as context.
@@ -4146,7 +4260,7 @@ var Key = class _Key {
4146
4260
  * degrees. Use {@link Key.chord} with an explicit quality instead.
4147
4261
  */
4148
4262
  diatonicTriad(degree) {
4149
- return new Chord(diatonicTriad(degree, this.#scale), this);
4263
+ return new Chord(this.#withoutSpelling(diatonicTriad(degree, this)), this);
4150
4264
  }
4151
4265
  /**
4152
4266
  * The diatonic seventh chord on a scale degree, carrying this key as context.
@@ -4158,7 +4272,7 @@ var Key = class _Key {
4158
4272
  * degrees. Use {@link Key.chord} with an explicit quality instead.
4159
4273
  */
4160
4274
  diatonicSeventh(degree) {
4161
- return new Chord(diatonicSeventh(degree, this.#scale), this);
4275
+ return new Chord(this.#withoutSpelling(diatonicSeventh(degree, this)), this);
4162
4276
  }
4163
4277
  /**
4164
4278
  * Build the chord denoted by a Roman numeral in this key (including applied
@@ -4268,12 +4382,11 @@ var Key = class _Key {
4268
4382
  *
4269
4383
  * Private class fields do not serialize, so an explicit `toJSON` keeps
4270
4384
  * `JSON.stringify(key)` from collapsing to `{}`. The result pairs the
4271
- * `KeyScale` with the spelled tonic and, for a detected harmonic or melodic
4272
- * minor, the scale form — everything {@link Key.fromJSON} needs to rebuild a
4273
- * key that reads and prints as this one.
4385
+ * `KeyScale` with the spelled tonic and the scale form — everything
4386
+ * {@link Key.fromJSON} needs to rebuild a key that reads and prints as this
4387
+ * one.
4274
4388
  *
4275
- * @returns The key/scale, its spelled tonic, and its scale form when it has
4276
- * one.
4389
+ * @returns The key/scale, its spelled tonic, and its scale form.
4277
4390
  */
4278
4391
  toJSON() {
4279
4392
  return { scale: this.scale, tonic: this.#tonic.data, variant: this.#variant };
@@ -4384,21 +4497,9 @@ function tempoFrom(tempo) {
4384
4497
  }
4385
4498
  return typeof tempo === "number" ? [{ startBeat: 0, bpm: tempo }] : tempo;
4386
4499
  }
4387
- function statedKeyRegions(key, notes, totalBeats) {
4500
+ function scoreKeyRegions(key, notes, totalBeats) {
4388
4501
  const first = notes[0];
4389
- if (first === void 0 || totalBeats <= first.startBeat) {
4390
- return [];
4391
- }
4392
- return [
4393
- {
4394
- // The region starts where the music does, as an inferred one does, so a
4395
- // pickup is covered by the key it sounds in.
4396
- startBeat: first.startBeat,
4397
- endBeat: totalBeats,
4398
- key: keyIdentity(key),
4399
- confidence: STATED_KEY_CONFIDENCE
4400
- }
4401
- ];
4502
+ return first === void 0 ? [] : statedKeyRegions(keyIdentity(key), first.startBeat, totalBeats);
4402
4503
  }
4403
4504
  function scoreData(notes, opts) {
4404
4505
  const data = {
@@ -4653,43 +4754,42 @@ var Score = class _Score {
4653
4754
  /**
4654
4755
  * The notes pushed off the grid the way a player would.
4655
4756
  *
4656
- * The metric accents are read from the score's opening signature, since
4657
- * {@link HumanizeOptions} names one signature rather than a map; pass `ts`
4658
- * to read a later stretch of a score that changes meter.
4757
+ * The metric accents are read against the score's meter map, exactly as
4758
+ * {@link humanize} reads that map; pass `ts` to read against another meter.
4659
4759
  *
4660
4760
  * @param opts Jitter amounts, accent depth and the seed; see
4661
4761
  * {@link HumanizeOptions}.
4662
4762
  * @returns The humanized score.
4663
4763
  */
4664
4764
  humanize(opts) {
4665
- return this.#withNotes(humanize(this.#data.notes, { ts: this.meterAt(0), ...opts }));
4765
+ return this.#withNotes(humanize(this.#data.notes, { ts: this.#data.meters, ...opts }));
4666
4766
  }
4667
4767
  /**
4668
4768
  * The notes with ornaments added.
4669
4769
  *
4670
- * Strong positions are read from the score's opening signature, since
4671
- * {@link OrnamentOptions} names one signature rather than a map.
4770
+ * Strong positions are read against the score's meter map, exactly as
4771
+ * {@link ornament} reads that map.
4672
4772
  *
4673
4773
  * @param opts The ornament style, how much is decorated, and the seed; see
4674
4774
  * {@link OrnamentOptions}.
4675
4775
  * @returns The ornamented score.
4676
4776
  */
4677
4777
  ornament(opts) {
4678
- return this.#withNotes(ornament(this.#data.notes, { ts: this.meterAt(0), ...opts }));
4778
+ return this.#withNotes(ornament(this.#data.notes, { ts: this.#data.meters, ...opts }));
4679
4779
  }
4680
4780
  /**
4681
4781
  * The notes placed on a groove template's slots.
4682
4782
  *
4683
- * The template's per-bar grid is laid out against the score's opening
4684
- * signature, which is the signature the template must have been extracted
4685
- * under; a template carrying a different one is refused rather than drifting.
4783
+ * The template's per-bar grid is laid out against the score's meter map,
4784
+ * exactly as {@link applyGrooveTemplate} reads that map; a template extracted
4785
+ * under another meter is refused rather than drifting.
4686
4786
  *
4687
4787
  * @param template The groove template, from `extractGrooveTemplate`.
4688
4788
  * @returns The score with the template's feel imposed.
4689
4789
  * @throws If the template was extracted under another meter.
4690
4790
  */
4691
4791
  groove(template) {
4692
- return this.#withNotes(applyGrooveTemplate(this.#data.notes, template, this.meterAt(0)));
4792
+ return this.#withNotes(applyGrooveTemplate(this.#data.notes, template, this.#data.meters));
4693
4793
  }
4694
4794
  /**
4695
4795
  * The feel of the score's own timing, as a template another score can take on.
@@ -4698,8 +4798,8 @@ var Score = class _Score {
4698
4798
  * slot by slot for how far ahead of or behind the grid it sits and how hard
4699
4799
  * it is struck, and the reading is handed back as a template. Only the
4700
4800
  * subdivision is named, because the score already holds the meter the grid is
4701
- * laid out against — its opening signature, which is the one the template
4702
- * records and the one an apply-time meter is checked against.
4801
+ * laid out against — its meter map, read exactly as
4802
+ * {@link extractGrooveTemplate} reads that map.
4703
4803
  *
4704
4804
  * @param subdivision Grid steps per quarter-note beat; a sixteenth-note grid
4705
4805
  * by default, which is where swing, push and drag are audible.
@@ -4713,7 +4813,7 @@ var Score = class _Score {
4713
4813
  * ```
4714
4814
  */
4715
4815
  grooveTemplate(subdivision) {
4716
- return extractGrooveTemplate(this.#data.notes, this.meterAt(0), subdivision);
4816
+ return extractGrooveTemplate(this.#data.notes, this.#data.meters, subdivision);
4717
4817
  }
4718
4818
  /**
4719
4819
  * The harmony the notes spell out, in time.
@@ -4767,7 +4867,8 @@ var Score = class _Score {
4767
4867
  keys(opts) {
4768
4868
  if (opts !== void 0) {
4769
4869
  if (this.#key !== void 0) {
4770
- return statedKeyRegions(this.#key, this.#data.notes, opts.totalBeats ?? this.totalBeats);
4870
+ const totalBeats = opts.totalBeats === void 0 || opts.totalBeats === null ? this.totalBeats : assertRange(opts.totalBeats, 0, Number.MAX_SAFE_INTEGER, "key timeline totalBeats");
4871
+ return scoreKeyRegions(this.#key, this.#data.notes, totalBeats);
4771
4872
  }
4772
4873
  return keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters, ...opts });
4773
4874
  }
@@ -4851,10 +4952,16 @@ var Score = class _Score {
4851
4952
  * @returns The grouping, its downbeats, a confidence, and a rationale.
4852
4953
  */
4853
4954
  hypermeter(opts) {
4854
- return hypermeter(this.#data.notes, this.#data.meters, {
4855
- cadenceBeats: this.#cadenceBeats(),
4856
- ...opts
4857
- });
4955
+ const asked = { ...opts };
4956
+ if ((asked.cadenceBeats ?? null) !== null) {
4957
+ return hypermeter(this.#data.notes, this.#data.meters, asked);
4958
+ }
4959
+ return hypermeterOver(
4960
+ this.#data.notes,
4961
+ this.#data.meters,
4962
+ asked,
4963
+ () => this.#cadenceSpans(asked.budget)
4964
+ );
4858
4965
  }
4859
4966
  /**
4860
4967
  * The motifs the melody repeats.
@@ -4869,8 +4976,9 @@ var Score = class _Score {
4869
4976
  *
4870
4977
  * A melody is one line, so a score holding several is read as its top voice:
4871
4978
  * notes struck together are one event and the highest of them stands for it.
4872
- * A piano part read whole therefore answers for the voice a listener follows
4873
- * rather than for a line assembled from the insides of its chords. Split the
4979
+ * A block-chord part read whole therefore answers for its top voice rather
4980
+ * than for a line assembled from the insides of its chords; a lower note
4981
+ * struck while a higher one still sounds stays in the line, though. Split the
4874
4982
  * score into lines first to have each of them answered for.
4875
4983
  *
4876
4984
  * @param opts Cell-length bounds and the recurrence threshold; see
@@ -5271,15 +5379,24 @@ var Score = class _Score {
5271
5379
  * Kept on first reading for the same reason the chord timeline is.
5272
5380
  */
5273
5381
  #keyRegions() {
5274
- const regions = this.#regions ?? (this.#key === void 0 ? keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters }) : statedKeyRegions(this.#key, this.#data.notes, this.totalBeats));
5382
+ const regions = this.#regions ?? (this.#key === void 0 ? keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters }) : scoreKeyRegions(this.#key, this.#data.notes, this.totalBeats));
5275
5383
  this.#regions = regions;
5276
5384
  return regions;
5277
5385
  }
5278
- /** Where the score's own harmony closes, as beats. */
5279
- #cadenceBeats() {
5280
- return detectCadences(this.#chordTimeline(), this.#keyContext(void 0)).map(
5281
- (hit) => hit.atBeat
5386
+ /**
5387
+ * Where the score's own harmony closes, as the spans its arrival chords sound
5388
+ * through, read in the key {@link Score.phrases} reads them in.
5389
+ */
5390
+ #cadenceSpans(budget) {
5391
+ const timeline = this.#chordTimeline();
5392
+ const key = keyContextFor(
5393
+ this.#key === void 0 ? void 0 : keyIdentity(this.#key),
5394
+ timeline,
5395
+ this.#data.meters,
5396
+ void 0,
5397
+ budget
5282
5398
  );
5399
+ return boundaryCadences(timeline, key).spans;
5283
5400
  }
5284
5401
  /** The score's context as chord-timeline options, with the caller's on top. */
5285
5402
  #analysisOptions(opts) {
@@ -5355,7 +5472,7 @@ function copySegments(segments) {
5355
5472
  function chordTimelineOf(segments) {
5356
5473
  return new Timeline({ segments: [...segments], totalBeats: spanEnd(segments) }).chordTimeline;
5357
5474
  }
5358
- function copySettings(settings, trackCount) {
5475
+ function copySettings(settings, tracks) {
5359
5476
  if (settings === void 0) {
5360
5477
  return void 0;
5361
5478
  }
@@ -5382,8 +5499,9 @@ function copySettings(settings, trackCount) {
5382
5499
  settings.harmonyTracks,
5383
5500
  "arrangement harmonyTracks"
5384
5501
  ).map(
5385
- (track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0, trackCount - 1)
5502
+ (track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0, tracks.length - 1)
5386
5503
  );
5504
+ harmonyTrackSet(copy.harmonyTracks, tracks, "arrangement harmonyTracks");
5387
5505
  }
5388
5506
  if (settings.minSeverity !== void 0) {
5389
5507
  copy.minSeverity = assertInteger(
@@ -5400,6 +5518,11 @@ function copySettings(settings, trackCount) {
5400
5518
  Number.MAX_SAFE_INTEGER,
5401
5519
  "arrangement pickupBeats"
5402
5520
  );
5521
+ assertTrackNotes(tracks, {
5522
+ allowNonPositiveDuration: true,
5523
+ minStartBeat: -copy.pickupBeats,
5524
+ budget: settings.budget
5525
+ });
5403
5526
  }
5404
5527
  if (settings.harmonicRhythm !== void 0) {
5405
5528
  copy.harmonicRhythm = assertRange(
@@ -5410,7 +5533,7 @@ function copySettings(settings, trackCount) {
5410
5533
  );
5411
5534
  }
5412
5535
  if (settings.profile !== void 0) {
5413
- copy.profile = settings.profile;
5536
+ copy.profile = arrangementProfile(settings.profile);
5414
5537
  }
5415
5538
  if (settings.budget !== void 0) {
5416
5539
  copy.budget = assertPositiveInt(settings.budget, "arrangement budget", Number.MAX_SAFE_INTEGER);
@@ -5423,7 +5546,7 @@ function copyArrangement(data) {
5423
5546
  const tracks = assertDataArray(data.tracks, "arrangement tracks").map(
5424
5547
  (track, index) => copyTrack(track, index, budget)
5425
5548
  );
5426
- const settings = copySettings(data.settings, tracks.length);
5549
+ const settings = copySettings(data.settings, tracks);
5427
5550
  return settings === void 0 ? { tracks } : { tracks, settings };
5428
5551
  }
5429
5552
  function arrangementData(tracks, settings) {
@@ -5736,7 +5859,8 @@ var Arrangement = class _Arrangement {
5736
5859
  }
5737
5860
  }
5738
5861
  return referenceFromReadings(harmony, this.#session().analysis, {
5739
- meters: this.#data.settings?.meters,
5862
+ // The meter the arrangement's own analysis reads, `ts` included.
5863
+ meters: this.#data.settings?.meters ?? this.#data.settings?.ts,
5740
5864
  totalBeats: this.timeline().totalBeats,
5741
5865
  ...melody.length === 0 ? {} : { melody },
5742
5866
  ...opts
@@ -5847,7 +5971,9 @@ function copyProfile(profile) {
5847
5971
  }
5848
5972
  const reach = {};
5849
5973
  for (const [pitch, limbs] of Object.entries(profile.reach)) {
5850
- reach[Number(pitch)] = [...limbs];
5974
+ if (String(Number(pitch)) === pitch) {
5975
+ reach[Number(pitch)] = [...limbs];
5976
+ }
5851
5977
  }
5852
5978
  const percussion = {
5853
5979
  kind: "percussion",
@@ -6359,7 +6485,7 @@ var Composer = class _Composer {
6359
6485
  ...instrument === void 0 ? {} : { instrument: toStringedProfile(instrument, "bass instrument") },
6360
6486
  segments,
6361
6487
  key,
6362
- ts: this.#openingMeter(),
6488
+ ts: this.#meters,
6363
6489
  ctx: this.context
6364
6490
  });
6365
6491
  return Score.of(notes, this.#scoreOptions(key));
@@ -6391,7 +6517,7 @@ var Composer = class _Composer {
6391
6517
  ...timeline === void 0 ? {} : { timeline: toChordTimeline(timeline) },
6392
6518
  melody: melody.notes,
6393
6519
  key,
6394
- ts: this.#openingMeter(),
6520
+ ts: this.#meters,
6395
6521
  ctx: this.context
6396
6522
  });
6397
6523
  return Score.of(notes, this.#scoreOptions(key));
@@ -6421,14 +6547,15 @@ var Composer = class _Composer {
6421
6547
  ...opts,
6422
6548
  melody: melody.notes,
6423
6549
  ...this.#key === void 0 ? {} : { key: this.#key },
6424
- ts: this.#openingMeter(),
6550
+ ts: this.#meters,
6425
6551
  ctx: this.context
6426
6552
  });
6427
6553
  const moved = melody.transpose(result.transposeSemitones);
6554
+ const last = result.chords[result.chords.length - 1];
6428
6555
  const totalBeats = harmonizedSpan(
6429
6556
  moved.totalBeats,
6430
6557
  result.chords,
6431
- beatsPerBar(this.#openingMeter())
6558
+ beatsPerBarAt(last?.startBeat ?? 0, this.#meters)
6432
6559
  );
6433
6560
  return {
6434
6561
  chords: Timeline.fromChords(result.chords, totalBeats, result.key),
@@ -6563,16 +6690,6 @@ var Composer = class _Composer {
6563
6690
  }
6564
6691
  return this.#key;
6565
6692
  }
6566
- /**
6567
- * The signature the parts are written against.
6568
- *
6569
- * Each generator names one time signature rather than a map, so a composer
6570
- * whose meter changes hands over the signature its music opens on — the same
6571
- * reading {@link Score.humanize} and its siblings take.
6572
- */
6573
- #openingMeter() {
6574
- return meterAt(0, this.#meters);
6575
- }
6576
6693
  /**
6577
6694
  * The one signature a part laid out in bars is written against.
6578
6695
  *
@@ -6587,7 +6704,7 @@ var Composer = class _Composer {
6587
6704
  `composer ${part} needs one meter for the whole part; the composer changes meter`
6588
6705
  );
6589
6706
  }
6590
- return this.#openingMeter();
6707
+ return meterAt(0, this.#meters);
6591
6708
  }
6592
6709
  /** The context every score a composer hands back is read against. */
6593
6710
  #scoreOptions(key) {
@@ -7084,6 +7201,7 @@ var Motif = class _Motif {
7084
7201
  * ```
7085
7202
  */
7086
7203
  static generate(opts) {
7204
+ assertRecord(opts, "motif options");
7087
7205
  return new _Motif(generateMotif({ ...opts, chord: chordFrom(opts.chord) }));
7088
7206
  }
7089
7207
  /**
@@ -7098,7 +7216,7 @@ var Motif = class _Motif {
7098
7216
  * @throws If a note carries a value the transforms cannot hold.
7099
7217
  */
7100
7218
  static fromNotes(notes) {
7101
- return new _Motif({ notes: [...notes] });
7219
+ return new _Motif({ notes: [...assertArray(notes, "motif notes")] });
7102
7220
  }
7103
7221
  /** Rebuild a motif from the plain data {@link Motif.data} hands out. */
7104
7222
  static fromData(cell) {
@@ -7417,7 +7535,7 @@ var Rhythm = class _Rhythm {
7417
7535
  * @returns The compressed pattern.
7418
7536
  */
7419
7537
  doubleTime() {
7420
- return this.#withGrid(doubleTime(toGrid(this.#data.events), this.#spanSteps()));
7538
+ return this.#withGrid(doubleTimeFigure(toGrid(this.#data.events), this.#spanSteps()));
7421
7539
  }
7422
7540
  /**
7423
7541
  * Stretch the pattern to twice its length: everything falls half as often.
@@ -7428,7 +7546,7 @@ var Rhythm = class _Rhythm {
7428
7546
  * @returns The stretched pattern.
7429
7547
  */
7430
7548
  halfTime() {
7431
- return this.#withGrid(halfTime(toGrid(this.#data.events), this.#spanSteps()));
7549
+ return this.#withGrid(halfTimeFigure(toGrid(this.#data.events), this.#spanSteps()));
7432
7550
  }
7433
7551
  /**
7434
7552
  * Keep as much decoration as the ornament dial asks for.
@@ -7475,14 +7593,16 @@ var Rhythm = class _Rhythm {
7475
7593
  rhythmic: resolved.rhythmic,
7476
7594
  ornament: resolved.ornament,
7477
7595
  rate: opts?.rate,
7478
- spanSteps: this.#spanSteps(),
7479
7596
  ts: this.#data.ts,
7480
7597
  // The transform hands the predicate the events it was given, which carry
7481
7598
  // the onset each was built from; the parameter is declared over the bare
7482
7599
  // grid event the rule reads, so the field is named back here.
7483
7600
  isOrnament: isOrnament === void 0 ? void 0 : (grid) => isOrnament(grid.source)
7484
7601
  };
7485
- return this.#withGrid(deform(toGrid(this.#data.events), options, resolved.part(PART)));
7602
+ const dials = resolveDials(options);
7603
+ return this.#withGrid(
7604
+ deformFigure(toGrid(this.#data.events), this.#spanSteps(), dials, resolved.part(PART), [])
7605
+ );
7486
7606
  }
7487
7607
  /**
7488
7608
  * Whether one player can sustain the pattern at the context's tempo.
@@ -7543,7 +7663,11 @@ var Rhythm = class _Rhythm {
7543
7663
  toJSON() {
7544
7664
  return this.data;
7545
7665
  }
7546
- /** The pattern's span as a count of sixteenths, for the transforms. */
7666
+ /**
7667
+ * The pattern's span as a count of sixteenths, for the transforms: whatever
7668
+ * span the pattern covers, an empty one or one ending off the straight grid
7669
+ * included.
7670
+ */
7547
7671
  #spanSteps() {
7548
7672
  return spanOf(this.#data.events) / STEP_BEATS;
7549
7673
  }
@@ -8527,4 +8651,4 @@ export {
8527
8651
  Tuning,
8528
8652
  Voicing
8529
8653
  };
8530
- //# sourceMappingURL=chunk-IPEF6NWB.js.map
8654
+ //# sourceMappingURL=chunk-5MWRBYAT.js.map