@libraz/libcantus 1.2.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 (131) hide show
  1. package/NOTICE +63 -0
  2. package/README.md +7 -1
  3. package/README_ja.md +17 -11
  4. package/dist/analyze/index.cjs +31 -7
  5. package/dist/analyze/index.cjs.map +1 -1
  6. package/dist/analyze/index.d.cts +161 -13
  7. package/dist/analyze/index.d.ts +161 -13
  8. package/dist/analyze/index.js +37 -13
  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-HPVSH7SF.cjs → chunk-3MDOEFIQ.cjs} +618 -954
  14. package/dist/chunk-3MDOEFIQ.cjs.map +1 -0
  15. package/dist/{chunk-FBH75DAY.js → chunk-5MWRBYAT.js} +560 -159
  16. package/dist/chunk-5MWRBYAT.js.map +1 -0
  17. package/dist/{chunk-J7FHVZII.cjs → chunk-6TTBEON3.cjs} +3074 -740
  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-HWHGOVO6.cjs → chunk-E3QO3UET.cjs} +31 -23
  22. package/dist/chunk-E3QO3UET.cjs.map +1 -0
  23. package/dist/{chunk-H2KJHBW4.js → chunk-FJFULJZE.js} +2375 -191
  24. package/dist/chunk-FJFULJZE.js.map +1 -0
  25. package/dist/{chunk-Q6LFSAKV.js → chunk-FK3DGAIZ.js} +2657 -323
  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-DSMJRXCZ.cjs → chunk-HYSVSFNI.cjs} +931 -530
  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-LQZQDBCD.cjs +5513 -0
  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-HYJJ2V2I.js → chunk-UTE5A36R.js} +483 -819
  46. package/dist/chunk-UTE5A36R.js.map +1 -0
  47. package/dist/{chunk-NFJ4P6ZZ.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 +20 -8
  67. package/dist/generate/index.cjs.map +1 -1
  68. package/dist/generate/index.d.cts +105 -17
  69. package/dist/generate/index.d.ts +105 -17
  70. package/dist/generate/index.js +19 -7
  71. package/dist/{index-CL4b4gUQ.d.ts → index-5VJgKz_Q.d.cts} +6 -2
  72. package/dist/{index-B9QLziDe.d.cts → index-BLuDesVG.d.ts} +20 -13
  73. package/dist/{index-BHIUyjhZ.d.ts → index-BV6APVa6.d.cts} +20 -13
  74. package/dist/{index-BMweUlm5.d.cts → 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-BNSM-DSk.d.cts → index-DVInzCDS.d.cts} +533 -28
  80. package/dist/{index-9eoQs2C1.d.ts → index-Dg-SAfhz.d.ts} +6 -4
  81. package/dist/{index-BJgP97N1.d.ts → index-jgeug9EA.d.ts} +533 -28
  82. package/dist/{index-cHvbYgJI.d.cts → index-pQihL4z7.d.cts} +6 -4
  83. package/dist/index.cjs +49 -13
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.d.cts +13 -13
  86. package/dist/index.d.ts +13 -13
  87. package/dist/index.js +55 -19
  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 +259 -48
  93. package/dist/model/index.d.ts +259 -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/{index-BRMbPkHE.d.ts → profile-B4WqcM4H.d.ts} +85 -610
  98. package/dist/{index-DGCNKAUr.d.cts → profile-BJd1XkPB.d.cts} +85 -610
  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/{index-CJBBiAmU.d.ts → types-7mGFwvMm.d.ts} +1210 -31
  105. package/dist/{index-BBWNZS0f.d.cts → types-_ofpb2be.d.cts} +1210 -31
  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-76ZBTRG2.cjs.map +0 -1
  110. package/dist/chunk-7KG3RTYS.js.map +0 -1
  111. package/dist/chunk-AGOVW7U6.js.map +0 -1
  112. package/dist/chunk-CGBADIO5.cjs.map +0 -1
  113. package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
  114. package/dist/chunk-FBH75DAY.js.map +0 -1
  115. package/dist/chunk-H2KJHBW4.js.map +0 -1
  116. package/dist/chunk-HPVSH7SF.cjs.map +0 -1
  117. package/dist/chunk-HWHGOVO6.cjs.map +0 -1
  118. package/dist/chunk-HYJJ2V2I.js.map +0 -1
  119. package/dist/chunk-J7FHVZII.cjs.map +0 -1
  120. package/dist/chunk-M5RYMAFI.js.map +0 -1
  121. package/dist/chunk-NB25P3EH.js.map +0 -1
  122. package/dist/chunk-NFJ4P6ZZ.js.map +0 -1
  123. package/dist/chunk-NM7UAMC3.cjs.map +0 -1
  124. package/dist/chunk-PM4XJE2Y.cjs.map +0 -1
  125. package/dist/chunk-PUKJP2Z5.js.map +0 -1
  126. package/dist/chunk-Q6LFSAKV.js.map +0 -1
  127. package/dist/chunk-RFIBF2RR.js.map +0 -1
  128. package/dist/chunk-S5BKZZGM.cjs.map +0 -1
  129. package/dist/chunk-SGLEQVJH.cjs +0 -3329
  130. package/dist/chunk-SGLEQVJH.cjs.map +0 -1
  131. package/dist/chunk-UMWZ4ZET.js.map +0 -1
@@ -1,20 +1,22 @@
1
1
  import {
2
2
  analyzeArrangement,
3
3
  analyzePolyphony,
4
+ analyzeReference,
5
+ arrangementProfile,
6
+ assertTrackNotes,
7
+ boundaryCadences,
4
8
  createArrangementSession,
5
- extractMotifs,
9
+ harmonyTrackSet,
6
10
  hypermeter,
7
- melodicContour,
8
- melodicSimilarity,
9
- motifFromNotes,
11
+ hypermeterOver,
12
+ keyContextFor,
10
13
  phrasesFromTimeline,
11
- reduceProgression,
12
- relateMotifs,
14
+ referenceFromReadings,
13
15
  sectionsFromNotes,
14
16
  structuralCadences,
15
17
  tensionCurveFrom,
16
18
  trackRoleOf
17
- } from "./chunk-HYJJ2V2I.js";
19
+ } from "./chunk-UTE5A36R.js";
18
20
  import {
19
21
  TWELVE_TET,
20
22
  beatsToDuration,
@@ -38,22 +40,25 @@ import {
38
40
  tempoAt,
39
41
  tempoReader,
40
42
  ticksToBeats
41
- } from "./chunk-UMWZ4ZET.js";
43
+ } from "./chunk-7NQ6AL3H.js";
42
44
  import {
43
45
  STEP_BEATS,
44
46
  applyGrooveTemplate,
45
47
  cellSpan,
46
- deform,
48
+ deformFigure,
49
+ deriveCompositionPlan,
47
50
  developMotif,
48
- doubleTime,
51
+ doubleTimeFigure,
52
+ evaluateComposition,
49
53
  extractGrooveTemplate,
50
54
  generateBassLine,
51
55
  generateCounterMelody,
52
56
  generateDrums,
57
+ generateMelody,
53
58
  generateMotif,
54
59
  generateProgression,
55
60
  generateRhythm,
56
- halfTime,
61
+ halfTimeFigure,
57
62
  harmonizeMelody,
58
63
  humanize,
59
64
  modalInterchangePalette,
@@ -62,6 +67,7 @@ import {
62
67
  ornament,
63
68
  ornamentBy,
64
69
  resolveContext,
70
+ resolveDials,
65
71
  rhythmDensity,
66
72
  rhythmToNoteEvents,
67
73
  substituteChord,
@@ -69,9 +75,11 @@ import {
69
75
  thin,
70
76
  transformMotif,
71
77
  withinCeiling
72
- } from "./chunk-Q6LFSAKV.js";
78
+ } from "./chunk-FK3DGAIZ.js";
73
79
  import {
74
80
  analyzeChord,
81
+ analyzeRhythm,
82
+ assertSpansInOrder,
75
83
  augmentedSixthChord,
76
84
  borrowedSource,
77
85
  chordAtBeat,
@@ -87,17 +95,25 @@ import {
87
95
  detectKeyFromNotes,
88
96
  detectModulations,
89
97
  explainRoman,
98
+ extractMotifs,
90
99
  functionOf,
91
100
  isBorrowedChord,
92
101
  isMinorKey,
93
102
  keyLookup,
94
103
  keyTimelineFromNotes,
104
+ melodicContour,
105
+ melodicSimilarity,
106
+ motifFromNotes,
107
+ motifGraph,
95
108
  pivotChords,
96
109
  prevailingKeyOf,
110
+ reduceProgression,
111
+ relateMotifs,
97
112
  romanToChord,
98
113
  secondaryDominantOf,
114
+ statedKeyRegions,
99
115
  toVoiceNotes
100
- } from "./chunk-H2KJHBW4.js";
116
+ } from "./chunk-FJFULJZE.js";
101
117
  import {
102
118
  BASS_4_STRING,
103
119
  BASS_5_STRING,
@@ -110,10 +126,10 @@ import {
110
126
  soundsIn,
111
127
  toInstrumentProfile,
112
128
  toStringedProfile
113
- } from "./chunk-7KG3RTYS.js";
129
+ } from "./chunk-VL5NXVCN.js";
114
130
  import {
115
131
  createNoteEventIndex
116
- } from "./chunk-PUKJP2Z5.js";
132
+ } from "./chunk-TXFWSU5D.js";
117
133
  import {
118
134
  SATB_RANGES,
119
135
  checkPartWriting,
@@ -128,14 +144,14 @@ import {
128
144
  voiceChordStyled,
129
145
  voiceLeadingCost,
130
146
  voiceProgression
131
- } from "./chunk-M5RYMAFI.js";
147
+ } from "./chunk-ZLNSH4UL.js";
132
148
  import {
133
149
  availableTensions,
134
150
  avoidNotes,
135
151
  chordScaleReport,
136
152
  chordScales,
137
153
  scalesForChanges
138
- } from "./chunk-NB25P3EH.js";
154
+ } from "./chunk-2G5MCGYU.js";
139
155
  import {
140
156
  NAMED_SCALES,
141
157
  assertChordQuality,
@@ -146,6 +162,7 @@ import {
146
162
  chordSpecOf,
147
163
  chordToneRole,
148
164
  chordToneSpellings,
165
+ classifySpelledInterval,
149
166
  diatonicPitchClasses,
150
167
  diatonicSeventh,
151
168
  diatonicTriad,
@@ -191,10 +208,11 @@ import {
191
208
  tryResolveKeyName,
192
209
  variantOfMask,
193
210
  voiceIndependence
194
- } from "./chunk-RFIBF2RR.js";
211
+ } from "./chunk-H7FVMBUC.js";
195
212
  import {
196
213
  InvalidInputError,
197
214
  MAX_NAME_ACCIDENTALS,
215
+ assertArray,
198
216
  assertFiniteNumber,
199
217
  assertInteger,
200
218
  assertMidiPitch,
@@ -202,9 +220,11 @@ import {
202
220
  assertOneOf,
203
221
  assertPositiveInt,
204
222
  assertRange,
223
+ assertRecord,
205
224
  assertTimeSignature,
206
225
  beatToBarPosition,
207
226
  beatsPerBar,
227
+ beatsPerBarAt,
208
228
  describeRejected,
209
229
  formatBarPosition,
210
230
  formatKeyName,
@@ -212,7 +232,6 @@ import {
212
232
  formatTimeSignature,
213
233
  intervalSemitones,
214
234
  isCompound,
215
- isConsonantInterval,
216
235
  isStrongBeat,
217
236
  meterAt,
218
237
  metricWeight,
@@ -235,7 +254,7 @@ import {
235
254
  tryParseTimeSignature,
236
255
  tuplet,
237
256
  unwrapParse
238
- } from "./chunk-AGOVW7U6.js";
257
+ } from "./chunk-YYVXRV3V.js";
239
258
 
240
259
  // src/model/interval.ts
241
260
  var INVERSION_SUM = 9;
@@ -244,6 +263,14 @@ function simpleDegree(numberValue) {
244
263
  const reduced = (numberValue - 1) % 7 + 1;
245
264
  return reduced === 1 && numberValue > 1 ? OCTAVE_DEGREES : reduced;
246
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
+ }
247
274
  function invertQuality(quality) {
248
275
  if (quality === "P") {
249
276
  return "P";
@@ -275,6 +302,12 @@ var Interval = class _Interval {
275
302
  * @returns The interval from `a` to `b`.
276
303
  */
277
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
+ }
278
311
  return a.intervalTo(b);
279
312
  }
280
313
  /**
@@ -305,7 +338,12 @@ var Interval = class _Interval {
305
338
  */
306
339
  static fromData(data) {
307
340
  const checked = toSpelledInterval(data);
308
- 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
+ );
309
347
  }
310
348
  /**
311
349
  * Rebuild an interval from its {@link Interval.toJSON} output.
@@ -430,12 +468,15 @@ var Interval = class _Interval {
430
468
  /**
431
469
  * Whether the interval is consonant.
432
470
  *
471
+ * The spelling decides, not the span: a diminished fourth is a dissonance
472
+ * though it sounds like a major third.
473
+ *
433
474
  * @param twoVoice When true, the perfect fourth counts as a dissonance,
434
475
  * matching two-voice counterpoint.
435
476
  * @returns True if the interval is consonant in that context.
436
477
  */
437
478
  isConsonant(twoVoice = true) {
438
- return isConsonantInterval(this.#semitones, twoVoice);
479
+ return classifySpelledInterval(this.toJSON(), twoVoice) !== 2 /* Dissonance */;
439
480
  }
440
481
  /**
441
482
  * Whether another interval is spelled identically.
@@ -482,7 +523,6 @@ var Interval = class _Interval {
482
523
  };
483
524
 
484
525
  // src/model/shared.ts
485
- var STATED_KEY_CONFIDENCE = 1;
486
526
  function describeShape(value) {
487
527
  if (value === null) {
488
528
  return "null";
@@ -1065,6 +1105,22 @@ var Note = class _Note {
1065
1105
  };
1066
1106
 
1067
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
+ }
1068
1124
  function copySegment(segment) {
1069
1125
  assertDataObject(segment, "timeline segment");
1070
1126
  return {
@@ -1087,14 +1143,14 @@ function copyKeyRegion(region) {
1087
1143
  confidence: assertFiniteNumber(region.confidence, "key region confidence")
1088
1144
  };
1089
1145
  if (region.modulation !== void 0) {
1090
- copy.modulation = region.modulation;
1146
+ copy.modulation = assertOneOf(region.modulation, KEY_RELATIONS, "key region modulation");
1091
1147
  }
1092
1148
  if (region.pivot !== void 0) {
1093
1149
  assertDataObject(region.pivot, "key region pivot");
1094
1150
  copy.pivot = {
1095
1151
  chord: new Chord(region.pivot.chord).toJSON(),
1096
- romanFrom: region.pivot.romanFrom,
1097
- romanTo: region.pivot.romanTo
1152
+ romanFrom: assertNumeral(region.pivot.romanFrom, "key region pivot romanFrom"),
1153
+ romanTo: assertNumeral(region.pivot.romanTo, "key region pivot romanTo")
1098
1154
  };
1099
1155
  }
1100
1156
  return copy;
@@ -1113,6 +1169,10 @@ function copyConfidence(confidence, segments) {
1113
1169
  (value, index) => assertRange(value, 0, 1, `timeline segmentConfidence[${index}]`)
1114
1170
  );
1115
1171
  }
1172
+ function validateTimeline(segments, keys) {
1173
+ assertSpansInOrder(segments, "timeline segments");
1174
+ assertSpansInOrder(keys, "timeline keys");
1175
+ }
1116
1176
  function clipSpan(span, fromBeat, toBeat) {
1117
1177
  const startBeat = Math.max(span.startBeat, fromBeat);
1118
1178
  const endBeat = Math.min(span.endBeat, toBeat);
@@ -1128,8 +1188,9 @@ var Timeline = class _Timeline {
1128
1188
  *
1129
1189
  * @param data The segments and the span they cover; copied, never retained.
1130
1190
  * @throws If the span, a segment, or a key region carries a number it cannot
1131
- * hold — a beat that is not finite, a mode mask that names no scale — or if
1132
- * 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.
1133
1194
  */
1134
1195
  constructor(data) {
1135
1196
  assertDataObject(data, "timeline data");
@@ -1148,6 +1209,7 @@ var Timeline = class _Timeline {
1148
1209
  this.#segmentConfidence = Object.freeze(
1149
1210
  copyConfidence(data.segmentConfidence, this.#segments.length)
1150
1211
  );
1212
+ validateTimeline(this.#segments, this.#keys);
1151
1213
  }
1152
1214
  /**
1153
1215
  * Build a timeline from placed chords.
@@ -1155,7 +1217,8 @@ var Timeline = class _Timeline {
1155
1217
  * @param spans The chords with their onsets, in any order.
1156
1218
  * @param totalBeats Where the last chord stops sounding.
1157
1219
  * @param key The key the chords are read in, when it is already known. It
1158
- * 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
1159
1222
  * carries no key, and the members that need one say so.
1160
1223
  * @returns The timeline.
1161
1224
  */
@@ -1163,16 +1226,9 @@ var Timeline = class _Timeline {
1163
1226
  const timeline = chordTimelineFromChords(spans, totalBeats);
1164
1227
  const data = { segments: timeline.segments, totalBeats };
1165
1228
  if (key !== void 0) {
1166
- data.keys = [
1167
- {
1168
- // The region starts where the music does, as an inferred one does,
1169
- // so a chord placed in a pickup is covered by the key it sounds in.
1170
- startBeat: timeline.segments[0]?.startBeat ?? 0,
1171
- endBeat: totalBeats,
1172
- key: keyIdentity(toKey(key)),
1173
- confidence: STATED_KEY_CONFIDENCE
1174
- }
1175
- ];
1229
+ const stated = keyIdentity(toKey(key));
1230
+ const first = timeline.segments[0];
1231
+ data.keys = first === void 0 ? [] : statedKeyRegions(stated, first.startBeat, totalBeats);
1176
1232
  }
1177
1233
  return new _Timeline(data);
1178
1234
  }
@@ -1203,11 +1259,25 @@ var Timeline = class _Timeline {
1203
1259
  * @throws If `beatsEach` is not a positive finite number of beats.
1204
1260
  */
1205
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
+ }
1206
1268
  assertRange(beatsEach, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "timeline beatsEach");
1207
- const chords = progression.chords;
1208
- 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
+ });
1209
1279
  const totalBeats = chords.length * beatsEach;
1210
- const key = progression.key;
1280
+ const key = read.key;
1211
1281
  return key === void 0 ? _Timeline.fromChords(spans, totalBeats) : _Timeline.fromChords(spans, totalBeats, key);
1212
1282
  }
1213
1283
  /** Rebuild a timeline from the plain data {@link Timeline.data} hands out. */
@@ -1599,7 +1669,14 @@ var Progression = class _Progression {
1599
1669
  */
1600
1670
  static #attachKey(chords, key) {
1601
1671
  const given = assertDataObjects(chords, "progression chords");
1602
- 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
+ });
1603
1680
  }
1604
1681
  /**
1605
1682
  * Start from a chord sequence, matching the `of` factory on the other
@@ -1647,19 +1724,34 @@ var Progression = class _Progression {
1647
1724
  return new _Progression(chords, key);
1648
1725
  }
1649
1726
  /**
1650
- * 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.
1651
1729
  *
1652
- * @param data The serialized chords and key.
1730
+ * @param data The serialized chords and keys.
1653
1731
  * @returns The progression.
1732
+ * @throws If `chordKeys` does not hold one entry per chord.
1654
1733
  */
1655
1734
  static fromJSON(data) {
1656
1735
  assertDataObject(data, "progression data");
1657
1736
  const key = data.key === void 0 ? void 0 : Key.fromJSON(data.key);
1658
- const chords = assertDataArray(data.chords, "progression chords").map(
1659
- (chord) => Chord.fromJSON(chord)
1660
- );
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
+ });
1661
1743
  return key === void 0 ? new _Progression(chords) : new _Progression(chords, key);
1662
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
+ }
1663
1755
  /**
1664
1756
  * Wrap plain progression data, matching the `fromData` factory on the other
1665
1757
  * classes.
@@ -1870,10 +1962,8 @@ var Progression = class _Progression {
1870
1962
  */
1871
1963
  voice(opts) {
1872
1964
  const key = opts?.key ?? this.#key;
1873
- return voiceProgression(
1874
- this.#chords.map((chord) => chord.data),
1875
- key === void 0 ? opts : { ...opts, key }
1876
- );
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 });
1877
1967
  }
1878
1968
  /**
1879
1969
  * The Roman numeral of each chord in a key.
@@ -1934,12 +2024,13 @@ var Progression = class _Progression {
1934
2024
  const resolved = this.#resolveKey(key);
1935
2025
  const chords = this.#chords.map((chord) => chord.analyze(resolved, opts));
1936
2026
  const { voicing, ...cadenceOpts } = opts ?? {};
1937
- const from = this.#chords[this.#chords.length - 2];
1938
- const to = this.#chords[this.#chords.length - 1];
1939
- const approach = this.#chords[this.#chords.length - 3];
1940
- 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, {
1941
2032
  ...cadenceOpts,
1942
- ...approach === void 0 ? {} : { approach: approach.data },
2033
+ ...approach === void 0 ? {} : { approach },
1943
2034
  ...voicing === void 0 ? {} : { voicing: this.#closingVoicing(voicing) }
1944
2035
  }) : null;
1945
2036
  return { chords, cadence };
@@ -2004,17 +2095,18 @@ var Progression = class _Progression {
2004
2095
  */
2005
2096
  cadences(key, opts) {
2006
2097
  const resolved = this.#resolveKey(key);
2098
+ const views = this.#viewsIn(resolved);
2007
2099
  const found = [];
2008
- for (let index = 1; index < this.#chords.length; index += 1) {
2009
- const from = this.#chords[index - 1];
2010
- 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];
2011
2103
  if (from === void 0 || to === void 0) {
2012
2104
  continue;
2013
2105
  }
2014
- const approach = index >= 2 ? this.#chords[index - 2]?.data : opts?.approach;
2106
+ const approach = index >= 2 ? views[index - 2] : opts?.approach;
2015
2107
  const pair = opts?.voicing;
2016
2108
  found.push(
2017
- detectCadence(from.data, to.data, resolved, {
2109
+ detectCadence(from, to, resolved, {
2018
2110
  ...opts?.alternatives === void 0 ? {} : { alternatives: opts.alternatives },
2019
2111
  ...approach === void 0 ? {} : { approach },
2020
2112
  ...pair === void 0 ? {} : { voicing: [pair[index - 1] ?? [], pair[index] ?? []] }
@@ -2175,16 +2267,26 @@ var Progression = class _Progression {
2175
2267
  /**
2176
2268
  * The plain progression data, for JSON serialization.
2177
2269
  *
2178
- * Private class fields do not serialize, so this preserves both the chord
2179
- * 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)`.
2180
2273
  *
2181
- * @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.
2182
2276
  */
2183
2277
  toJSON() {
2184
- return {
2278
+ const out = {
2185
2279
  chords: this.#chords.map((chord) => chord.toJSON()),
2186
2280
  key: this.#key?.toJSON()
2187
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);
2188
2290
  }
2189
2291
  /**
2190
2292
  * Resolve the key for an analysis method: explicit first, then carried.
@@ -2617,7 +2719,8 @@ var Chord = class _Chord {
2617
2719
  * @throws If no key is given and none is carried.
2618
2720
  */
2619
2721
  roman(key, opts) {
2620
- return chordToRoman(this.#data, this.#resolveKey(key), opts);
2722
+ const resolved = this.#resolveKey(key);
2723
+ return chordToRoman(this.#dataWithKey(resolved), resolved, opts);
2621
2724
  }
2622
2725
  /**
2623
2726
  * The chord's Roman numeral together with the reasoning behind it: the degree
@@ -2645,7 +2748,8 @@ var Chord = class _Chord {
2645
2748
  * ```
2646
2749
  */
2647
2750
  explain(key, opts) {
2648
- return explainRoman(this.#data, this.#resolveKey(key), opts);
2751
+ const resolved = this.#resolveKey(key);
2752
+ return explainRoman(this.#dataWithKey(resolved), resolved, opts);
2649
2753
  }
2650
2754
  /**
2651
2755
  * The chord's harmonic function (tonic / subdominant / dominant) in a key.
@@ -2656,7 +2760,8 @@ var Chord = class _Chord {
2656
2760
  * @throws If no key is given and none is carried.
2657
2761
  */
2658
2762
  function(key) {
2659
- return functionOf(this.#data, this.#resolveKey(key));
2763
+ const resolved = this.#resolveKey(key);
2764
+ return functionOf(this.#dataWithKey(resolved), resolved);
2660
2765
  }
2661
2766
  /**
2662
2767
  * Full functional analysis: function, borrowing, Roman numeral, and the
@@ -2676,7 +2781,8 @@ var Chord = class _Chord {
2676
2781
  * ```
2677
2782
  */
2678
2783
  analyze(key, opts) {
2679
- return analyzeChord(this.#data, this.#resolveKey(key), opts);
2784
+ const resolved = this.#resolveKey(key);
2785
+ return analyzeChord(this.#dataWithKey(resolved), resolved, opts);
2680
2786
  }
2681
2787
  /**
2682
2788
  * Whether the chord is borrowed from the parallel mode (modal interchange).
@@ -2687,7 +2793,8 @@ var Chord = class _Chord {
2687
2793
  * @throws If no key is given and none is carried.
2688
2794
  */
2689
2795
  isBorrowed(key) {
2690
- return isBorrowedChord(this.#data, this.#resolveKey(key));
2796
+ const resolved = this.#resolveKey(key);
2797
+ return isBorrowedChord(this.#dataWithKey(resolved), resolved);
2691
2798
  }
2692
2799
  /**
2693
2800
  * The origin of a non-diatonic chord (parallel mode or Neapolitan), or null.
@@ -2698,7 +2805,8 @@ var Chord = class _Chord {
2698
2805
  * @throws If no key is given and none is carried.
2699
2806
  */
2700
2807
  borrowedSource(key) {
2701
- return borrowedSource(this.#data, this.#resolveKey(key));
2808
+ const resolved = this.#resolveKey(key);
2809
+ return borrowedSource(this.#dataWithKey(resolved), resolved);
2702
2810
  }
2703
2811
  /**
2704
2812
  * The chord written as the figures a bass would carry under it: `6`, `64`,
@@ -2723,7 +2831,8 @@ var Chord = class _Chord {
2723
2831
  * ```
2724
2832
  */
2725
2833
  figuredBass(key) {
2726
- return figuredBassOf(this.#data, this.#resolveKey(key));
2834
+ const resolved = this.#resolveKey(key);
2835
+ return figuredBassOf(this.#dataWithKey(resolved), resolved);
2727
2836
  }
2728
2837
  /**
2729
2838
  * The chords that can stand in for this one in a key: its tritone substitute,
@@ -2750,7 +2859,8 @@ var Chord = class _Chord {
2750
2859
  * ```
2751
2860
  */
2752
2861
  substitutions(key, opts) {
2753
- return substituteChord(this.#data, this.#resolveKey(key), opts);
2862
+ const resolved = this.#resolveKey(key);
2863
+ return substituteChord(this.#dataWithKey(resolved), resolved, opts);
2754
2864
  }
2755
2865
  /**
2756
2866
  * The modal-interchange palette of the chord's key: the parallel mode's
@@ -2804,7 +2914,8 @@ var Chord = class _Chord {
2804
2914
  */
2805
2915
  voice(opts) {
2806
2916
  const key = opts?.key ?? this.#key;
2807
- 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 });
2808
2919
  }
2809
2920
  /**
2810
2921
  * Realize the chord as a single styled voicing (`close`, `drop2`, `drop3`,
@@ -2827,7 +2938,7 @@ var Chord = class _Chord {
2827
2938
  */
2828
2939
  negativeHarmony(key) {
2829
2940
  const resolved = this.#resolveKey(key);
2830
- return new _Chord(negativeHarmonyMirror(this.#data, resolved), resolved);
2941
+ return new _Chord(negativeHarmonyMirror(this.#dataWithKey(resolved), resolved), resolved);
2831
2942
  }
2832
2943
  /**
2833
2944
  * The V7 that tonicizes this chord: a dominant seventh a perfect fifth above
@@ -3217,17 +3328,18 @@ var Key = class _Key {
3217
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`
3218
3329
  );
3219
3330
  }
3331
+ const note = tonic instanceof Note ? tonic : new Note(tonic.data);
3220
3332
  const rootPc = pitchClassOf(scale.rootPc);
3221
- if (tonic.pitchClass !== rootPc) {
3333
+ if (note.pitchClass !== rootPc) {
3222
3334
  throw new InvalidInputError(
3223
- `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`
3224
3336
  );
3225
3337
  }
3226
3338
  if (variant !== void 0) {
3227
3339
  assertKeyVariant(variant, scale.modeMask12);
3228
3340
  }
3229
3341
  this.#scale = { rootPc, modeMask12: scale.modeMask12 };
3230
- this.#tonic = tonic;
3342
+ this.#tonic = note;
3231
3343
  this.#variant = variant ?? variantOfMask(scale.modeMask12);
3232
3344
  }
3233
3345
  /** Preserve detection metadata while replacing its plain key with this API's Key. */
@@ -3533,7 +3645,7 @@ var Key = class _Key {
3533
3645
  get data() {
3534
3646
  return this.toJSON();
3535
3647
  }
3536
- /** 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. */
3537
3649
  get variant() {
3538
3650
  return this.#variant;
3539
3651
  }
@@ -3778,7 +3890,7 @@ var Key = class _Key {
3778
3890
  */
3779
3891
  pivotsTo(other) {
3780
3892
  assertKeyArgument(other, "key");
3781
- return pivotChords(this.#scale, other).map(({ chord, romanFrom, romanTo }) => ({
3893
+ return pivotChords(this, resolveKey(other)).map(({ chord, romanFrom, romanTo }) => ({
3782
3894
  chord: new Chord(chord, this),
3783
3895
  romanFrom,
3784
3896
  romanTo
@@ -3887,7 +3999,7 @@ var Key = class _Key {
3887
3999
  if (this.pitchClasses().length !== HEPTATONIC_DEGREES) {
3888
4000
  return this.isMinor ? "minor" : "major";
3889
4001
  }
3890
- const { quality } = diatonicTriad(n, this.#scale);
4002
+ const { quality } = diatonicTriad(n, this);
3891
4003
  return quality === "min" || quality === "dim" ? "minor" : "major";
3892
4004
  }
3893
4005
  /**
@@ -3896,7 +4008,7 @@ var Key = class _Key {
3896
4008
  *
3897
4009
  * Unlike {@link Key.transpose}, which picks a letter from the semitone count,
3898
4010
  * the interval's diatonic number decides the tonic's letter: G# minor down an
3899
- * 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.
3900
4012
  * The mode mask is unchanged, so the scale keeps its shape.
3901
4013
  *
3902
4014
  * @param interval The interval to apply, as a name (`'A4'`, `'-A4'`), plain
@@ -4109,6 +4221,15 @@ var Key = class _Key {
4109
4221
  const anchor = tonic === void 0 ? this.#tonic.data : toNoteData(tonic);
4110
4222
  return spellPitchClasses([...pcs], anchor, this.#scale).map((note) => new Note(note));
4111
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
+ }
4112
4233
  /**
4113
4234
  * Build a chord on a scale degree, carrying this key as context.
4114
4235
  *
@@ -4126,8 +4247,8 @@ var Key = class _Key {
4126
4247
  * an explicit `quality` for those.
4127
4248
  */
4128
4249
  chord(degree, quality) {
4129
- const data = quality === void 0 ? diatonicTriad(degree, this.#scale) : chordFromDegree(degree, quality, this.#scale);
4130
- 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);
4131
4252
  }
4132
4253
  /**
4133
4254
  * The diatonic triad on a scale degree, carrying this key as context.
@@ -4139,7 +4260,7 @@ var Key = class _Key {
4139
4260
  * degrees. Use {@link Key.chord} with an explicit quality instead.
4140
4261
  */
4141
4262
  diatonicTriad(degree) {
4142
- return new Chord(diatonicTriad(degree, this.#scale), this);
4263
+ return new Chord(this.#withoutSpelling(diatonicTriad(degree, this)), this);
4143
4264
  }
4144
4265
  /**
4145
4266
  * The diatonic seventh chord on a scale degree, carrying this key as context.
@@ -4151,7 +4272,7 @@ var Key = class _Key {
4151
4272
  * degrees. Use {@link Key.chord} with an explicit quality instead.
4152
4273
  */
4153
4274
  diatonicSeventh(degree) {
4154
- return new Chord(diatonicSeventh(degree, this.#scale), this);
4275
+ return new Chord(this.#withoutSpelling(diatonicSeventh(degree, this)), this);
4155
4276
  }
4156
4277
  /**
4157
4278
  * Build the chord denoted by a Roman numeral in this key (including applied
@@ -4261,12 +4382,11 @@ var Key = class _Key {
4261
4382
  *
4262
4383
  * Private class fields do not serialize, so an explicit `toJSON` keeps
4263
4384
  * `JSON.stringify(key)` from collapsing to `{}`. The result pairs the
4264
- * `KeyScale` with the spelled tonic and, for a detected harmonic or melodic
4265
- * minor, the scale form — everything {@link Key.fromJSON} needs to rebuild a
4266
- * 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.
4267
4388
  *
4268
- * @returns The key/scale, its spelled tonic, and its scale form when it has
4269
- * one.
4389
+ * @returns The key/scale, its spelled tonic, and its scale form.
4270
4390
  */
4271
4391
  toJSON() {
4272
4392
  return { scale: this.scale, tonic: this.#tonic.data, variant: this.#variant };
@@ -4377,21 +4497,9 @@ function tempoFrom(tempo) {
4377
4497
  }
4378
4498
  return typeof tempo === "number" ? [{ startBeat: 0, bpm: tempo }] : tempo;
4379
4499
  }
4380
- function statedKeyRegions(key, notes, totalBeats) {
4500
+ function scoreKeyRegions(key, notes, totalBeats) {
4381
4501
  const first = notes[0];
4382
- if (first === void 0 || totalBeats <= first.startBeat) {
4383
- return [];
4384
- }
4385
- return [
4386
- {
4387
- // The region starts where the music does, as an inferred one does, so a
4388
- // pickup is covered by the key it sounds in.
4389
- startBeat: first.startBeat,
4390
- endBeat: totalBeats,
4391
- key: keyIdentity(key),
4392
- confidence: STATED_KEY_CONFIDENCE
4393
- }
4394
- ];
4502
+ return first === void 0 ? [] : statedKeyRegions(keyIdentity(key), first.startBeat, totalBeats);
4395
4503
  }
4396
4504
  function scoreData(notes, opts) {
4397
4505
  const data = {
@@ -4411,6 +4519,8 @@ var Score = class _Score {
4411
4519
  #chords;
4412
4520
  /** The key regions, made on the first member that needs them. */
4413
4521
  #regions;
4522
+ /** The reference profile asked for with no options, made on first read. */
4523
+ #profile;
4414
4524
  /**
4415
4525
  * Wrap plain score data.
4416
4526
  *
@@ -4644,43 +4754,42 @@ var Score = class _Score {
4644
4754
  /**
4645
4755
  * The notes pushed off the grid the way a player would.
4646
4756
  *
4647
- * The metric accents are read from the score's opening signature, since
4648
- * {@link HumanizeOptions} names one signature rather than a map; pass `ts`
4649
- * 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.
4650
4759
  *
4651
4760
  * @param opts Jitter amounts, accent depth and the seed; see
4652
4761
  * {@link HumanizeOptions}.
4653
4762
  * @returns The humanized score.
4654
4763
  */
4655
4764
  humanize(opts) {
4656
- 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 }));
4657
4766
  }
4658
4767
  /**
4659
4768
  * The notes with ornaments added.
4660
4769
  *
4661
- * Strong positions are read from the score's opening signature, since
4662
- * {@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.
4663
4772
  *
4664
4773
  * @param opts The ornament style, how much is decorated, and the seed; see
4665
4774
  * {@link OrnamentOptions}.
4666
4775
  * @returns The ornamented score.
4667
4776
  */
4668
4777
  ornament(opts) {
4669
- 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 }));
4670
4779
  }
4671
4780
  /**
4672
4781
  * The notes placed on a groove template's slots.
4673
4782
  *
4674
- * The template's per-bar grid is laid out against the score's opening
4675
- * signature, which is the signature the template must have been extracted
4676
- * 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.
4677
4786
  *
4678
4787
  * @param template The groove template, from `extractGrooveTemplate`.
4679
4788
  * @returns The score with the template's feel imposed.
4680
4789
  * @throws If the template was extracted under another meter.
4681
4790
  */
4682
4791
  groove(template) {
4683
- return this.#withNotes(applyGrooveTemplate(this.#data.notes, template, this.meterAt(0)));
4792
+ return this.#withNotes(applyGrooveTemplate(this.#data.notes, template, this.#data.meters));
4684
4793
  }
4685
4794
  /**
4686
4795
  * The feel of the score's own timing, as a template another score can take on.
@@ -4689,8 +4798,8 @@ var Score = class _Score {
4689
4798
  * slot by slot for how far ahead of or behind the grid it sits and how hard
4690
4799
  * it is struck, and the reading is handed back as a template. Only the
4691
4800
  * subdivision is named, because the score already holds the meter the grid is
4692
- * laid out against — its opening signature, which is the one the template
4693
- * 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.
4694
4803
  *
4695
4804
  * @param subdivision Grid steps per quarter-note beat; a sixteenth-note grid
4696
4805
  * by default, which is where swing, push and drag are audible.
@@ -4704,7 +4813,7 @@ var Score = class _Score {
4704
4813
  * ```
4705
4814
  */
4706
4815
  grooveTemplate(subdivision) {
4707
- return extractGrooveTemplate(this.#data.notes, this.meterAt(0), subdivision);
4816
+ return extractGrooveTemplate(this.#data.notes, this.#data.meters, subdivision);
4708
4817
  }
4709
4818
  /**
4710
4819
  * The harmony the notes spell out, in time.
@@ -4758,7 +4867,8 @@ var Score = class _Score {
4758
4867
  keys(opts) {
4759
4868
  if (opts !== void 0) {
4760
4869
  if (this.#key !== void 0) {
4761
- 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);
4762
4872
  }
4763
4873
  return keyTimelineFromNotes(this.#data.notes, { meters: this.#data.meters, ...opts });
4764
4874
  }
@@ -4842,10 +4952,16 @@ var Score = class _Score {
4842
4952
  * @returns The grouping, its downbeats, a confidence, and a rationale.
4843
4953
  */
4844
4954
  hypermeter(opts) {
4845
- return hypermeter(this.#data.notes, this.#data.meters, {
4846
- cadenceBeats: this.#cadenceBeats(),
4847
- ...opts
4848
- });
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
+ );
4849
4965
  }
4850
4966
  /**
4851
4967
  * The motifs the melody repeats.
@@ -4860,8 +4976,9 @@ var Score = class _Score {
4860
4976
  *
4861
4977
  * A melody is one line, so a score holding several is read as its top voice:
4862
4978
  * notes struck together are one event and the highest of them stands for it.
4863
- * A piano part read whole therefore answers for the voice a listener follows
4864
- * 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
4865
4982
  * score into lines first to have each of them answered for.
4866
4983
  *
4867
4984
  * @param opts Cell-length bounds and the recurrence threshold; see
@@ -4898,6 +5015,143 @@ var Score = class _Score {
4898
5015
  contour() {
4899
5016
  return melodicContour(this.#data.notes);
4900
5017
  }
5018
+ /**
5019
+ * The score's structural fingerprint: form, phrase shape, harmonic function
5020
+ * and rhythm, and how its motifs derive from one another.
5021
+ *
5022
+ * The harmony is the score's own kept chord analysis, read once whether this
5023
+ * is asked before or after {@link Score.timeline} — unless `opts.timeline`
5024
+ * gives the harmony directly, in which case the reading runs fresh through
5025
+ * {@link analyzeReference} rather than the harmony this score already holds.
5026
+ * The meter, the key and the span are the score's own and are not on offer
5027
+ * here: a caller wanting another meter reads through {@link
5028
+ * Score.withMeters}, and one wanting another key or span calls {@link
5029
+ * analyzeReference} directly.
5030
+ *
5031
+ * Asked with no options, the answer is kept and a copy of it handed back, as
5032
+ * {@link Score.timeline} keeps its own reading; asked with options, the
5033
+ * answer is read afresh and not kept.
5034
+ *
5035
+ * @param opts The harmony, the melody, and the budget; see {@link
5036
+ * ReferenceProfileOptions}. Meter, key and span are the score's own.
5037
+ * @returns The profile.
5038
+ * @example
5039
+ * ```ts
5040
+ * import { Score } from '@libraz/libcantus';
5041
+ * const triad = (pitches: number[], startBeat: number) =>
5042
+ * pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
5043
+ * const score = Score.of(
5044
+ * [
5045
+ * ...triad([60, 64, 67], 0),
5046
+ * ...triad([65, 69, 72], 4),
5047
+ * ...triad([67, 71, 74], 8),
5048
+ * ...triad([60, 64, 67], 12),
5049
+ * ],
5050
+ * { key: 'C major' },
5051
+ * );
5052
+ * score.reference().harmony.chords.map((chord) => chord.roman); // ['I', 'IV', 'V', 'I']
5053
+ * ```
5054
+ */
5055
+ reference(opts) {
5056
+ if (opts === void 0 && this.#profile !== void 0) {
5057
+ return copyPlain(this.#profile, "reference profile");
5058
+ }
5059
+ const keyed = this.#key === void 0 ? {} : { key: keyIdentity(this.#key) };
5060
+ const asked = {
5061
+ meters: this.#data.meters,
5062
+ totalBeats: this.totalBeats,
5063
+ ...keyed,
5064
+ ...opts
5065
+ };
5066
+ const profile = opts?.timeline === void 0 ? referenceFromReadings(this.#data.notes, this.#chordAnalysis(), asked) : analyzeReference(this.#data.notes, asked);
5067
+ if (opts === void 0) {
5068
+ this.#profile = profile;
5069
+ return copyPlain(profile, "reference profile");
5070
+ }
5071
+ return profile;
5072
+ }
5073
+ /**
5074
+ * The onset placement of the score's melodic line.
5075
+ *
5076
+ * A melody is one line, so a score holding several is read as its top voice,
5077
+ * as {@link Score.motifs} and {@link Score.contour} are.
5078
+ *
5079
+ * @param opts The span's end and the budget; see {@link
5080
+ * RhythmAnalysisOptions}. The meter is the score's own.
5081
+ * @returns The rhythm reading.
5082
+ * @example
5083
+ * ```ts
5084
+ * import { Score } from '@libraz/libcantus';
5085
+ * const score = Score.of([
5086
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
5087
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
5088
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
5089
+ * { pitch: 65, startBeat: 3, durationBeat: 1 },
5090
+ * ]);
5091
+ * score.rhythm().onsetDensity; // 4
5092
+ * ```
5093
+ */
5094
+ rhythm(opts) {
5095
+ return analyzeRhythm(this.#data.notes, {
5096
+ meters: this.#data.meters,
5097
+ totalBeats: this.totalBeats,
5098
+ ...opts
5099
+ });
5100
+ }
5101
+ /**
5102
+ * How the score's motifs derive from one another: which statement grew out
5103
+ * of which, and by what transformation.
5104
+ *
5105
+ * {@link Score.motifs} finds the cells; this reads across their occurrences
5106
+ * and names, for every statement after the first, the earlier one it most
5107
+ * plausibly grew out of. A melody is one line, so a score holding several is
5108
+ * read as its top voice, as {@link Score.motifs} is.
5109
+ *
5110
+ * @param opts Cell-length bounds and the recurrence threshold {@link
5111
+ * Score.motifs} takes, plus the variation threshold and the budget; see
5112
+ * {@link MotifGraphOptions} and {@link ExtractMotifsOptions}.
5113
+ * @returns The derivation forest.
5114
+ * @example
5115
+ * ```ts
5116
+ * import { Score } from '@libraz/libcantus';
5117
+ * const score = Score.of([
5118
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
5119
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
5120
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
5121
+ * { pitch: 60, startBeat: 3, durationBeat: 1 },
5122
+ * { pitch: 62, startBeat: 4, durationBeat: 1 },
5123
+ * { pitch: 64, startBeat: 5, durationBeat: 1 },
5124
+ * ]);
5125
+ * score.motifGraph().edges.length; // 1
5126
+ * ```
5127
+ */
5128
+ motifGraph(opts) {
5129
+ return motifGraph(this.#data.notes, extractMotifs(this.#data.notes, opts), {
5130
+ key: this.key(),
5131
+ ...opts
5132
+ });
5133
+ }
5134
+ /**
5135
+ * How this score, read as a melody, keeps to a composition plan.
5136
+ *
5137
+ * @param plan The plan the melody was written for.
5138
+ * @param opts The budget; see {@link EvaluateCompositionOptions}.
5139
+ * @returns The violations found and the fit reading; see {@link
5140
+ * evaluateComposition}.
5141
+ * @example
5142
+ * ```ts
5143
+ * import { Composer, Score } from '@libraz/libcantus';
5144
+ * const notes = [60, 62, 64, 65, 67, 65, 64, 62, 60, 62, 64, 65, 67, 65, 64, 60].map(
5145
+ * (pitch, startBeat) => ({ pitch, startBeat, durationBeat: 1 }),
5146
+ * );
5147
+ * const composer = Composer.of({ key: 'C major', seed: 5 });
5148
+ * const plan = composer.plan(Score.of(notes, { key: 'C major' }).reference());
5149
+ * composer.melody(plan).evaluate(plan).violations.some((v) => v.severity === 'error'); // false
5150
+ * ```
5151
+ */
5152
+ evaluate(plan, opts) {
5153
+ return evaluateComposition(this.#data.notes, plan, opts);
5154
+ }
4901
5155
  /**
4902
5156
  * Each note's role in the harmony sounding under it.
4903
5157
  *
@@ -5125,15 +5379,24 @@ var Score = class _Score {
5125
5379
  * Kept on first reading for the same reason the chord timeline is.
5126
5380
  */
5127
5381
  #keyRegions() {
5128
- 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));
5129
5383
  this.#regions = regions;
5130
5384
  return regions;
5131
5385
  }
5132
- /** Where the score's own harmony closes, as beats. */
5133
- #cadenceBeats() {
5134
- return detectCadences(this.#chordTimeline(), this.#keyContext(void 0)).map(
5135
- (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
5136
5398
  );
5399
+ return boundaryCadences(timeline, key).spans;
5137
5400
  }
5138
5401
  /** The score's context as chord-timeline options, with the caller's on top. */
5139
5402
  #analysisOptions(opts) {
@@ -5209,7 +5472,7 @@ function copySegments(segments) {
5209
5472
  function chordTimelineOf(segments) {
5210
5473
  return new Timeline({ segments: [...segments], totalBeats: spanEnd(segments) }).chordTimeline;
5211
5474
  }
5212
- function copySettings(settings, trackCount) {
5475
+ function copySettings(settings, tracks) {
5213
5476
  if (settings === void 0) {
5214
5477
  return void 0;
5215
5478
  }
@@ -5236,8 +5499,9 @@ function copySettings(settings, trackCount) {
5236
5499
  settings.harmonyTracks,
5237
5500
  "arrangement harmonyTracks"
5238
5501
  ).map(
5239
- (track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0, trackCount - 1)
5502
+ (track, index) => assertInteger(track, `arrangement harmonyTracks[${index}]`, 0, tracks.length - 1)
5240
5503
  );
5504
+ harmonyTrackSet(copy.harmonyTracks, tracks, "arrangement harmonyTracks");
5241
5505
  }
5242
5506
  if (settings.minSeverity !== void 0) {
5243
5507
  copy.minSeverity = assertInteger(
@@ -5254,6 +5518,11 @@ function copySettings(settings, trackCount) {
5254
5518
  Number.MAX_SAFE_INTEGER,
5255
5519
  "arrangement pickupBeats"
5256
5520
  );
5521
+ assertTrackNotes(tracks, {
5522
+ allowNonPositiveDuration: true,
5523
+ minStartBeat: -copy.pickupBeats,
5524
+ budget: settings.budget
5525
+ });
5257
5526
  }
5258
5527
  if (settings.harmonicRhythm !== void 0) {
5259
5528
  copy.harmonicRhythm = assertRange(
@@ -5264,7 +5533,7 @@ function copySettings(settings, trackCount) {
5264
5533
  );
5265
5534
  }
5266
5535
  if (settings.profile !== void 0) {
5267
- copy.profile = settings.profile;
5536
+ copy.profile = arrangementProfile(settings.profile);
5268
5537
  }
5269
5538
  if (settings.budget !== void 0) {
5270
5539
  copy.budget = assertPositiveInt(settings.budget, "arrangement budget", Number.MAX_SAFE_INTEGER);
@@ -5277,7 +5546,7 @@ function copyArrangement(data) {
5277
5546
  const tracks = assertDataArray(data.tracks, "arrangement tracks").map(
5278
5547
  (track, index) => copyTrack(track, index, budget)
5279
5548
  );
5280
- const settings = copySettings(data.settings, tracks.length);
5549
+ const settings = copySettings(data.settings, tracks);
5281
5550
  return settings === void 0 ? { tracks } : { tracks, settings };
5282
5551
  }
5283
5552
  function arrangementData(tracks, settings) {
@@ -5532,6 +5801,71 @@ var Arrangement = class _Arrangement {
5532
5801
  keys: [...keys]
5533
5802
  });
5534
5803
  }
5804
+ /**
5805
+ * The arrangement's structural fingerprint: form, phrase shape, harmonic
5806
+ * function and rhythm, and how its melody's motifs derive from one another.
5807
+ *
5808
+ * The harmony reads the arrangement's own session analysis directly — the
5809
+ * tracks `settings.harmonyTracks` names, or else every track but the
5810
+ * percussion ones — so the harmony is never inferred a second time. The
5811
+ * melody is the notes of the tracks played with `role: 'melody'`, joined in
5812
+ * the order the tracks were given; an arrangement naming none reads the top
5813
+ * line of the harmony notes instead, as {@link analyzeReference} does
5814
+ * without a `melody` option. Meter, key, harmony and melody are the
5815
+ * arrangement's own and are not on offer here: a caller wanting others calls
5816
+ * {@link analyzeReference} directly, with the same notes {@link
5817
+ * Arrangement.tracks} hands out.
5818
+ *
5819
+ * @param opts The budget; see {@link ReferenceProfileOptions}.
5820
+ * @returns The profile.
5821
+ * @example
5822
+ * ```ts
5823
+ * import { Arrangement } from '@libraz/libcantus';
5824
+ * const triad = (pitches: number[], startBeat: number) =>
5825
+ * pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
5826
+ * const arrangement = Arrangement.of(
5827
+ * [
5828
+ * {
5829
+ * name: 'lead',
5830
+ * role: 'melody',
5831
+ * notes: [
5832
+ * ...triad([60, 64, 67], 0),
5833
+ * ...triad([65, 69, 72], 4),
5834
+ * ...triad([67, 71, 74], 8),
5835
+ * ...triad([60, 64, 67], 12),
5836
+ * ],
5837
+ * },
5838
+ * ],
5839
+ * { key: 'C major' },
5840
+ * );
5841
+ * arrangement.reference().harmony.chords.map((chord) => chord.roman); // ['I', 'IV', 'V', 'I']
5842
+ * ```
5843
+ */
5844
+ reference(opts) {
5845
+ const tracks = this.#data.tracks;
5846
+ const harmonyTracks = this.#data.settings?.harmonyTracks;
5847
+ const harmony = [];
5848
+ const melody = [];
5849
+ for (let index = 0; index < tracks.length; index += 1) {
5850
+ const track = tracks[index];
5851
+ if (track === void 0) {
5852
+ continue;
5853
+ }
5854
+ if (track.role !== "drums" && (harmonyTracks === void 0 || harmonyTracks.includes(index))) {
5855
+ harmony.push(...track.notes);
5856
+ }
5857
+ if (track.role === "melody") {
5858
+ melody.push(...track.notes);
5859
+ }
5860
+ }
5861
+ return referenceFromReadings(harmony, this.#session().analysis, {
5862
+ // The meter the arrangement's own analysis reads, `ts` included.
5863
+ meters: this.#data.settings?.meters ?? this.#data.settings?.ts,
5864
+ totalBeats: this.timeline().totalBeats,
5865
+ ...melody.length === 0 ? {} : { melody },
5866
+ ...opts
5867
+ });
5868
+ }
5535
5869
  /**
5536
5870
  * Whether another arrangement holds the same tracks under the same settings.
5537
5871
  *
@@ -5637,7 +5971,9 @@ function copyProfile(profile) {
5637
5971
  }
5638
5972
  const reach = {};
5639
5973
  for (const [pitch, limbs] of Object.entries(profile.reach)) {
5640
- reach[Number(pitch)] = [...limbs];
5974
+ if (String(Number(pitch)) === pitch) {
5975
+ reach[Number(pitch)] = [...limbs];
5976
+ }
5641
5977
  }
5642
5978
  const percussion = {
5643
5979
  kind: "percussion",
@@ -6149,7 +6485,7 @@ var Composer = class _Composer {
6149
6485
  ...instrument === void 0 ? {} : { instrument: toStringedProfile(instrument, "bass instrument") },
6150
6486
  segments,
6151
6487
  key,
6152
- ts: this.#openingMeter(),
6488
+ ts: this.#meters,
6153
6489
  ctx: this.context
6154
6490
  });
6155
6491
  return Score.of(notes, this.#scoreOptions(key));
@@ -6181,7 +6517,7 @@ var Composer = class _Composer {
6181
6517
  ...timeline === void 0 ? {} : { timeline: toChordTimeline(timeline) },
6182
6518
  melody: melody.notes,
6183
6519
  key,
6184
- ts: this.#openingMeter(),
6520
+ ts: this.#meters,
6185
6521
  ctx: this.context
6186
6522
  });
6187
6523
  return Score.of(notes, this.#scoreOptions(key));
@@ -6211,14 +6547,15 @@ var Composer = class _Composer {
6211
6547
  ...opts,
6212
6548
  melody: melody.notes,
6213
6549
  ...this.#key === void 0 ? {} : { key: this.#key },
6214
- ts: this.#openingMeter(),
6550
+ ts: this.#meters,
6215
6551
  ctx: this.context
6216
6552
  });
6217
6553
  const moved = melody.transpose(result.transposeSemitones);
6554
+ const last = result.chords[result.chords.length - 1];
6218
6555
  const totalBeats = harmonizedSpan(
6219
6556
  moved.totalBeats,
6220
6557
  result.chords,
6221
- beatsPerBar(this.#openingMeter())
6558
+ beatsPerBarAt(last?.startBeat ?? 0, this.#meters)
6222
6559
  );
6223
6560
  return {
6224
6561
  chords: Timeline.fromChords(result.chords, totalBeats, result.key),
@@ -6228,6 +6565,73 @@ var Composer = class _Composer {
6228
6565
  transposeSemitones: result.transposeSemitones
6229
6566
  };
6230
6567
  }
6568
+ /**
6569
+ * A composition plan derived from a reference profile.
6570
+ *
6571
+ * The composer's key is the plan's home key unless `opts.key` names another,
6572
+ * and its seed and algorithm version are the ones the plan records, so the
6573
+ * plan is written under the same recipe as the composer's other parts.
6574
+ *
6575
+ * @param reference The reference profile to derive from.
6576
+ * @param opts Everything {@link deriveCompositionPlan} takes but the context,
6577
+ * which is the composer's.
6578
+ * @returns The plan.
6579
+ * @throws If the composer draws from a caller-held `rng`: a plan records its
6580
+ * seed, and a source cannot be written into it.
6581
+ * @example
6582
+ * ```ts
6583
+ * import { Composer, Score } from '@libraz/libcantus';
6584
+ * const notes = [60, 62, 64, 65, 67, 65, 64, 62, 60, 62, 64, 65, 67, 65, 64, 60].map(
6585
+ * (pitch, startBeat) => ({ pitch, startBeat, durationBeat: 1 }),
6586
+ * );
6587
+ * const reference = Score.of(notes, { key: 'C major' }).reference();
6588
+ * const plan = Composer.of({ key: 'D major', seed: 5 }).plan(reference);
6589
+ * plan.seed; // 5
6590
+ * plan.keys[0].scale.rootPc; // 2
6591
+ * ```
6592
+ */
6593
+ plan(reference, opts) {
6594
+ const ctx = this.context;
6595
+ if (ctx.rng !== void 0) {
6596
+ throw new InvalidInputError(
6597
+ "composer plan cannot record a caller-held rng; give the composer a seed instead"
6598
+ );
6599
+ }
6600
+ return deriveCompositionPlan(reference, {
6601
+ ...this.#key === void 0 ? {} : { key: this.#key },
6602
+ ...opts,
6603
+ ctx
6604
+ });
6605
+ }
6606
+ /**
6607
+ * A melody written to a composition plan.
6608
+ *
6609
+ * The plan is the reproduction recipe, so its seed and algorithm version are
6610
+ * the ones the melody is drawn under, not the composer's. The score is read
6611
+ * in the plan's meter and home key, at the composer's tempo.
6612
+ *
6613
+ * @param plan The plan to follow.
6614
+ * @param opts The budget; see {@link MelodyOptions}.
6615
+ * @returns The melody, as a score.
6616
+ * @example
6617
+ * ```ts
6618
+ * import { Composer, Score } from '@libraz/libcantus';
6619
+ * const notes = [60, 62, 64, 65, 67, 65, 64, 62, 60, 62, 64, 65, 67, 65, 64, 60].map(
6620
+ * (pitch, startBeat) => ({ pitch, startBeat, durationBeat: 1 }),
6621
+ * );
6622
+ * const composer = Composer.of({ key: 'C major', seed: 5 });
6623
+ * const plan = composer.plan(Score.of(notes, { key: 'C major' }).reference());
6624
+ * composer.melody(plan).notes.length > 0; // true
6625
+ * ```
6626
+ */
6627
+ melody(plan, opts) {
6628
+ const notes = generateMelody(plan, opts);
6629
+ const score = { meters: plan.meters, key: plan.keys[0] };
6630
+ if (this.#options.bpm !== void 0) {
6631
+ score.tempo = this.#options.bpm;
6632
+ }
6633
+ return Score.of(notes, score);
6634
+ }
6231
6635
  /**
6232
6636
  * Whether another composer holds the same settings.
6233
6637
  *
@@ -6286,16 +6690,6 @@ var Composer = class _Composer {
6286
6690
  }
6287
6691
  return this.#key;
6288
6692
  }
6289
- /**
6290
- * The signature the parts are written against.
6291
- *
6292
- * Each generator names one time signature rather than a map, so a composer
6293
- * whose meter changes hands over the signature its music opens on — the same
6294
- * reading {@link Score.humanize} and its siblings take.
6295
- */
6296
- #openingMeter() {
6297
- return meterAt(0, this.#meters);
6298
- }
6299
6693
  /**
6300
6694
  * The one signature a part laid out in bars is written against.
6301
6695
  *
@@ -6310,7 +6704,7 @@ var Composer = class _Composer {
6310
6704
  `composer ${part} needs one meter for the whole part; the composer changes meter`
6311
6705
  );
6312
6706
  }
6313
- return this.#openingMeter();
6707
+ return meterAt(0, this.#meters);
6314
6708
  }
6315
6709
  /** The context every score a composer hands back is read against. */
6316
6710
  #scoreOptions(key) {
@@ -6807,6 +7201,7 @@ var Motif = class _Motif {
6807
7201
  * ```
6808
7202
  */
6809
7203
  static generate(opts) {
7204
+ assertRecord(opts, "motif options");
6810
7205
  return new _Motif(generateMotif({ ...opts, chord: chordFrom(opts.chord) }));
6811
7206
  }
6812
7207
  /**
@@ -6821,7 +7216,7 @@ var Motif = class _Motif {
6821
7216
  * @throws If a note carries a value the transforms cannot hold.
6822
7217
  */
6823
7218
  static fromNotes(notes) {
6824
- return new _Motif({ notes: [...notes] });
7219
+ return new _Motif({ notes: [...assertArray(notes, "motif notes")] });
6825
7220
  }
6826
7221
  /** Rebuild a motif from the plain data {@link Motif.data} hands out. */
6827
7222
  static fromData(cell) {
@@ -7140,7 +7535,7 @@ var Rhythm = class _Rhythm {
7140
7535
  * @returns The compressed pattern.
7141
7536
  */
7142
7537
  doubleTime() {
7143
- return this.#withGrid(doubleTime(toGrid(this.#data.events), this.#spanSteps()));
7538
+ return this.#withGrid(doubleTimeFigure(toGrid(this.#data.events), this.#spanSteps()));
7144
7539
  }
7145
7540
  /**
7146
7541
  * Stretch the pattern to twice its length: everything falls half as often.
@@ -7151,7 +7546,7 @@ var Rhythm = class _Rhythm {
7151
7546
  * @returns The stretched pattern.
7152
7547
  */
7153
7548
  halfTime() {
7154
- return this.#withGrid(halfTime(toGrid(this.#data.events), this.#spanSteps()));
7549
+ return this.#withGrid(halfTimeFigure(toGrid(this.#data.events), this.#spanSteps()));
7155
7550
  }
7156
7551
  /**
7157
7552
  * Keep as much decoration as the ornament dial asks for.
@@ -7198,14 +7593,16 @@ var Rhythm = class _Rhythm {
7198
7593
  rhythmic: resolved.rhythmic,
7199
7594
  ornament: resolved.ornament,
7200
7595
  rate: opts?.rate,
7201
- spanSteps: this.#spanSteps(),
7202
7596
  ts: this.#data.ts,
7203
7597
  // The transform hands the predicate the events it was given, which carry
7204
7598
  // the onset each was built from; the parameter is declared over the bare
7205
7599
  // grid event the rule reads, so the field is named back here.
7206
7600
  isOrnament: isOrnament === void 0 ? void 0 : (grid) => isOrnament(grid.source)
7207
7601
  };
7208
- 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
+ );
7209
7606
  }
7210
7607
  /**
7211
7608
  * Whether one player can sustain the pattern at the context's tempo.
@@ -7266,7 +7663,11 @@ var Rhythm = class _Rhythm {
7266
7663
  toJSON() {
7267
7664
  return this.data;
7268
7665
  }
7269
- /** 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
+ */
7270
7671
  #spanSteps() {
7271
7672
  return spanOf(this.#data.events) / STEP_BEATS;
7272
7673
  }
@@ -8250,4 +8651,4 @@ export {
8250
8651
  Tuning,
8251
8652
  Voicing
8252
8653
  };
8253
- //# sourceMappingURL=chunk-FBH75DAY.js.map
8654
+ //# sourceMappingURL=chunk-5MWRBYAT.js.map