@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
@@ -30,7 +30,6 @@
30
30
 
31
31
 
32
32
 
33
- var _chunkSGLEQVJHcjs = require('./chunk-SGLEQVJH.cjs');
34
33
 
35
34
 
36
35
 
@@ -41,7 +40,6 @@ var _chunkSGLEQVJHcjs = require('./chunk-SGLEQVJH.cjs');
41
40
 
42
41
 
43
42
 
44
- var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
45
43
 
46
44
 
47
45
 
@@ -60,17 +58,42 @@ var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
60
58
 
61
59
 
62
60
 
61
+ var _chunkLQZQDBCDcjs = require('./chunk-LQZQDBCD.cjs');
63
62
 
64
63
 
65
64
 
66
65
 
67
66
 
68
- var _chunkS5BKZZGMcjs = require('./chunk-S5BKZZGM.cjs');
67
+
68
+
69
+ var _chunkYDGMWH43cjs = require('./chunk-YDGMWH43.cjs');
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+ var _chunkS3QELJUEcjs = require('./chunk-S3QELJUE.cjs');
69
92
 
70
93
  // src/analyze/arrange/internal.ts
71
94
  var MAX_LANE_LEAP = 24;
72
95
  function covers(note, beat) {
73
- return beat >= note.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && beat < note.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS;
96
+ return beat >= note.startBeat - _chunkS3QELJUEcjs.BEAT_EPS && beat < note.endBeat - _chunkS3QELJUEcjs.BEAT_EPS;
74
97
  }
75
98
  function isPercussion(role) {
76
99
  return role === "drums";
@@ -84,31 +107,31 @@ var TRACK_ROLE_TABLE = {
84
107
  };
85
108
  var TRACK_ROLES = Object.keys(TRACK_ROLE_TABLE);
86
109
  function trackRoleOf(role, name) {
87
- return role === void 0 ? "other" : _chunkS5BKZZGMcjs.assertOneOf.call(void 0, role, TRACK_ROLES, name);
110
+ return role === void 0 ? "other" : _chunkS3QELJUEcjs.assertOneOf.call(void 0, role, TRACK_ROLES, name);
88
111
  }
89
112
  var DEFAULT_PROFILE = "pop";
90
- var SAFETY_PROFILES = Object.keys(_chunk3R3QVKY5cjs.PROFILE_WEIGHTS);
113
+ var SAFETY_PROFILES = Object.keys(_chunkYDGMWH43cjs.PROFILE_WEIGHTS);
91
114
  function arrangementProfile(profile) {
92
- return _chunkS5BKZZGMcjs.assertOneOf.call(void 0, _nullishCoalesce(profile, () => ( DEFAULT_PROFILE)), SAFETY_PROFILES, "arrangement profile");
115
+ return _chunkS3QELJUEcjs.assertOneOf.call(void 0, _nullishCoalesce(profile, () => ( DEFAULT_PROFILE)), SAFETY_PROFILES, "arrangement profile");
93
116
  }
94
117
  function assertTrackNotes(tracks, options) {
95
- _chunkS5BKZZGMcjs.assertArray.call(void 0, tracks, "arrangement tracks");
118
+ _chunkS3QELJUEcjs.assertArray.call(void 0, tracks, "arrangement tracks");
96
119
  let noteCount = 0;
97
120
  for (let index = 0; index < tracks.length; index += 1) {
98
121
  const notes = _optionalChain([tracks, 'access', _2 => _2[index], 'optionalAccess', _3 => _3.notes]);
99
122
  if (!Array.isArray(notes)) {
100
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
101
- `tracks[${index}].notes must be an array; received ${typeof notes}`
123
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
124
+ `tracks[${index}].notes must be an array; received ${_chunkS3QELJUEcjs.describeRejected.call(void 0, notes)}`
102
125
  );
103
126
  }
104
- _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, `tracks[${index}].notes`, options);
127
+ _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, `tracks[${index}].notes`, options);
105
128
  noteCount += notes.length;
106
129
  }
107
130
  return noteCount;
108
131
  }
109
132
  function assertGivenTimeline(timeline, name) {
110
133
  if (timeline !== void 0) {
111
- _chunkSGLEQVJHcjs.assertChordTimeline.call(void 0, timeline, name);
134
+ _chunkLQZQDBCDcjs.assertChordTimeline.call(void 0, timeline, name);
112
135
  }
113
136
  }
114
137
  function assertGivenKeys(keys, name) {
@@ -116,26 +139,27 @@ function assertGivenKeys(keys, name) {
116
139
  return;
117
140
  }
118
141
  if (!Array.isArray(keys)) {
119
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
120
- `${name} must be an array of key regions; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, keys)}`
142
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
143
+ `${name} must be an array of key regions; received ${_chunkS3QELJUEcjs.describeRejected.call(void 0, keys)}`
121
144
  );
122
145
  }
123
146
  for (let index = 0; index < keys.length; index += 1) {
124
147
  const region = keys[index];
125
148
  if (typeof region !== "object" || region === null) {
126
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
127
- `${name}[${index}] must be a key region; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, region)}`
149
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
150
+ `${name}[${index}] must be a key region; received ${_chunkS3QELJUEcjs.describeRejected.call(void 0, region)}`
128
151
  );
129
152
  }
130
- _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, region.startBeat, `${name}[${index}].startBeat`);
131
- _chunkS5BKZZGMcjs.assertFiniteNumber.call(void 0, region.endBeat, `${name}[${index}].endBeat`);
153
+ _chunkS3QELJUEcjs.assertFiniteNumber.call(void 0, region.startBeat, `${name}[${index}].startBeat`);
154
+ _chunkS3QELJUEcjs.assertFiniteNumber.call(void 0, region.endBeat, `${name}[${index}].endBeat`);
132
155
  if (region.key === void 0 || region.key === null || typeof region.key !== "string" && typeof region.key !== "object") {
133
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
134
- `${name}[${index}].key must name a key; received ${_chunkS5BKZZGMcjs.describeRejected.call(void 0, region.key)}`
156
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
157
+ `${name}[${index}].key must name a key; received ${_chunkS3QELJUEcjs.describeRejected.call(void 0, region.key)}`
135
158
  );
136
159
  }
137
- _chunk3R3QVKY5cjs.resolveKey.call(void 0, region.key);
160
+ _chunkYDGMWH43cjs.resolveKey.call(void 0, region.key);
138
161
  }
162
+ _chunkLQZQDBCDcjs.assertSpansInOrder.call(void 0, keys, name);
139
163
  }
140
164
  function harmonyTrackSet(harmonyTracks, tracks, name = "harmonyTracks") {
141
165
  if (harmonyTracks === void 0) {
@@ -145,16 +169,16 @@ function harmonyTrackSet(harmonyTracks, tracks, name = "harmonyTracks") {
145
169
  for (let index = 0; index < harmonyTracks.length; index += 1) {
146
170
  const value = harmonyTracks[index];
147
171
  if (value === void 0 || !Number.isFinite(value)) {
148
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(`${name}[${index}] must be a track index; received ${value}`);
172
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(`${name}[${index}] must be a track index; received ${value}`);
149
173
  }
150
174
  const track = Math.trunc(value);
151
175
  if (track < 0 || track >= tracks.length) {
152
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
176
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
153
177
  `${name}[${index}] ${track} is outside the arrangement's ${tracks.length} tracks`
154
178
  );
155
179
  }
156
180
  if (isPercussion(_optionalChain([tracks, 'access', _4 => _4[track], 'optionalAccess', _5 => _5.role]))) {
157
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
181
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
158
182
  `${name}[${index}] ${track} names a percussion track, whose pitches state no harmony`
159
183
  );
160
184
  }
@@ -195,7 +219,7 @@ function splitIntoSubVoices(ordered) {
195
219
  continue;
196
220
  }
197
221
  const end = last.startBeat + last.durationBeat;
198
- if (!_chunkSGLEQVJHcjs.hasEnded.call(void 0, end, note.startBeat)) {
222
+ if (!_chunkLQZQDBCDcjs.hasEnded.call(void 0, end, note.startBeat)) {
199
223
  continue;
200
224
  }
201
225
  const distance = Math.abs(last.pitch - note.pitch);
@@ -216,13 +240,13 @@ function splitIntoSubVoices(ordered) {
216
240
  for (let start = 0; start < ordered.length; ) {
217
241
  const onset = _nullishCoalesce(_optionalChain([ordered, 'access', _13 => _13[start], 'optionalAccess', _14 => _14.startBeat]), () => ( 0));
218
242
  let end = start + 1;
219
- while (end < ordered.length && _chunkSGLEQVJHcjs.sameInstant.call(void 0, _nullishCoalesce(_optionalChain([ordered, 'access', _15 => _15[end], 'optionalAccess', _16 => _16.startBeat]), () => ( 0)), onset)) {
243
+ while (end < ordered.length && _chunkLQZQDBCDcjs.sameInstant.call(void 0, _nullishCoalesce(_optionalChain([ordered, 'access', _15 => _15[end], 'optionalAccess', _16 => _16.startBeat]), () => ( 0)), onset)) {
220
244
  end += 1;
221
245
  }
222
246
  const block = ordered.slice(start, end);
223
247
  if (block.length > 1) {
224
248
  const freeLanes = lanes.map((lane, index) => ({ index, last: lane[lane.length - 1] })).filter(
225
- (entry) => entry.last !== void 0 && _chunkSGLEQVJHcjs.hasEnded.call(void 0, entry.last.startBeat + entry.last.durationBeat, onset)
249
+ (entry) => entry.last !== void 0 && _chunkLQZQDBCDcjs.hasEnded.call(void 0, entry.last.startBeat + entry.last.durationBeat, onset)
226
250
  ).sort((a, b) => a.last.pitch - b.last.pitch);
227
251
  const assignment = pairWithFreeLanes(block, freeLanes);
228
252
  const taken = new Set(
@@ -310,7 +334,7 @@ function lastStartingAtOrBefore(sounding, beat) {
310
334
  let high = sounding.length;
311
335
  while (low < high) {
312
336
  const middle = Math.floor((low + high) / 2);
313
- if ((_nullishCoalesce(_optionalChain([sounding, 'access', _22 => _22[middle], 'optionalAccess', _23 => _23.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + _chunkS5BKZZGMcjs.BEAT_EPS) {
337
+ if ((_nullishCoalesce(_optionalChain([sounding, 'access', _22 => _22[middle], 'optionalAccess', _23 => _23.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + _chunkS3QELJUEcjs.BEAT_EPS) {
314
338
  low = middle + 1;
315
339
  } else {
316
340
  high = middle;
@@ -321,12 +345,17 @@ function lastStartingAtOrBefore(sounding, beat) {
321
345
  function prepareTracks(tracks) {
322
346
  let nextId = 0;
323
347
  return tracks.map((track, index) => {
324
- const ordered = track.notes.map((note, originalIndex) => ({ note, originalIndex })).filter(({ note }) => note.durationBeat > 0).sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch).map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));
348
+ const ordered = track.notes.map((note, originalIndex) => ({ note, originalIndex })).filter(({ note }) => note.durationBeat > 0).sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch).map(({ note, originalIndex }) => ({
349
+ ...note,
350
+ pitch: note.pitch === 0 ? 0 : note.pitch,
351
+ id: nextId++,
352
+ originalIndex
353
+ }));
325
354
  const voices = splitIntoSubVoices(ordered).map((voice) => {
326
355
  const sounding = voice.map((note, i) => {
327
356
  const prev = i > 0 ? voice[i - 1] : void 0;
328
357
  const prevEnd = prev === void 0 ? void 0 : prev.startBeat + prev.durationBeat;
329
- const contiguous = prevEnd !== void 0 && _chunkSGLEQVJHcjs.adjacent.call(void 0, prevEnd, note.startBeat);
358
+ const contiguous = prevEnd !== void 0 && _chunkLQZQDBCDcjs.adjacent.call(void 0, prevEnd, note.startBeat);
330
359
  return {
331
360
  id: note.id,
332
361
  originalIndex: note.originalIndex,
@@ -359,24 +388,25 @@ function withGivenKeys(result, given) {
359
388
  if (given === void 0) {
360
389
  return result;
361
390
  }
362
- return { ...result, keys: given, prevailingKey: _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, given), () => ( result.prevailingKey)) };
391
+ return { ...result, keys: given, prevailingKey: _nullishCoalesce(_chunkLQZQDBCDcjs.prevailingKeyOf.call(void 0, given), () => ( result.prevailingKey)) };
363
392
  }
364
393
  function callerTimeline(timeline, key, given, pooled, meters, budget) {
365
- const chartEnd = timeline.segments.reduce((end, segment) => Math.max(end, segment.endBeat), 0);
366
- const soundingEnd = pooled.reduce(
367
- (end, note) => Math.max(end, note.startBeat + note.durationBeat),
368
- 0
394
+ const chartEnd = timeline.segments.reduce(
395
+ (end, segment) => Math.max(end, segment.endBeat),
396
+ Number.NEGATIVE_INFINITY
369
397
  );
370
- const totalBeats = Math.max(chartEnd, soundingEnd);
371
- const { startBeat: musicStart } = _chunkSGLEQVJHcjs.gridForNotes.call(void 0, pooled, _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, 0, meters));
372
- const keys = _nullishCoalesce(given, () => ( _chunkSGLEQVJHcjs.attachPivots.call(void 0,
373
- key !== void 0 ? [{ startBeat: musicStart, endBeat: totalBeats, key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, key), confidence: 1 }] : _chunkSGLEQVJHcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }),
398
+ const soundingEnd = _chunkLQZQDBCDcjs.latestSoundingEnd.call(void 0, pooled, Number.NEGATIVE_INFINITY);
399
+ const spanEnd = Math.max(chartEnd, soundingEnd);
400
+ const totalBeats = Number.isFinite(spanEnd) ? spanEnd : 0;
401
+ const { startBeat: musicStart } = _chunkLQZQDBCDcjs.gridForNotes.call(void 0, pooled, _chunkS3QELJUEcjs.beatsPerBarAt.call(void 0, 0, meters));
402
+ const keys = _nullishCoalesce(given, () => ( _chunkLQZQDBCDcjs.attachPivots.call(void 0,
403
+ key !== void 0 ? _chunkLQZQDBCDcjs.statedKeyRegions.call(void 0, _chunkYDGMWH43cjs.resolveKey.call(void 0, key), musicStart, totalBeats) : _chunkLQZQDBCDcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }),
374
404
  timeline.segments
375
405
  )));
376
406
  return {
377
407
  timeline,
378
408
  keys,
379
- prevailingKey: _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))),
409
+ prevailingKey: _nullishCoalesce(_chunkLQZQDBCDcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunkYDGMWH43cjs.resolveKey.call(void 0, _chunkYDGMWH43cjs.majorKey.call(void 0, 0)))),
380
410
  segmentConfidence: timeline.segments.map(() => 0)
381
411
  };
382
412
  }
@@ -393,7 +423,7 @@ function soundingVoicesAt(prepared, beat) {
393
423
  continue;
394
424
  }
395
425
  const snap = { pitch: note.pitch };
396
- const previous = _chunkSGLEQVJHcjs.sameInstant.call(void 0, note.startBeat, beat) ? note.prevPitch : note.pitch;
426
+ const previous = _chunkLQZQDBCDcjs.sameInstant.call(void 0, note.startBeat, beat) ? note.prevPitch : note.pitch;
397
427
  if (previous !== void 0) {
398
428
  snap.prevPitch = previous;
399
429
  }
@@ -422,11 +452,11 @@ function snapshotsOf(voices) {
422
452
  return voices.map(({ snapshot }) => snapshot);
423
453
  }
424
454
  function analyzePolyphony(notes, chordAtBeat, key) {
425
- const passage = _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "notes", { allowNonPositiveDuration: true });
426
- _chunkS5BKZZGMcjs.assertFunction.call(void 0, chordAtBeat, "chordAtBeat");
455
+ const passage = _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, "notes", { allowNonPositiveDuration: true });
456
+ _chunkS3QELJUEcjs.assertFunction.call(void 0, chordAtBeat, "chordAtBeat");
427
457
  const prepared = prepareTracks([{ notes: [...passage] }]);
428
458
  const track = prepared[0];
429
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
459
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0,
430
460
  passage.length * (_nullishCoalesce(_optionalChain([track, 'optionalAccess', _29 => _29.voices, 'access', _30 => _30.length]), () => ( 0))),
431
461
  "polyphony note-voice comparisons"
432
462
  );
@@ -437,7 +467,7 @@ function analyzePolyphony(notes, chordAtBeat, key) {
437
467
  if (subVoice === void 0) {
438
468
  continue;
439
469
  }
440
- const analyzed = _chunkSGLEQVJHcjs.analyzeVoice.call(void 0,
470
+ const analyzed = _chunkLQZQDBCDcjs.analyzeVoice.call(void 0,
441
471
  subVoice.voice,
442
472
  chordAtBeat,
443
473
  key,
@@ -455,7 +485,7 @@ function analyzePolyphony(notes, chordAtBeat, key) {
455
485
  if (analyzed !== void 0) {
456
486
  return analyzed;
457
487
  }
458
- const [only] = _chunkSGLEQVJHcjs.analyzeVoice.call(void 0, [{ ...note, id: index }], chordAtBeat, key);
488
+ const [only] = _chunkLQZQDBCDcjs.analyzeVoice.call(void 0, [{ ...note, id: index }], chordAtBeat, key);
459
489
  return _nullishCoalesce(only, () => ( {
460
490
  noteId: index,
461
491
  pitch: note.pitch,
@@ -470,7 +500,7 @@ function firstSegmentAfter(timeline, beat) {
470
500
  let high = timeline.segments.length;
471
501
  while (low < high) {
472
502
  const middle = Math.floor((low + high) / 2);
473
- if ((_nullishCoalesce(_optionalChain([timeline, 'access', _35 => _35.segments, 'access', _36 => _36[middle], 'optionalAccess', _37 => _37.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + _chunkS5BKZZGMcjs.BEAT_EPS) {
503
+ if ((_nullishCoalesce(_optionalChain([timeline, 'access', _35 => _35.segments, 'access', _36 => _36[middle], 'optionalAccess', _37 => _37.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + _chunkS3QELJUEcjs.BEAT_EPS) {
474
504
  low = middle + 1;
475
505
  } else {
476
506
  high = middle;
@@ -483,7 +513,7 @@ function evaluationBeats(note, timeline) {
483
513
  const beats = [note.startBeat];
484
514
  for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {
485
515
  const start = _optionalChain([timeline, 'access', _38 => _38.segments, 'access', _39 => _39[i], 'optionalAccess', _40 => _40.startBeat]);
486
- if (start === void 0 || start >= noteEnd - _chunkS5BKZZGMcjs.BEAT_EPS) {
516
+ if (start === void 0 || start >= noteEnd - _chunkS3QELJUEcjs.BEAT_EPS) {
487
517
  break;
488
518
  }
489
519
  beats.push(start);
@@ -494,19 +524,19 @@ function analyzeArrangement(tracks, opts = {}) {
494
524
  return analyzeArrangementWith(tracks, opts).analysis;
495
525
  }
496
526
  function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
497
- const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "arrangement meters");
527
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, opts, "arrangement meters");
498
528
  const budget = opts.budget;
499
529
  const noteOptions = { allowNonPositiveDuration: true, budget };
500
530
  if (opts.pickupBeats !== void 0) {
501
- _chunkS5BKZZGMcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "arrangement pickupBeats");
531
+ _chunkS3QELJUEcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "arrangement pickupBeats");
502
532
  noteOptions.minStartBeat = -opts.pickupBeats;
503
533
  }
504
- _chunkS5BKZZGMcjs.assertArray.call(void 0, tracks, "arrangement tracks");
505
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
534
+ _chunkS3QELJUEcjs.assertArray.call(void 0, tracks, "arrangement tracks");
535
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
506
536
  const noteCount = assertTrackNotes(tracks, noteOptions);
507
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
537
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
508
538
  const profile = arrangementProfile(opts.profile);
509
- const minSeverity = opts.minSeverity === void 0 ? 1 /* Warning */ : _chunkS5BKZZGMcjs.assertInteger.call(void 0,
539
+ const minSeverity = opts.minSeverity === void 0 ? 1 /* Warning */ : _chunkS3QELJUEcjs.assertInteger.call(void 0,
510
540
  opts.minSeverity,
511
541
  "arrangement minSeverity",
512
542
  0 /* Safe */,
@@ -517,12 +547,12 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
517
547
  const harmonyTracks = harmonyTrackSet(opts.harmonyTracks, tracks);
518
548
  const prepared = prepareTracks(tracks);
519
549
  const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
520
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, noteCount * voiceCount, "arrangement note-voice comparisons", budget);
550
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, noteCount * voiceCount, "arrangement note-voice comparisons", budget);
521
551
  const pooled = poolNotes(tracks, harmonyTracks);
522
552
  let evidence;
523
553
  let inferred;
524
554
  if (opts.timeline === void 0) {
525
- const run = _chunkSGLEQVJHcjs.analyzeTimeline.call(void 0,
555
+ const run = _chunkLQZQDBCDcjs.analyzeTimeline.call(void 0,
526
556
  pooled,
527
557
  {
528
558
  key: opts.key,
@@ -540,9 +570,9 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
540
570
  inferred = callerTimeline(opts.timeline, opts.key, opts.keys, pooled, meters, budget);
541
571
  }
542
572
  const { timeline, keys, prevailingKey, segmentConfidence } = inferred;
543
- const keyAt = _chunkSGLEQVJHcjs.keyLookup.call(void 0, keys, prevailingKey);
544
- const scaleAt = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
545
- const cadences = _chunkSGLEQVJHcjs.detectCadences.call(void 0, timeline, scaleAt);
573
+ const keyAt = _chunkLQZQDBCDcjs.keyLookup.call(void 0, keys, prevailingKey);
574
+ const scaleAt = (beat) => _chunkYDGMWH43cjs.scaleOf.call(void 0, keyAt(beat));
575
+ const cadences = _chunkLQZQDBCDcjs.detectCadences.call(void 0, timeline, scaleAt);
546
576
  const soundingCache = /* @__PURE__ */ new Map();
547
577
  const trackAnalyses = [];
548
578
  const conflicts = [];
@@ -561,7 +591,7 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
561
591
  if (!subVoice) {
562
592
  continue;
563
593
  }
564
- const analyzed = _chunkSGLEQVJHcjs.analyzeVoice.call(void 0,
594
+ const analyzed = _chunkLQZQDBCDcjs.analyzeVoice.call(void 0,
565
595
  subVoice.voice,
566
596
  timeline.at,
567
597
  scaleAt,
@@ -578,7 +608,7 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
578
608
  continue;
579
609
  }
580
610
  for (const beat of evaluationBeats(note, timeline)) {
581
- const atOnset = _chunkSGLEQVJHcjs.sameInstant.call(void 0, beat, note.startBeat);
611
+ const atOnset = _chunkLQZQDBCDcjs.sameInstant.call(void 0, beat, note.startBeat);
582
612
  const others = otherVoicesSounding(prepared, t, v, beat, soundingCache);
583
613
  const safetyQuery = {
584
614
  profile,
@@ -587,11 +617,11 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
587
617
  chord: timeline.at(beat),
588
618
  key: scaleAt(beat),
589
619
  otherVoices: snapshotsOf(others),
590
- strongBeat: _chunkS5BKZZGMcjs.isStrongBeat.call(void 0, beat, meters)
620
+ strongBeat: _chunkS3QELJUEcjs.isStrongBeat.call(void 0, beat, meters)
591
621
  };
592
- let result = _chunk3R3QVKY5cjs.evaluateSafety.call(void 0, safetyQuery, { suggestions: false });
622
+ let result = _chunkYDGMWH43cjs.evaluateSafety.call(void 0, safetyQuery, { suggestions: false });
593
623
  if (result.safety !== 0 /* Safe */ && result.safety >= minSeverity) {
594
- result = _chunk3R3QVKY5cjs.evaluateSafety.call(void 0, safetyQuery, { partners: true });
624
+ result = _chunkYDGMWH43cjs.evaluateSafety.call(void 0, safetyQuery, { partners: true });
595
625
  const conflict = {
596
626
  beat,
597
627
  trackName: track.name,
@@ -704,7 +734,7 @@ function sessionOf(tracks, opts, analysis, evidence) {
704
734
  tracks,
705
735
  analysis,
706
736
  update(edits) {
707
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, edits.length, "arrangement edits", opts.budget);
737
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, edits.length, "arrangement edits", opts.budget);
708
738
  const next = [...tracks];
709
739
  let edited = null;
710
740
  let reordered = false;
@@ -715,16 +745,16 @@ function sessionOf(tracks, opts, analysis, evidence) {
715
745
  }
716
746
  const target = next[edit.trackIndex];
717
747
  if (target === void 0) {
718
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
748
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
719
749
  `edits[${index}].trackIndex ${edit.trackIndex} is outside the session's ${tracks.length} tracks`
720
750
  );
721
751
  }
722
752
  if (!Array.isArray(edit.notes)) {
723
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
724
- `edits[${index}].notes must be an array; received ${typeof edit.notes}`
753
+ throw new (0, _chunkS3QELJUEcjs.InvalidInputError)(
754
+ `edits[${index}].notes must be an array; received ${_chunkS3QELJUEcjs.describeRejected.call(void 0, edit.notes)}`
725
755
  );
726
756
  }
727
- _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, edit.notes, `edits[${index}].notes`, {
757
+ _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, edit.notes, `edits[${index}].notes`, {
728
758
  allowNonPositiveDuration: true,
729
759
  budget: opts.budget
730
760
  });
@@ -735,7 +765,7 @@ function sessionOf(tracks, opts, analysis, evidence) {
735
765
  if (edited === null && !reordered) {
736
766
  return sessionOf(next, opts, analysis, evidence);
737
767
  }
738
- const dirty = evidence === void 0 ? void 0 : edited === null ? { from: 0, to: 0 } : _chunkSGLEQVJHcjs.dirtySlotsFor.call(void 0, evidence, edited.startBeat, edited.endBeat);
768
+ const dirty = evidence === void 0 ? void 0 : edited === null ? { from: 0, to: 0 } : _chunkLQZQDBCDcjs.dirtySlotsFor.call(void 0, evidence, edited.startBeat, edited.endBeat);
739
769
  const run = analyzeArrangementWith(next, opts, evidence, dirty);
740
770
  return sessionOf(next, opts, run.analysis, run.evidence);
741
771
  }
@@ -756,26 +786,27 @@ var FUNCTION_WEIGHT = 0.5;
756
786
  var DISSONANCE_WEIGHT = 0.35;
757
787
  var SPAN_WEIGHT = 0.15;
758
788
  var SPAN_SATURATION = 24;
789
+ var MAX_DISTINCT_PITCHES = 128;
759
790
  function clamp012(value) {
760
791
  return Math.min(1, Math.max(0, value));
761
792
  }
762
793
  function tensionCurve(tracks, opts = {}) {
763
- const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "arrangement meters");
794
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, opts, "arrangement meters");
764
795
  const budget = opts.budget;
765
796
  const noteOptions = { allowNonPositiveDuration: true, budget };
766
797
  if (opts.pickupBeats !== void 0) {
767
- _chunkS5BKZZGMcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "arrangement pickupBeats");
798
+ _chunkS3QELJUEcjs.assertRange.call(void 0, opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, "arrangement pickupBeats");
768
799
  noteOptions.minStartBeat = -opts.pickupBeats;
769
800
  }
770
- _chunkS5BKZZGMcjs.assertArray.call(void 0, tracks, "arrangement tracks");
771
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
801
+ _chunkS3QELJUEcjs.assertArray.call(void 0, tracks, "arrangement tracks");
802
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
772
803
  const noteCount = assertTrackNotes(tracks, noteOptions);
773
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
804
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
774
805
  const profile = arrangementProfile(opts.profile);
775
806
  const step = _nullishCoalesce(opts.step, () => ( 1));
776
- _chunkS5BKZZGMcjs.assertRange.call(void 0, step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
807
+ _chunkS3QELJUEcjs.assertRange.call(void 0, step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
777
808
  if (opts.minSeverity !== void 0) {
778
- _chunkS5BKZZGMcjs.assertInteger.call(void 0,
809
+ _chunkS3QELJUEcjs.assertInteger.call(void 0,
779
810
  opts.minSeverity,
780
811
  "arrangement minSeverity",
781
812
  0 /* Safe */,
@@ -787,20 +818,15 @@ function tensionCurve(tracks, opts = {}) {
787
818
  const harmonyTracks = harmonyTrackSet(opts.harmonyTracks, tracks);
788
819
  const pooled = poolNotes(tracks, harmonyTracks);
789
820
  const all = poolNotes(tracks);
790
- const totalBeats = all.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
791
- const { origin: sampleStart, startBeat: musicStart } = _chunkSGLEQVJHcjs.gridForNotes.call(void 0, all, step);
792
- const suppliedKeys = opts.timeline === void 0 ? void 0 : _nullishCoalesce(opts.keys, () => ( (opts.key !== void 0 ? [
793
- {
794
- // The one region a named key holds starts where the music does,
795
- // as the searched regions and the timeline's own key window do:
796
- // a key cannot be in force over beats nothing sounds in.
797
- startBeat: musicStart,
798
- endBeat: totalBeats,
799
- key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, opts.key),
800
- confidence: 1
801
- }
802
- ] : _chunkSGLEQVJHcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }))));
803
- const { timeline, keys, prevailingKey } = opts.timeline === void 0 || suppliedKeys === void 0 ? _chunkSGLEQVJHcjs.chordTimelineFromNotes.call(void 0, pooled, {
821
+ const totalBeats = _chunkLQZQDBCDcjs.latestSoundingEnd.call(void 0, all);
822
+ const { origin: sampleStart, startBeat: musicStart } = _chunkLQZQDBCDcjs.gridForNotes.call(void 0, all, step);
823
+ const suppliedKeys = opts.timeline === void 0 ? void 0 : _nullishCoalesce(opts.keys, () => ( (opts.key !== void 0 ? (
824
+ // The one region a named key holds starts where the music does, as
825
+ // the searched regions and the timeline's own key window do: a key
826
+ // cannot be in force over beats nothing sounds in.
827
+ _chunkLQZQDBCDcjs.statedKeyRegions.call(void 0, _chunkYDGMWH43cjs.resolveKey.call(void 0, opts.key), musicStart, totalBeats)
828
+ ) : _chunkLQZQDBCDcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }))));
829
+ const { timeline, keys, prevailingKey } = opts.timeline === void 0 || suppliedKeys === void 0 ? _chunkLQZQDBCDcjs.chordTimelineFromNotes.call(void 0, pooled, {
804
830
  key: opts.key,
805
831
  meters,
806
832
  pickupBeats: opts.pickupBeats,
@@ -809,17 +835,17 @@ function tensionCurve(tracks, opts = {}) {
809
835
  }) : {
810
836
  timeline: opts.timeline,
811
837
  keys: suppliedKeys,
812
- prevailingKey: _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, suppliedKeys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))))
838
+ prevailingKey: _nullishCoalesce(_chunkLQZQDBCDcjs.prevailingKeyOf.call(void 0, suppliedKeys), () => ( _chunkYDGMWH43cjs.resolveKey.call(void 0, _chunkYDGMWH43cjs.majorKey.call(void 0, 0))))
813
839
  };
814
840
  const regions = _nullishCoalesce(opts.keys, () => ( keys));
815
- const keyAt = _chunkSGLEQVJHcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, regions), () => ( prevailingKey)));
841
+ const keyAt = _chunkLQZQDBCDcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkLQZQDBCDcjs.prevailingKeyOf.call(void 0, regions), () => ( prevailingKey)));
816
842
  const prepared = prepareTracks(tracks);
817
843
  const points = [];
818
- const sampleCount = Math.max(0, Math.ceil((totalBeats - sampleStart) / step - _chunkS5BKZZGMcjs.BEAT_EPS));
819
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, sampleCount, "tension samples", budget);
844
+ const sampleCount = Math.max(0, Math.ceil((totalBeats - sampleStart) / step - _chunkS3QELJUEcjs.BEAT_EPS));
845
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, sampleCount, "tension samples", budget);
820
846
  const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
821
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
822
- sampleCount * Math.min(128, voiceCount) * voiceCount,
847
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0,
848
+ sampleCount * Math.min(MAX_DISTINCT_PITCHES, voiceCount) * voiceCount,
823
849
  "tension sample-voice comparisons",
824
850
  budget
825
851
  );
@@ -827,16 +853,15 @@ function tensionCurve(tracks, opts = {}) {
827
853
  const beat = i === 0 ? Math.max(sampleStart, musicStart) : sampleStart + i * step;
828
854
  points.push({
829
855
  beat,
830
- // The pitch classes alone: tension is scored from what sounds against
831
- // the tonic, and no reading below here asks how the key is written.
832
- tension: sampleTension(prepared, timeline, _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat)), meters, profile, beat)
856
+ // The key whole: an augmented sixth is told from a dominant by its letters.
857
+ tension: sampleTension(prepared, timeline, keyAt(beat), meters, profile, beat)
833
858
  });
834
859
  }
835
860
  return points;
836
861
  }
837
862
  function tensionCurveFrom(tracks, analysis, opts = {}) {
838
- const asked = _chunkS5BKZZGMcjs.assertRecord.call(void 0, opts, "opts");
839
- const carried = carriedHarmony(_chunkS5BKZZGMcjs.assertRecord.call(void 0, analysis, "analysis"), asked);
863
+ const asked = _chunkS3QELJUEcjs.assertRecord.call(void 0, opts, "opts");
864
+ const carried = carriedHarmony(_chunkS3QELJUEcjs.assertRecord.call(void 0, analysis, "analysis"), asked);
840
865
  return tensionCurve(tracks, { ...carried, ...asked });
841
866
  }
842
867
  function carriedHarmony(analysis, opts) {
@@ -870,9 +895,9 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
870
895
  return 0;
871
896
  }
872
897
  const chord = timeline.at(beat);
873
- const functionScore = chord ? FUNCTION_TENSION[_chunkSGLEQVJHcjs.functionOf.call(void 0, chord, key)] : 0;
874
- const chordPcs = chord ? new Set(_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord)) : null;
875
- const strongBeat = _chunkS5BKZZGMcjs.isStrongBeat.call(void 0, beat, meters);
898
+ const functionScore = chord ? FUNCTION_TENSION[_chunkLQZQDBCDcjs.functionOf.call(void 0, chord, key)] : 0;
899
+ const chordPcs = chord ? new Set(_chunkYDGMWH43cjs.chordPitchClasses.call(void 0, chord)) : null;
900
+ const strongBeat = _chunkS3QELJUEcjs.isStrongBeat.call(void 0, beat, meters);
876
901
  let nonChord = 0;
877
902
  let dissonant = 0;
878
903
  const pitches = /* @__PURE__ */ new Map();
@@ -888,7 +913,7 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
888
913
  }
889
914
  }
890
915
  for (const [pitch, count] of pitches) {
891
- if (chordPcs && !chordPcs.has(_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pitch))) {
916
+ if (chordPcs && !chordPcs.has(_chunkS3QELJUEcjs.pitchClassOf.call(void 0, pitch))) {
892
917
  nonChord += count;
893
918
  }
894
919
  const at = _nullishCoalesce(firstOf.get(pitch), () => ( 0));
@@ -899,8 +924,8 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
899
924
  others[at] = tail;
900
925
  others.pop();
901
926
  }
902
- const result = _chunk3R3QVKY5cjs.evaluateSafety.call(void 0,
903
- { profile, candidatePitch: pitch, chord, key, otherVoices: others, strongBeat },
927
+ const result = _chunkYDGMWH43cjs.evaluateSafety.call(void 0,
928
+ { profile, candidatePitch: pitch, chord, key: key.scale, otherVoices: others, strongBeat },
904
929
  { suggestions: false }
905
930
  );
906
931
  if (held !== void 0 && tail !== void 0) {
@@ -926,19 +951,34 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
926
951
 
927
952
  // src/analyze/form/hypermeter.ts
928
953
  var GROUP_CANDIDATES = [2, 3, 4, 6, 8];
929
- var GROUP_PRIOR = /* @__PURE__ */ new Map([
954
+ var GROUP_PRIOR_LIST = [
930
955
  [2, 0.9],
931
956
  [3, 0.75],
932
957
  [4, 1],
933
958
  [6, 0.7],
934
959
  [8, 0.85]
935
- ]);
960
+ ];
961
+ var GROUP_PRIOR = new Map(GROUP_PRIOR_LIST);
936
962
  var CONTRAST_SHARE = 0.65;
937
963
  var CADENCE_SHARE = 0.35;
938
964
  var GROUP_FINAL_FIT = 1;
939
965
  var ELIDED_FIT = 0.5;
940
966
  var MIN_GROUPS = 2;
941
967
  var MARGIN_FLOOR = 0.7;
968
+ function cadenceSpans(arrivals, segments) {
969
+ return arrivals.map(({ atBeat, strength }) => ({
970
+ atBeat,
971
+ endBeat: _nullishCoalesce(_optionalChain([_chunkLQZQDBCDcjs.segmentStartingAt.call(void 0, segments, atBeat), 'optionalAccess', _41 => _41.endBeat]), () => ( atBeat)),
972
+ strength
973
+ }));
974
+ }
975
+ function cadenceBarsOf(spans, meter) {
976
+ return spans.map((span) => ({
977
+ arrival: _chunkS3QELJUEcjs.barIndexAt.call(void 0, span.atBeat, meter),
978
+ last: _chunkLQZQDBCDcjs.lastBarOf.call(void 0, meter, span.atBeat, span.endBeat),
979
+ strength: span.strength
980
+ }));
981
+ }
942
982
  function accentContrast(novelty, slices, groupBars, phase) {
943
983
  let headSum = 0;
944
984
  let headCount = 0;
@@ -950,7 +990,7 @@ function accentContrast(novelty, slices, groupBars, phase) {
950
990
  continue;
951
991
  }
952
992
  const value = _nullishCoalesce(novelty[i], () => ( 0));
953
- if (_chunkSGLEQVJHcjs.floorMod.call(void 0, slice.index - phase, groupBars) === 0) {
993
+ if (_chunkLQZQDBCDcjs.floorMod.call(void 0, slice.index - phase, groupBars) === 0) {
954
994
  headSum += value;
955
995
  headCount += 1;
956
996
  } else {
@@ -963,22 +1003,41 @@ function accentContrast(novelty, slices, groupBars, phase) {
963
1003
  }
964
1004
  return headSum / headCount - otherSum / otherCount;
965
1005
  }
966
- function cadenceFitOf(cadenceBars, slices, groupBars, phase) {
967
- const lastBar = _nullishCoalesce(_optionalChain([slices, 'access', _41 => _41[slices.length - 1], 'optionalAccess', _42 => _42.index]), () => ( -1));
1006
+ function cadenceFitOf(cadences, slices, groupBars, phase) {
1007
+ const lastBar = _nullishCoalesce(_optionalChain([slices, 'access', _42 => _42[slices.length - 1], 'optionalAccess', _43 => _43.index]), () => ( -1));
1008
+ const closing = /* @__PURE__ */ new Map();
1009
+ const eliding = /* @__PURE__ */ new Map();
1010
+ const credit = (bars, bar, strength) => {
1011
+ bars.set(bar, Math.max(_nullishCoalesce(bars.get(bar), () => ( 0)), strength));
1012
+ };
1013
+ for (const cadence of cadences) {
1014
+ const end = cadence.arrival + _chunkLQZQDBCDcjs.floorMod.call(void 0, groupBars - 1 + phase - cadence.arrival, groupBars);
1015
+ if (end <= cadence.last) {
1016
+ credit(closing, end, cadence.strength);
1017
+ } else if (_chunkLQZQDBCDcjs.floorMod.call(void 0, cadence.arrival - phase, groupBars) === 0 && // Elision needs a next group to arrive into. Past the last bar the span
1018
+ // holds there is none, so a cadence beat beyond the music is not evidence
1019
+ // about how the music that was read is grouped.
1020
+ cadence.arrival <= lastBar) {
1021
+ credit(eliding, cadence.arrival - 1, cadence.strength);
1022
+ }
1023
+ }
968
1024
  let ends = 0;
969
- let fit = 0;
1025
+ let closed = 0;
1026
+ let weighted = 0;
970
1027
  for (const slice of slices) {
971
- if (slice.index < 0 || _chunkSGLEQVJHcjs.floorMod.call(void 0, slice.index - phase, groupBars) !== groupBars - 1) {
1028
+ if (slice.index < 0 || _chunkLQZQDBCDcjs.floorMod.call(void 0, slice.index - phase, groupBars) !== groupBars - 1) {
972
1029
  continue;
973
1030
  }
974
1031
  ends += 1;
975
- if (cadenceBars.has(slice.index)) {
976
- fit += GROUP_FINAL_FIT;
977
- } else if (slice.index < lastBar && cadenceBars.has(slice.index + 1)) {
978
- fit += ELIDED_FIT;
979
- }
1032
+ const final = closing.get(slice.index);
1033
+ const elided = eliding.get(slice.index);
1034
+ closed += Math.max(
1035
+ final === void 0 ? 0 : GROUP_FINAL_FIT,
1036
+ elided === void 0 ? 0 : ELIDED_FIT
1037
+ );
1038
+ weighted += Math.max(GROUP_FINAL_FIT * (_nullishCoalesce(final, () => ( 0))), ELIDED_FIT * (_nullishCoalesce(elided, () => ( 0))));
980
1039
  }
981
- return ends === 0 ? 0 : _chunkSGLEQVJHcjs.clamp01.call(void 0, fit / ends);
1040
+ return ends === 0 ? { closed: 0, weighted: 0 } : { closed: _chunkLQZQDBCDcjs.clamp01.call(void 0, closed / ends), weighted: _chunkLQZQDBCDcjs.clamp01.call(void 0, weighted / ends) };
982
1041
  }
983
1042
  function undecidedReading(slices) {
984
1043
  const fullBars = slices.filter((slice) => slice.index >= 0);
@@ -992,52 +1051,70 @@ function undecidedReading(slices) {
992
1051
  };
993
1052
  }
994
1053
  function hypermeter(notes, meter, opts = {}) {
995
- const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
996
- const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, { meters: meter }, "meter");
997
- _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "hypermeter notes", {
1054
+ return hypermeterOver(
1055
+ notes,
1056
+ meter,
1057
+ opts,
1058
+ (asked) => cadenceSpans(
1059
+ (_nullishCoalesce(asked.cadenceBeats, () => ( []))).map((atBeat) => ({ atBeat, strength: 1 })),
1060
+ []
1061
+ )
1062
+ );
1063
+ }
1064
+ function hypermeterOver(notes, meter, opts, spansOf) {
1065
+ const asked = _chunkS3QELJUEcjs.assertOptions.call(void 0, opts, "opts");
1066
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, { meters: meter }, "meter");
1067
+ _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, "hypermeter notes", {
998
1068
  allowNonPositiveDuration: true,
999
1069
  budget: asked.budget
1000
1070
  });
1001
1071
  const sounding = notes.filter((note) => note.durationBeat > 0);
1002
- const firstOnset = _chunkSGLEQVJHcjs.firstSoundingBeat.call(void 0, sounding);
1003
- const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
1004
- const spanEnd = _nullishCoalesce(asked.totalBeats, () => ( lastEnd));
1005
- _chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "hypermeter totalBeats");
1006
- const spanStart = _chunkSGLEQVJHcjs.barGridStart.call(void 0, firstOnset, meters);
1007
- const slices = _chunkSGLEQVJHcjs.sliceBars.call(void 0, sounding, meters, spanStart, spanEnd, asked.budget);
1072
+ const firstOnset = _chunkLQZQDBCDcjs.firstSoundingBeat.call(void 0, sounding);
1073
+ const spanEnd = _chunkLQZQDBCDcjs.resolveSpanEnd.call(void 0,
1074
+ asked.totalBeats,
1075
+ firstOnset,
1076
+ _chunkLQZQDBCDcjs.latestSoundingEnd.call(void 0, sounding),
1077
+ "hypermeter totalBeats"
1078
+ );
1079
+ const spanStart = _chunkLQZQDBCDcjs.barGridStart.call(void 0, firstOnset, meters);
1080
+ if (sounding.length === 0 || spanEnd <= spanStart + _chunkS3QELJUEcjs.BEAT_EPS) {
1081
+ return undecidedReading([]);
1082
+ }
1083
+ const slices = _chunkLQZQDBCDcjs.sliceBars.call(void 0, sounding, meters, spanStart, spanEnd, asked.budget);
1008
1084
  const fullBars = slices.reduce((count, slice) => slice.index >= 0 ? count + 1 : count, 0);
1009
1085
  const candidates = GROUP_CANDIDATES.filter((groupBars) => fullBars >= groupBars * MIN_GROUPS);
1010
1086
  if (candidates.length === 0) {
1011
1087
  return undecidedReading(slices);
1012
1088
  }
1013
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
1089
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0,
1014
1090
  candidates.reduce((sum, groupBars) => sum + groupBars * slices.length, 0),
1015
1091
  "hypermeter readings",
1016
1092
  asked.budget
1017
1093
  );
1018
- const novelty = slices.map((_, index) => _chunkSGLEQVJHcjs.harmonicNovelty.call(void 0, slices, index));
1019
- const cadenceBars = new Set((_nullishCoalesce(asked.cadenceBeats, () => ( []))).map((beat) => _chunkS5BKZZGMcjs.barIndexAt.call(void 0, beat, meters)));
1020
- const hasCadences = cadenceBars.size > 0;
1094
+ const novelty = slices.map((_, index) => _chunkLQZQDBCDcjs.harmonicNovelty.call(void 0, slices, index));
1095
+ const cadenceBars = cadenceBarsOf(spansOf(asked), meters);
1096
+ const hasCadences = cadenceBars.length > 0;
1021
1097
  const readings = [];
1022
1098
  for (const groupBars of candidates) {
1023
1099
  const phases = Array.from({ length: groupBars }, (_, phase) => ({
1024
1100
  phase,
1025
1101
  contrast: accentContrast(novelty, slices, groupBars, phase),
1026
- cadenceFit: cadenceFitOf(cadenceBars, slices, groupBars, phase)
1102
+ fit: cadenceFitOf(cadenceBars, slices, groupBars, phase)
1027
1103
  }));
1028
- const bestFit = phases.reduce((best2, reading) => Math.max(best2, reading.cadenceFit), 0);
1029
- for (const { phase, contrast, cadenceFit } of phases) {
1030
- if (hasCadences && cadenceFit < bestFit - _chunkS5BKZZGMcjs.BEAT_EPS) {
1104
+ const bestFit = phases.reduce((best2, reading) => Math.max(best2, reading.fit.weighted), 0);
1105
+ for (const { phase, contrast, fit } of phases) {
1106
+ if (hasCadences && fit.weighted < bestFit - _chunkS3QELJUEcjs.BEAT_EPS) {
1031
1107
  continue;
1032
1108
  }
1033
- const evidence = _chunkSGLEQVJHcjs.clamp01.call(void 0,
1109
+ const cadenceFit = fit.closed;
1110
+ const evidence = _chunkLQZQDBCDcjs.clamp01.call(void 0,
1034
1111
  hasCadences ? (
1035
1112
  // Floored at zero rather than summed raw: group heads that restate
1036
1113
  // the harmony their group opened with — the commonest way a phrase
1037
1114
  // begins — change less than the bars between them, and a negative
1038
1115
  // contrast subtracted from the cadence term would count that
1039
1116
  // restatement as evidence against the very phase it confirms.
1040
- CONTRAST_SHARE * _chunkSGLEQVJHcjs.clamp01.call(void 0, contrast) + CADENCE_SHARE * cadenceFit
1117
+ CONTRAST_SHARE * _chunkLQZQDBCDcjs.clamp01.call(void 0, contrast) + CADENCE_SHARE * cadenceFit
1041
1118
  ) : contrast
1042
1119
  );
1043
1120
  readings.push({
@@ -1058,8 +1135,8 @@ function hypermeter(notes, meter, opts = {}) {
1058
1135
  return undecidedReading(slices);
1059
1136
  }
1060
1137
  const runnerUp = readings.find((reading) => reading.groupBars !== best.groupBars);
1061
- const margin = runnerUp === void 0 || best.score <= _chunkS5BKZZGMcjs.BEAT_EPS ? 1 : _chunkSGLEQVJHcjs.clamp01.call(void 0, (best.score - runnerUp.score) / best.score);
1062
- const downbeats = slices.filter((slice) => slice.index >= 0 && _chunkSGLEQVJHcjs.floorMod.call(void 0, slice.index - best.phase, best.groupBars) === 0).map((slice) => slice.startBeat);
1138
+ const margin = runnerUp === void 0 || best.score <= _chunkS3QELJUEcjs.BEAT_EPS ? 1 : _chunkLQZQDBCDcjs.clamp01.call(void 0, (best.score - runnerUp.score) / best.score);
1139
+ const downbeats = slices.filter((slice) => slice.index >= 0 && _chunkLQZQDBCDcjs.floorMod.call(void 0, slice.index - best.phase, best.groupBars) === 0).map((slice) => slice.startBeat);
1063
1140
  const cadencePart = hasCadences ? `${Math.round(best.cadenceFit * 100)}% of group ends closed by a cadence` : "no cadences given";
1064
1141
  return {
1065
1142
  groupBars: best.groupBars,
@@ -1069,554 +1146,11 @@ function hypermeter(notes, meter, opts = {}) {
1069
1146
  // runner-up is nearly always a multiple or a divisor of the winner — four
1070
1147
  // bars nest inside eight — and a reading of the same accents at another
1071
1148
  // level is weaker counter-evidence than an unrelated rival would be.
1072
- confidence: _chunkSGLEQVJHcjs.clamp01.call(void 0, best.evidence * (MARGIN_FLOOR + (1 - MARGIN_FLOOR) * margin)),
1149
+ confidence: _chunkLQZQDBCDcjs.clamp01.call(void 0, best.evidence * (MARGIN_FLOOR + (1 - MARGIN_FLOOR) * margin)),
1073
1150
  rationale: `${best.groupBars}-bar groups from bar ${best.phase}: harmonic change ${Math.round(best.contrast * 100)}% stronger on group heads, ${cadencePart}`
1074
1151
  };
1075
1152
  }
1076
1153
 
1077
- // src/analyze/melody/internal.ts
1078
- var RHYTHM_GRID = 64;
1079
- function phraseNotes(phrase) {
1080
- if (Array.isArray(phrase)) {
1081
- return phrase;
1082
- }
1083
- return _chunkS5BKZZGMcjs.assertRecord.call(void 0, phrase, "melodic phrase").notes;
1084
- }
1085
- function orderedNotes(notes, name, budget) {
1086
- _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, name, { allowNonPositiveDuration: true, budget });
1087
- const sounding = notes.filter((note) => note.durationBeat > 0).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
1088
- const line = [];
1089
- for (const note of sounding) {
1090
- const previous = line[line.length - 1];
1091
- if (previous !== void 0 && Math.abs(note.startBeat - previous.startBeat) <= _chunkS5BKZZGMcjs.BEAT_EPS) {
1092
- line[line.length - 1] = note;
1093
- continue;
1094
- }
1095
- line.push(note);
1096
- }
1097
- return line;
1098
- }
1099
- function intervalsOf(notes) {
1100
- const out = [];
1101
- for (let i = 1; i < notes.length; i += 1) {
1102
- out.push((_nullishCoalesce(_optionalChain([notes, 'access', _43 => _43[i], 'optionalAccess', _44 => _44.pitch]), () => ( 0))) - (_nullishCoalesce(_optionalChain([notes, 'access', _45 => _45[i - 1], 'optionalAccess', _46 => _46.pitch]), () => ( 0))));
1103
- }
1104
- return out;
1105
- }
1106
- function onsetGaps(notes) {
1107
- const out = [];
1108
- for (let i = 1; i < notes.length; i += 1) {
1109
- out.push((_nullishCoalesce(_optionalChain([notes, 'access', _47 => _47[i], 'optionalAccess', _48 => _48.startBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([notes, 'access', _49 => _49[i - 1], 'optionalAccess', _50 => _50.startBeat]), () => ( 0))));
1110
- }
1111
- return out;
1112
- }
1113
- function retrogradeGaps(notes) {
1114
- const out = [];
1115
- for (let i = notes.length - 1; i >= 1; i -= 1) {
1116
- const later = notes[i];
1117
- const earlier = notes[i - 1];
1118
- out.push(
1119
- (_nullishCoalesce(_optionalChain([later, 'optionalAccess', _51 => _51.startBeat]), () => ( 0))) + (_nullishCoalesce(_optionalChain([later, 'optionalAccess', _52 => _52.durationBeat]), () => ( 0))) - ((_nullishCoalesce(_optionalChain([earlier, 'optionalAccess', _53 => _53.startBeat]), () => ( 0))) + (_nullishCoalesce(_optionalChain([earlier, 'optionalAccess', _54 => _54.durationBeat]), () => ( 0))))
1120
- );
1121
- }
1122
- return out;
1123
- }
1124
- function rhythmProfile(gaps) {
1125
- const unit = _nullishCoalesce(gaps[0], () => ( 0));
1126
- if (!(unit > _chunkS5BKZZGMcjs.BEAT_EPS)) {
1127
- return gaps.length === 0 ? [] : null;
1128
- }
1129
- return gaps.map((gap) => Math.round(gap / unit * RHYTHM_GRID) / RHYTHM_GRID);
1130
- }
1131
- function sameNumbers(a, b) {
1132
- if (a.length !== b.length) {
1133
- return false;
1134
- }
1135
- for (let i = 0; i < a.length; i += 1) {
1136
- if (Math.abs((_nullishCoalesce(a[i], () => ( 0))) - (_nullishCoalesce(b[i], () => ( 0)))) > _chunkS5BKZZGMcjs.BEAT_EPS) {
1137
- return false;
1138
- }
1139
- }
1140
- return true;
1141
- }
1142
- function roundTo(value, digits) {
1143
- const scale = 10 ** digits;
1144
- return Math.round(value * scale) / scale;
1145
- }
1146
- function endBeatOf(notes) {
1147
- let end = 0;
1148
- for (const note of notes) {
1149
- end = Math.max(end, note.startBeat + note.durationBeat);
1150
- }
1151
- return end;
1152
- }
1153
- function onsetSpan(notes) {
1154
- if (notes.length < 2) {
1155
- return 0;
1156
- }
1157
- return (_nullishCoalesce(_optionalChain([notes, 'access', _55 => _55[notes.length - 1], 'optionalAccess', _56 => _56.startBeat]), () => ( 0))) - (_nullishCoalesce(_optionalChain([notes, 'access', _57 => _57[0], 'optionalAccess', _58 => _58.startBeat]), () => ( 0)));
1158
- }
1159
-
1160
- // src/analyze/melody/contour.ts
1161
- var DIRECTIONAL_SHARE = 0.75;
1162
- function extremes(notes) {
1163
- if (notes.length === 0) {
1164
- return { peak: -1, trough: -1 };
1165
- }
1166
- let peak = 0;
1167
- let trough = 0;
1168
- for (let i = 1; i < notes.length; i += 1) {
1169
- if ((_nullishCoalesce(_optionalChain([notes, 'access', _59 => _59[i], 'optionalAccess', _60 => _60.pitch]), () => ( 0))) > (_nullishCoalesce(_optionalChain([notes, 'access', _61 => _61[peak], 'optionalAccess', _62 => _62.pitch]), () => ( 0)))) {
1170
- peak = i;
1171
- }
1172
- if ((_nullishCoalesce(_optionalChain([notes, 'access', _63 => _63[i], 'optionalAccess', _64 => _64.pitch]), () => ( 0))) < (_nullishCoalesce(_optionalChain([notes, 'access', _65 => _65[trough], 'optionalAccess', _66 => _66.pitch]), () => ( 0)))) {
1173
- trough = i;
1174
- }
1175
- }
1176
- return { peak, trough };
1177
- }
1178
- function melodicContour(notes) {
1179
- const line = orderedNotes(phraseNotes(notes), "contour notes");
1180
- const intervals = intervalsOf(line);
1181
- const directions = intervals.map(
1182
- (step) => step > 0 ? "up" : step < 0 ? "down" : "same"
1183
- );
1184
- const { peak, trough } = extremes(line);
1185
- const range = (_nullishCoalesce(_optionalChain([line, 'access', _67 => _67[peak], 'optionalAccess', _68 => _68.pitch]), () => ( 0))) - (_nullishCoalesce(_optionalChain([line, 'access', _69 => _69[trough], 'optionalAccess', _70 => _70.pitch]), () => ( 0)));
1186
- const motion = intervals.reduce((sum, step) => sum + Math.abs(step), 0);
1187
- if (motion === 0) {
1188
- return {
1189
- directions,
1190
- shape: "static",
1191
- peakIndex: peak,
1192
- troughIndex: trough,
1193
- range: 0,
1194
- rationale: line.length < 2 ? "Too short to have a direction, so the line is static" : "Every note at the same pitch, so the line is static"
1195
- };
1196
- }
1197
- const first = _nullishCoalesce(_optionalChain([line, 'access', _71 => _71[0], 'optionalAccess', _72 => _72.pitch]), () => ( 0));
1198
- const last = _nullishCoalesce(_optionalChain([line, 'access', _73 => _73[line.length - 1], 'optionalAccess', _74 => _74.pitch]), () => ( 0));
1199
- const net = last - first;
1200
- const directness = Math.abs(net) / motion;
1201
- const share = Math.round(directness * 100);
1202
- if (directness >= DIRECTIONAL_SHARE) {
1203
- const shape = net > 0 ? "ascending" : "descending";
1204
- return {
1205
- directions,
1206
- shape,
1207
- peakIndex: peak,
1208
- troughIndex: trough,
1209
- range,
1210
- rationale: `${share}% of the motion runs ${net > 0 ? "up" : "down"}, so the line is ${shape}`
1211
- };
1212
- }
1213
- const peakPitch = _nullishCoalesce(_optionalChain([line, 'access', _75 => _75[peak], 'optionalAccess', _76 => _76.pitch]), () => ( 0));
1214
- const archMotion = peakPitch - first + (peakPitch - last);
1215
- if (peak > 0 && peak < line.length - 1 && archMotion >= DIRECTIONAL_SHARE * motion) {
1216
- return {
1217
- directions,
1218
- shape: "arch",
1219
- peakIndex: peak,
1220
- troughIndex: trough,
1221
- range,
1222
- rationale: `Rises to its peak at note ${peak + 1} of ${line.length} and falls back, so the line is an arch`
1223
- };
1224
- }
1225
- return {
1226
- directions,
1227
- shape: "wave",
1228
- peakIndex: peak,
1229
- troughIndex: trough,
1230
- range,
1231
- rationale: `Changes direction without settling either way (${share}% of the motion nets out), so the line is a wave`
1232
- };
1233
- }
1234
-
1235
- // src/analyze/melody/motifs.ts
1236
- var MIN_CELL_NOTES = 3;
1237
- var MAX_CELL_NOTES = 8;
1238
- var MIN_OCCURRENCES = 2;
1239
- function windowSignature(notes) {
1240
- const profile = rhythmProfile(onsetGaps(notes));
1241
- if (profile === null) {
1242
- return null;
1243
- }
1244
- return `${notes.length}|${intervalsOf(notes).join(",")}|${profile.join(",")}`;
1245
- }
1246
- function nonOverlapping(starts, length) {
1247
- const picked = [];
1248
- let lastEnd = Number.NEGATIVE_INFINITY;
1249
- for (const start of starts) {
1250
- if (start > lastEnd) {
1251
- picked.push(start);
1252
- lastEnd = start + length - 1;
1253
- }
1254
- }
1255
- return picked;
1256
- }
1257
- function subsumes(outer, inner) {
1258
- let at = 0;
1259
- for (const start of inner.starts) {
1260
- while (at + 1 < outer.starts.length && (_nullishCoalesce(outer.starts[at + 1], () => ( 0))) <= start) {
1261
- at += 1;
1262
- }
1263
- const from = outer.starts[at];
1264
- if (from === void 0 || start < from || start + inner.length > from + outer.length) {
1265
- return false;
1266
- }
1267
- }
1268
- return true;
1269
- }
1270
- function listBeats(occurrences) {
1271
- const shown = occurrences.slice(0, 5).map((occurrence) => roundTo(occurrence.startBeat, 3));
1272
- return occurrences.length > shown.length ? `${shown.join(", ")}, and ${occurrences.length - shown.length} more` : shown.join(", ");
1273
- }
1274
- function motifFromGroup(group, notes) {
1275
- const first = _nullishCoalesce(group.starts[0], () => ( 0));
1276
- const prime = notes.slice(first, first + group.length).map((note) => ({ ...note }));
1277
- const primePitch = _nullishCoalesce(_optionalChain([prime, 'access', _77 => _77[0], 'optionalAccess', _78 => _78.pitch]), () => ( 0));
1278
- const primeSpan = onsetSpan(prime);
1279
- const occurrences = group.starts.map((start) => {
1280
- const statement = notes.slice(start, start + group.length);
1281
- const span = onsetSpan(statement);
1282
- return {
1283
- noteIndex: start,
1284
- startBeat: _nullishCoalesce(_optionalChain([statement, 'access', _79 => _79[0], 'optionalAccess', _80 => _80.startBeat]), () => ( 0)),
1285
- endBeat: endBeatOf(statement),
1286
- transpose: (_nullishCoalesce(_optionalChain([statement, 'access', _81 => _81[0], 'optionalAccess', _82 => _82.pitch]), () => ( 0))) - primePitch,
1287
- timeRatio: primeSpan > _chunkS5BKZZGMcjs.BEAT_EPS ? span / primeSpan : 1
1288
- };
1289
- });
1290
- const shape = melodicContour(prime).shape;
1291
- const transposed = occurrences.some((occurrence) => occurrence.transpose !== 0);
1292
- const stretched = occurrences.some((occurrence) => Math.abs(occurrence.timeRatio - 1) > _chunkS5BKZZGMcjs.BEAT_EPS);
1293
- const detail = [
1294
- transposed ? "at more than one pitch level" : "at the same pitch level",
1295
- stretched ? "and in more than one set of note values" : ""
1296
- ].filter((part) => part !== "").join(" ");
1297
- return {
1298
- notes: prime,
1299
- intervals: intervalsOf(prime),
1300
- rhythm: _nullishCoalesce(rhythmProfile(onsetGaps(prime)), () => ( [])),
1301
- occurrences,
1302
- rationale: `${group.length}-note ${shape} cell stated ${occurrences.length} ${occurrences.length === 1 ? "time" : "times"} (beats ${listBeats(occurrences)}), ${detail}`
1303
- };
1304
- }
1305
- function extractMotifs(notes, opts = {}) {
1306
- const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
1307
- const minNotes = _chunkS5BKZZGMcjs.assertInteger.call(void 0, _nullishCoalesce(asked.minNotes, () => ( MIN_CELL_NOTES)), "motif minNotes", 2, 64);
1308
- const maxNotes = _chunkS5BKZZGMcjs.assertInteger.call(void 0, _nullishCoalesce(asked.maxNotes, () => ( MAX_CELL_NOTES)), "motif maxNotes", 2, 64);
1309
- if (maxNotes < minNotes) {
1310
- throw new (0, _chunkS5BKZZGMcjs.InvalidInputError)(
1311
- `motif maxNotes must be at least minNotes ${minNotes}; received ${maxNotes}`
1312
- );
1313
- }
1314
- const minOccurrences = _chunkS5BKZZGMcjs.assertInteger.call(void 0,
1315
- _nullishCoalesce(asked.minOccurrences, () => ( MIN_OCCURRENCES)),
1316
- "motif minOccurrences",
1317
- 2
1318
- );
1319
- const sounding = orderedNotes(notes, "melody notes", asked.budget);
1320
- if (sounding.length < minNotes) {
1321
- return [];
1322
- }
1323
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0,
1324
- sounding.length * (maxNotes - minNotes + 1),
1325
- "motif windows",
1326
- asked.budget
1327
- );
1328
- const groups = /* @__PURE__ */ new Map();
1329
- for (let length = minNotes; length <= maxNotes; length += 1) {
1330
- for (let start = 0; start + length <= sounding.length; start += 1) {
1331
- const signature = windowSignature(sounding.slice(start, start + length));
1332
- if (signature === null) {
1333
- continue;
1334
- }
1335
- const group = groups.get(signature);
1336
- if (group === void 0) {
1337
- groups.set(signature, { signature, length, starts: [start] });
1338
- } else {
1339
- group.starts.push(start);
1340
- }
1341
- }
1342
- }
1343
- const candidates = [...groups.values()].map((group) => ({ ...group, starts: nonOverlapping(group.starts, group.length) })).filter((group) => group.starts.length >= minOccurrences).sort(
1344
- (a, b) => b.length - a.length || b.starts.length - a.starts.length || (_nullishCoalesce(a.starts[0], () => ( 0))) - (_nullishCoalesce(b.starts[0], () => ( 0))) || (a.signature < b.signature ? -1 : a.signature > b.signature ? 1 : 0)
1345
- );
1346
- const keptByLength = /* @__PURE__ */ new Map();
1347
- const kept = [];
1348
- let comparisons = 0;
1349
- for (const candidate of candidates) {
1350
- const first = _nullishCoalesce(candidate.starts[0], () => ( 0));
1351
- let covered = false;
1352
- for (let length = candidate.length + 1; length <= maxNotes && !covered; length += 1) {
1353
- const byStart = keptByLength.get(length);
1354
- if (byStart === void 0) {
1355
- continue;
1356
- }
1357
- for (let from = Math.max(0, first - (length - candidate.length)); from <= first; from += 1) {
1358
- comparisons += 1;
1359
- const outer = byStart.get(from);
1360
- if (outer === void 0 || outer.starts.length < candidate.starts.length) {
1361
- continue;
1362
- }
1363
- comparisons += outer.starts.length + candidate.starts.length;
1364
- if (subsumes(outer, candidate)) {
1365
- covered = true;
1366
- break;
1367
- }
1368
- }
1369
- }
1370
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, comparisons, "motif subsumption comparisons", asked.budget);
1371
- if (!covered) {
1372
- kept.push(candidate);
1373
- let byStart = keptByLength.get(candidate.length);
1374
- if (byStart === void 0) {
1375
- byStart = /* @__PURE__ */ new Map();
1376
- keptByLength.set(candidate.length, byStart);
1377
- }
1378
- for (const start of candidate.starts) {
1379
- byStart.set(start, candidate);
1380
- }
1381
- }
1382
- }
1383
- return kept.map((group) => motifFromGroup(group, sounding));
1384
- }
1385
- function motifFromNotes(notes) {
1386
- const sounding = orderedNotes(notes, "motif notes").map((note) => ({ ...note }));
1387
- return motifFromGroup(
1388
- { signature: _nullishCoalesce(windowSignature(sounding), () => ( "")), length: sounding.length, starts: [0] },
1389
- sounding
1390
- );
1391
- }
1392
-
1393
- // src/analyze/melody/relation.ts
1394
- function spellingOf(key) {
1395
- if (key === void 0) {
1396
- return "sharp";
1397
- }
1398
- return _chunk3R3QVKY5cjs.keySignatureFifths.call(void 0, key.tonic, key.scale) < 0 ? "flat" : "sharp";
1399
- }
1400
- function intervalBetween(from, to, key) {
1401
- const spelling = spellingOf(key);
1402
- return _chunkS5BKZZGMcjs.spelledInterval.call(void 0, _chunkS5BKZZGMcjs.midiToNote.call(void 0, from, spelling), _chunkS5BKZZGMcjs.midiToNote.call(void 0, to, spelling));
1403
- }
1404
- function intervalName(interval) {
1405
- return `${interval.descending && interval.semitones !== 0 ? "-" : ""}${interval.quality}${interval.number}`;
1406
- }
1407
- function ladderIndices(notes, key) {
1408
- const root = _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, key.rootPc);
1409
- const offsets = _chunk3R3QVKY5cjs.scaleTonesInDegreeOrder.call(void 0, key).map((pc) => _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, pc - root));
1410
- if (offsets.length === 0) {
1411
- return null;
1412
- }
1413
- const out = [];
1414
- for (const note of notes) {
1415
- const semitones = note.pitch - root;
1416
- const octave = Math.floor(semitones / 12);
1417
- const degree = offsets.indexOf(semitones - octave * 12);
1418
- if (degree < 0) {
1419
- return null;
1420
- }
1421
- out.push(octave * offsets.length + degree);
1422
- }
1423
- return out;
1424
- }
1425
- function ladderSteps(indices) {
1426
- const out = [];
1427
- for (let i = 1; i < indices.length; i += 1) {
1428
- out.push((_nullishCoalesce(indices[i], () => ( 0))) - (_nullishCoalesce(indices[i - 1], () => ( 0))));
1429
- }
1430
- return out;
1431
- }
1432
- function pitchShapes(a, b) {
1433
- const shapes = [];
1434
- if (sameNumbers(a, b)) {
1435
- shapes.push("exact");
1436
- }
1437
- if (sameNumbers(
1438
- b,
1439
- a.map((step) => -step)
1440
- )) {
1441
- shapes.push("inversion");
1442
- }
1443
- const reversed = [...a].reverse();
1444
- if (sameNumbers(
1445
- b,
1446
- reversed.map((step) => -step)
1447
- )) {
1448
- shapes.push("retrograde");
1449
- }
1450
- if (sameNumbers(b, reversed)) {
1451
- shapes.push("retrogradeInversion");
1452
- }
1453
- return shapes;
1454
- }
1455
- function totalGap(gaps) {
1456
- return gaps.reduce((sum, gap) => sum + gap, 0);
1457
- }
1458
- function stretchRatio(modelGaps, answerGaps) {
1459
- const span = totalGap(modelGaps);
1460
- return span > _chunkS5BKZZGMcjs.BEAT_EPS ? totalGap(answerGaps) / span : 1;
1461
- }
1462
- function stretchPhrase(ratio) {
1463
- return `note values ${roundTo(ratio, 3)}x the model's`;
1464
- }
1465
- function relateMotifs(a, b, keyLike) {
1466
- const key = keyLike === void 0 ? void 0 : _chunk3R3QVKY5cjs.resolveKey.call(void 0, keyLike);
1467
- const model = _chunkS5BKZZGMcjs.assertArray.call(void 0, _chunkS5BKZZGMcjs.assertRecord.call(void 0, a, "a").notes, "a.notes");
1468
- const answer = _chunkS5BKZZGMcjs.assertArray.call(void 0, _chunkS5BKZZGMcjs.assertRecord.call(void 0, b, "b").notes, "b.notes");
1469
- if (model.length === 0 || model.length !== answer.length) {
1470
- return null;
1471
- }
1472
- const modelGaps = onsetGaps(model);
1473
- const answerGaps = onsetGaps(answer);
1474
- const modelProfile = rhythmProfile(modelGaps);
1475
- const answerProfile = rhythmProfile(answerGaps);
1476
- const retroGaps = retrogradeGaps(model);
1477
- const retroProfile = rhythmProfile(retroGaps);
1478
- if (modelProfile === null || answerProfile === null) {
1479
- return null;
1480
- }
1481
- const forward = sameNumbers(modelProfile, answerProfile);
1482
- const backward = retroProfile !== null && sameNumbers(retroProfile, answerProfile);
1483
- const semitones = (_nullishCoalesce(_optionalChain([answer, 'access', _83 => _83[0], 'optionalAccess', _84 => _84.pitch]), () => ( 0))) - (_nullishCoalesce(_optionalChain([model, 'access', _85 => _85[0], 'optionalAccess', _86 => _86.pitch]), () => ( 0)));
1484
- const interval = intervalBetween(_nullishCoalesce(_optionalChain([model, 'access', _87 => _87[0], 'optionalAccess', _88 => _88.pitch]), () => ( 0)), _nullishCoalesce(_optionalChain([answer, 'access', _89 => _89[0], 'optionalAccess', _90 => _90.pitch]), () => ( 0)), key);
1485
- const gap = (_nullishCoalesce(_optionalChain([answer, 'access', _91 => _91[0], 'optionalAccess', _92 => _92.startBeat]), () => ( 0))) - endBeatOf(model);
1486
- const sequence = gap >= -_chunkSGLEQVJHcjs.HUMANIZE_ADJACENCY && gap <= _chunkSGLEQVJHcjs.HUMANIZE_ADJACENCY;
1487
- const timeRatio = stretchRatio(modelGaps, answerGaps);
1488
- const stretched = Math.abs(timeRatio - 1) > _chunkS5BKZZGMcjs.BEAT_EPS;
1489
- const shapes = pitchShapes(intervalsOf(model), intervalsOf(answer));
1490
- const base = { sequence, semitones, interval, timeRatio };
1491
- const backwardBase = { ...base, timeRatio: stretchRatio(retroGaps, answerGaps) };
1492
- if (forward && shapes.includes("exact")) {
1493
- if (semitones === 0) {
1494
- if (!stretched) {
1495
- return {
1496
- ...base,
1497
- kind: "repetition",
1498
- rationale: "Exact repetition: the same pitches in the same rhythm"
1499
- };
1500
- }
1501
- const augmented = timeRatio > 1;
1502
- return {
1503
- ...base,
1504
- kind: augmented ? "augmentation" : "diminution",
1505
- rationale: `${augmented ? "Augmentation" : "Diminution"}: the same pitches in ${stretchPhrase(timeRatio)}`
1506
- };
1507
- }
1508
- const name = intervalName(interval);
1509
- return {
1510
- ...base,
1511
- kind: "transposition",
1512
- rationale: sequence ? `Real sequence: the cell restated ${name} away with every interval kept literally` + (stretched ? `, in ${stretchPhrase(timeRatio)}` : "") : `Transposition by ${name}: every interval kept literally` + (stretched ? `, in ${stretchPhrase(timeRatio)}` : "")
1513
- };
1514
- }
1515
- if (forward && key !== void 0) {
1516
- const modelLadder = ladderIndices(model, key.scale);
1517
- const answerLadder = ladderIndices(answer, key.scale);
1518
- if (modelLadder !== null && answerLadder !== null) {
1519
- const degrees = (_nullishCoalesce(answerLadder[0], () => ( 0))) - (_nullishCoalesce(modelLadder[0], () => ( 0)));
1520
- if (degrees !== 0 && sameNumbers(ladderSteps(modelLadder), ladderSteps(answerLadder))) {
1521
- const direction = degrees > 0 ? "higher" : "lower";
1522
- const count = Math.abs(degrees);
1523
- return {
1524
- ...base,
1525
- kind: "tonalTransposition",
1526
- degrees,
1527
- rationale: `${sequence ? "Tonal sequence" : "Tonal transposition"}: the cell restated ${count} scale ${count === 1 ? "degree" : "degrees"} ${direction} inside the key, so the key resizes its intervals \u2014 a real one would have kept them literal`
1528
- };
1529
- }
1530
- }
1531
- }
1532
- if (forward && shapes.includes("inversion")) {
1533
- return {
1534
- ...base,
1535
- kind: "inversion",
1536
- rationale: "Inversion: every interval turned upside down about the first note" + (stretched ? `, in ${stretchPhrase(timeRatio)}` : "")
1537
- };
1538
- }
1539
- if (backward && shapes.includes("retrograde")) {
1540
- return {
1541
- ...backwardBase,
1542
- kind: "retrograde",
1543
- rationale: "Retrograde: the cell read back to front, rhythm included"
1544
- };
1545
- }
1546
- if (backward && shapes.includes("retrogradeInversion")) {
1547
- return {
1548
- ...backwardBase,
1549
- kind: "retrogradeInversion",
1550
- rationale: "Retrograde inversion: the cell read back to front and turned upside down"
1551
- };
1552
- }
1553
- return null;
1554
- }
1555
-
1556
- // src/analyze/melody/similarity.ts
1557
- var INTERVAL_TOLERANCE = 6;
1558
- var PITCH_WEIGHT = 0.75;
1559
- var RHYTHM_WEIGHT = 0.25;
1560
- function gradedEditDistance(a, b, cost) {
1561
- let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
1562
- for (let i = 1; i <= a.length; i += 1) {
1563
- const row = new Array(b.length + 1);
1564
- row[0] = i;
1565
- for (let j = 1; j <= b.length; j += 1) {
1566
- const substitution = (_nullishCoalesce(previous[j - 1], () => ( 0))) + cost(_nullishCoalesce(a[i - 1], () => ( 0)), _nullishCoalesce(b[j - 1], () => ( 0)));
1567
- const deletion = (_nullishCoalesce(previous[j], () => ( 0))) + 1;
1568
- const insertion = (_nullishCoalesce(row[j - 1], () => ( 0))) + 1;
1569
- row[j] = Math.min(substitution, deletion, insertion);
1570
- }
1571
- previous = row;
1572
- }
1573
- return _nullishCoalesce(previous[b.length], () => ( 0));
1574
- }
1575
- function distanceToSimilarity(distance, a, b) {
1576
- const longest = Math.max(a, b);
1577
- if (longest === 0) {
1578
- return 1;
1579
- }
1580
- return Math.min(1, Math.max(0, 1 - distance / longest));
1581
- }
1582
- function intervalCost(x, y) {
1583
- return Math.min(1, Math.abs(x - y) / INTERVAL_TOLERANCE);
1584
- }
1585
- function gapCost(x, y) {
1586
- if (!(x > _chunkS5BKZZGMcjs.BEAT_EPS) || !(y > _chunkS5BKZZGMcjs.BEAT_EPS)) {
1587
- return Math.abs(x - y) > _chunkS5BKZZGMcjs.BEAT_EPS ? 1 : 0;
1588
- }
1589
- return Math.min(1, Math.abs(Math.log2(x / y)));
1590
- }
1591
- function compareMelodies(a, b) {
1592
- const left = orderedNotes(phraseNotes(a), "melody a");
1593
- const right = orderedNotes(phraseNotes(b), "melody b");
1594
- const leftIntervals = intervalsOf(left);
1595
- const rightIntervals = intervalsOf(right);
1596
- const pitchSimilarity = distanceToSimilarity(
1597
- gradedEditDistance(leftIntervals, rightIntervals, intervalCost),
1598
- leftIntervals.length,
1599
- rightIntervals.length
1600
- );
1601
- const leftGaps = _nullishCoalesce(rhythmProfile(onsetGaps(left)), () => ( onsetGaps(left)));
1602
- const rightGaps = _nullishCoalesce(rhythmProfile(onsetGaps(right)), () => ( onsetGaps(right)));
1603
- const rhythmSimilarity = distanceToSimilarity(
1604
- gradedEditDistance(leftGaps, rightGaps, gapCost),
1605
- leftGaps.length,
1606
- rightGaps.length
1607
- );
1608
- const similarity = PITCH_WEIGHT * pitchSimilarity + RHYTHM_WEIGHT * rhythmSimilarity;
1609
- return {
1610
- similarity,
1611
- pitchSimilarity,
1612
- rhythmSimilarity,
1613
- rationale: `Interval shapes ${Math.round(pitchSimilarity * 100)}% alike, rhythms ${Math.round(rhythmSimilarity * 100)}% alike, weighted ${PITCH_WEIGHT}/${RHYTHM_WEIGHT} into ${roundTo(similarity, 3)}`
1614
- };
1615
- }
1616
- function melodicSimilarity(a, b) {
1617
- return compareMelodies(a, b).similarity;
1618
- }
1619
-
1620
1154
  // src/analyze/form/phrase.ts
1621
1155
  var CADENCE_BOUNDARY_STRENGTH = {
1622
1156
  authentic: 0.9,
@@ -1644,6 +1178,7 @@ var PHRASE_CUT_COST = 1.3;
1644
1178
  var CLOSING_SHARE = 0.65;
1645
1179
  var FINAL_PHRASE_BONUS = 0.15;
1646
1180
  var HYPERMETRIC_CLOSE_BONUS = 0.1;
1181
+ var IMPERFECT_CLOSURE_FACTOR = 0.8;
1647
1182
  var EVADED_FACTOR = 0.6;
1648
1183
  var SIGNAL_ORDER = [
1649
1184
  "cadence",
@@ -1654,7 +1189,7 @@ var SIGNAL_ORDER = [
1654
1189
  "hypermeter"
1655
1190
  ];
1656
1191
  function addSignal(boundaries, beat, signal, strength, cadence = null) {
1657
- const key = Math.round(beat / _chunkS5BKZZGMcjs.BEAT_EPS);
1192
+ const key = Math.round(beat / _chunkS3QELJUEcjs.BEAT_EPS);
1658
1193
  const existing = boundaries.get(key);
1659
1194
  const boundary = _nullishCoalesce(existing, () => ( { beat, strengths: /* @__PURE__ */ new Map(), cadence: null }));
1660
1195
  boundary.strengths.set(signal, Math.max(_nullishCoalesce(boundary.strengths.get(signal), () => ( 0)), strength));
@@ -1668,17 +1203,23 @@ function readBoundary(boundary) {
1668
1203
  (a, b) => b[1] - a[1] || SIGNAL_ORDER.indexOf(a[0]) - SIGNAL_ORDER.indexOf(b[0])
1669
1204
  );
1670
1205
  return {
1671
- strength: _chunkSGLEQVJHcjs.combineEvidence.call(void 0, signals.map(([, strength]) => strength)),
1206
+ strength: _chunkLQZQDBCDcjs.combineEvidence.call(void 0, signals.map(([, strength]) => strength)),
1207
+ uncredited: _chunkLQZQDBCDcjs.combineEvidence.call(void 0,
1208
+ signals.filter(([signal]) => signal !== "cadence").map(([, strength]) => strength)
1209
+ ),
1672
1210
  signals: signals.map(([signal]) => signal)
1673
1211
  };
1674
1212
  }
1213
+ function closesPhrase(atBeat, startBeat, endBeat) {
1214
+ return atBeat >= startBeat - _chunkS3QELJUEcjs.BEAT_EPS && atBeat < endBeat - _chunkS3QELJUEcjs.BEAT_EPS;
1215
+ }
1675
1216
  function lengthFit(length, expected) {
1676
1217
  if (!(expected > 0)) {
1677
1218
  return 0;
1678
1219
  }
1679
1220
  return Math.max(-1, 1 - Math.abs(length - expected) / expected);
1680
1221
  }
1681
- function choosePhrasePath(beats, strengths, minPhraseBeats, expected) {
1222
+ function choosePhrasePath(beats, strengths, minPhraseBeats, expected, held) {
1682
1223
  const value = new Array(beats.length).fill(Number.NEGATIVE_INFINITY);
1683
1224
  const cameFrom = new Array(beats.length).fill(-1);
1684
1225
  value[0] = 0;
@@ -1689,10 +1230,12 @@ function choosePhrasePath(beats, strengths, minPhraseBeats, expected) {
1689
1230
  continue;
1690
1231
  }
1691
1232
  const length = endBeat - (_nullishCoalesce(beats[i], () => ( 0)));
1692
- if (length < minPhraseBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
1233
+ if (length < minPhraseBeats - _chunkS3QELJUEcjs.BEAT_EPS) {
1693
1234
  continue;
1694
1235
  }
1695
- const gain = (_nullishCoalesce(value[i], () => ( 0))) + (_nullishCoalesce(strengths[j], () => ( 0))) + lengthFit(length, expected) - PHRASE_CUT_COST;
1236
+ const credited = held === void 0 || closesPhrase(_nullishCoalesce(held.arrivals[j], () => ( endBeat)), _nullishCoalesce(beats[i], () => ( 0)), endBeat);
1237
+ const strength = credited ? _nullishCoalesce(strengths[j], () => ( 0)) : _nullishCoalesce(held.uncredited[j], () => ( 0));
1238
+ const gain = (_nullishCoalesce(value[i], () => ( 0))) + strength + lengthFit(length, expected) - PHRASE_CUT_COST;
1696
1239
  if (gain > (_nullishCoalesce(value[j], () => ( Number.NEGATIVE_INFINITY)))) {
1697
1240
  value[j] = gain;
1698
1241
  cameFrom[j] = i;
@@ -1711,44 +1254,47 @@ function choosePhrasePath(beats, strengths, minPhraseBeats, expected) {
1711
1254
  }
1712
1255
  return path;
1713
1256
  }
1714
- function arrivalEnd(timeline, atBeat) {
1715
- return _nullishCoalesce(_optionalChain([_chunkSGLEQVJHcjs.segmentStartingAt.call(void 0, timeline.segments, atBeat), 'optionalAccess', _93 => _93.endBeat]), () => ( atBeat));
1257
+ function boundaryCadences(timeline, key) {
1258
+ const hits = _chunkLQZQDBCDcjs.detectCadences.call(void 0, timeline, key).filter((hit) => hit.cadence.type !== null);
1259
+ const spans = cadenceSpans(
1260
+ hits.map((hit) => ({
1261
+ atBeat: hit.atBeat,
1262
+ strength: CADENCE_BOUNDARY_STRENGTH[hit.cadence.type]
1263
+ })),
1264
+ timeline.segments
1265
+ );
1266
+ return { hits, spans };
1716
1267
  }
1717
- function barSpanOf(startBeat, endBeat, meters) {
1718
- const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, startBeat, meters);
1719
- const lastBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, endBeat, meters);
1720
- const partOf = (beat, bar) => {
1721
- const barStart = _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar, beat: 0 }, meters);
1722
- const barLength = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, barStart, meters);
1723
- return barLength > 0 ? (beat - barStart) / barLength : 0;
1724
- };
1725
- const bars = lastBar - firstBar + partOf(endBeat, lastBar) - partOf(startBeat, firstBar);
1726
- return Math.round(bars * 100) / 100;
1268
+ function keyContextFor(key, timeline, meters, totalBeats, budget) {
1269
+ if (key !== void 0) {
1270
+ return key;
1271
+ }
1272
+ const regions = _chunkLQZQDBCDcjs.detectModulations.call(void 0, timeline.segments, { meters, totalBeats, budget });
1273
+ const keyAt = _chunkLQZQDBCDcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkLQZQDBCDcjs.prevailingKeyOf.call(void 0, regions), () => ( _chunkYDGMWH43cjs.resolveKey.call(void 0, _chunkYDGMWH43cjs.majorKey.call(void 0, 0)))));
1274
+ return (beat) => _chunkYDGMWH43cjs.scaleOf.call(void 0, keyAt(beat));
1727
1275
  }
1728
- function keyContextFor(opts, notes, meters, totalBeats) {
1729
- if (opts.key !== void 0) {
1730
- return opts.key;
1276
+ function heldCadenceBoundaries(boundaries, hit, span, meters, spanEnd) {
1277
+ const firstLine = _chunkS3QELJUEcjs.barIndexAt.call(void 0, span.atBeat, meters) + 1;
1278
+ const lastLine = _chunkS3QELJUEcjs.barIndexAt.call(void 0, Math.min(span.endBeat, spanEnd), meters);
1279
+ for (let bar = firstLine; bar <= lastLine; bar += 1) {
1280
+ const line = _chunkS3QELJUEcjs.barPositionToBeat.call(void 0, { bar, beat: 0 }, meters);
1281
+ if (line < span.endBeat - _chunkS3QELJUEcjs.BEAT_EPS) {
1282
+ addSignal(boundaries, line, "cadence", span.strength, hit);
1283
+ }
1731
1284
  }
1732
- const regions = _chunkSGLEQVJHcjs.keyTimelineFromNotes.call(void 0, notes, {
1733
- meters,
1734
- totalBeats,
1735
- budget: opts.budget
1736
- });
1737
- const keyAt = _chunkSGLEQVJHcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, regions), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))));
1738
- return (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
1739
1285
  }
1740
1286
  function restBoundaries(boundaries, notes, meters, restBeats) {
1741
1287
  const ordered = [...notes].sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
1742
1288
  let covered = Number.NEGATIVE_INFINITY;
1743
1289
  for (const note of ordered) {
1744
1290
  const gap = note.startBeat - covered;
1745
- if (Number.isFinite(covered) && gap >= restBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
1746
- const bar = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, note.startBeat, meters);
1291
+ if (Number.isFinite(covered) && gap >= restBeats - _chunkS3QELJUEcjs.BEAT_EPS) {
1292
+ const bar = _chunkS3QELJUEcjs.beatsPerBarAt.call(void 0, note.startBeat, meters);
1747
1293
  addSignal(
1748
1294
  boundaries,
1749
1295
  note.startBeat,
1750
1296
  "rest",
1751
- REST_STRENGTH * _chunkSGLEQVJHcjs.clamp01.call(void 0, bar > 0 ? gap / bar : 1)
1297
+ REST_STRENGTH * _chunkLQZQDBCDcjs.clamp01.call(void 0, bar > 0 ? gap / bar : 1)
1752
1298
  );
1753
1299
  }
1754
1300
  covered = Math.max(covered, note.startBeat + note.durationBeat);
@@ -1758,13 +1304,13 @@ function repetitionBoundaries(boundaries, notes, meters, spanStart, spanEnd, win
1758
1304
  if (!(windowBeats > 0)) {
1759
1305
  return;
1760
1306
  }
1761
- const slices = _chunkSGLEQVJHcjs.sliceBars.call(void 0, notes, meters, spanStart, spanEnd, budget);
1762
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, slices.length * notes.length, "form repetition comparisons", budget);
1763
- const onsetsIn = (from, to) => notes.filter((note) => note.startBeat >= from - _chunkS5BKZZGMcjs.BEAT_EPS && note.startBeat < to - _chunkS5BKZZGMcjs.BEAT_EPS);
1307
+ const slices = _chunkLQZQDBCDcjs.sliceBars.call(void 0, notes, meters, spanStart, spanEnd, budget);
1308
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, slices.length * notes.length, "form repetition comparisons", budget);
1309
+ const onsetsIn = (from, to) => notes.filter((note) => note.startBeat >= from - _chunkS3QELJUEcjs.BEAT_EPS && note.startBeat < to - _chunkS3QELJUEcjs.BEAT_EPS);
1764
1310
  let comparisons = 0;
1765
1311
  for (const slice of slices) {
1766
1312
  const beat = slice.startBeat;
1767
- if (beat - windowBeats < spanStart - _chunkS5BKZZGMcjs.BEAT_EPS || beat + windowBeats > spanEnd + _chunkS5BKZZGMcjs.BEAT_EPS) {
1313
+ if (beat - windowBeats < spanStart - _chunkS3QELJUEcjs.BEAT_EPS || beat + windowBeats > spanEnd + _chunkS3QELJUEcjs.BEAT_EPS) {
1768
1314
  continue;
1769
1315
  }
1770
1316
  const before = onsetsIn(beat - windowBeats, beat);
@@ -1773,8 +1319,8 @@ function repetitionBoundaries(boundaries, notes, meters, spanStart, spanEnd, win
1773
1319
  continue;
1774
1320
  }
1775
1321
  comparisons += before.length * after.length;
1776
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, comparisons, "form repetition melody comparisons", budget);
1777
- const similarity = melodicSimilarity(before, after);
1322
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, comparisons, "form repetition melody comparisons", budget);
1323
+ const similarity = _chunkLQZQDBCDcjs.melodicSimilarity.call(void 0, before, after);
1778
1324
  if (similarity >= REPETITION_THRESHOLD) {
1779
1325
  addSignal(boundaries, beat, "repetition", REPETITION_STRENGTH * similarity);
1780
1326
  }
@@ -1790,7 +1336,7 @@ function structuralWeightOf(cadence, confidence, aligned, isLast) {
1790
1336
  if (result.strength === "perfect") {
1791
1337
  base = 1;
1792
1338
  } else if (result.strength === "imperfect") {
1793
- base *= 0.8;
1339
+ base *= IMPERFECT_CLOSURE_FACTOR;
1794
1340
  }
1795
1341
  }
1796
1342
  if (result.evaded) {
@@ -1803,7 +1349,7 @@ function structuralWeightOf(cadence, confidence, aligned, isLast) {
1803
1349
  if (aligned) {
1804
1350
  weight += HYPERMETRIC_CLOSE_BONUS;
1805
1351
  }
1806
- return _chunkSGLEQVJHcjs.clamp01.call(void 0, weight);
1352
+ return _chunkLQZQDBCDcjs.clamp01.call(void 0, weight);
1807
1353
  }
1808
1354
  function describeCadence(cadence) {
1809
1355
  if (cadence === null || cadence.cadence.type === null) {
@@ -1814,72 +1360,61 @@ function describeCadence(cadence) {
1814
1360
  return `a ${graded} cadence at beat ${cadence.atBeat}`;
1815
1361
  }
1816
1362
  function phrasesFromTimeline(timeline, notes, opts = {}) {
1817
- _chunkSGLEQVJHcjs.assertChordTimeline.call(void 0, timeline);
1818
- const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "phrase meters");
1819
- _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "phrase notes", {
1363
+ _chunkLQZQDBCDcjs.assertChordTimeline.call(void 0, timeline);
1364
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, opts, "phrase meters");
1365
+ _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, "phrase notes", {
1820
1366
  allowNonPositiveDuration: true,
1821
1367
  budget: opts.budget
1822
1368
  });
1823
1369
  if (opts.hypermeter !== void 0) {
1824
- _chunkS5BKZZGMcjs.assertRange.call(void 0, opts.hypermeter.confidence, 0, 1, "phrase hypermeter confidence");
1825
- _chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, opts.hypermeter.groupBars, "phrase hypermeter groupBars");
1370
+ _chunkS3QELJUEcjs.assertRange.call(void 0, opts.hypermeter.confidence, 0, 1, "phrase hypermeter confidence");
1371
+ _chunkS3QELJUEcjs.assertPositiveInt.call(void 0, opts.hypermeter.groupBars, "phrase hypermeter groupBars");
1826
1372
  }
1827
1373
  const sounding = notes.filter((note) => note.durationBeat > 0);
1828
1374
  const segments = timeline.segments;
1829
1375
  const firstSegment = segments[0];
1830
1376
  const lastSegment = segments[segments.length - 1];
1831
- const firstOnset = sounding.reduce(
1832
- (first, n) => Math.min(first, n.startBeat),
1833
- _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _94 => _94.startBeat]), () => ( 0))
1377
+ const earliest = Math.min(
1378
+ _chunkLQZQDBCDcjs.firstSoundingBeat.call(void 0, sounding, Number.POSITIVE_INFINITY),
1379
+ _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _44 => _44.startBeat]), () => ( Number.POSITIVE_INFINITY))
1834
1380
  );
1835
- const spanStart = _chunkSGLEQVJHcjs.barGridStart.call(void 0, firstOnset, meters);
1836
- const notesEnd = sounding.reduce(
1837
- (end, n) => Math.max(end, n.startBeat + n.durationBeat),
1838
- _nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _95 => _95.endBeat]), () => ( 0))
1381
+ const firstOnset = Number.isFinite(earliest) ? earliest : 0;
1382
+ const spanStart = _chunkLQZQDBCDcjs.barGridStart.call(void 0, firstOnset, meters);
1383
+ const latestEnd = Math.max(
1384
+ _nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _45 => _45.endBeat]), () => ( Number.NEGATIVE_INFINITY)),
1385
+ _chunkLQZQDBCDcjs.latestSoundingEnd.call(void 0, sounding, Number.NEGATIVE_INFINITY)
1839
1386
  );
1840
- const spanEnd = Math.max(spanStart, _nullishCoalesce(opts.totalBeats, () => ( notesEnd)));
1841
- _chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "phrase totalBeats");
1842
- if (spanEnd <= spanStart + _chunkS5BKZZGMcjs.BEAT_EPS) {
1387
+ const notesEnd = Number.isFinite(latestEnd) ? latestEnd : 0;
1388
+ const spanEnd = _chunkLQZQDBCDcjs.resolveSpanEnd.call(void 0, opts.totalBeats, spanStart, notesEnd, "phrase totalBeats");
1389
+ if (spanEnd <= spanStart + _chunkS3QELJUEcjs.BEAT_EPS) {
1843
1390
  return [];
1844
1391
  }
1845
- const barBeats = _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, Math.max(0, spanStart), meters);
1392
+ const barBeats = _chunkS3QELJUEcjs.beatsPerBarAt.call(void 0, Math.max(0, spanStart), meters);
1846
1393
  const restBeats = _nullishCoalesce(opts.restBeats, () => ( barBeats / 2));
1847
- _chunkS5BKZZGMcjs.assertRange.call(void 0, restBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase restBeats");
1394
+ _chunkS3QELJUEcjs.assertRange.call(void 0, restBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase restBeats");
1848
1395
  const longNoteBeats = _nullishCoalesce(opts.longNoteBeats, () => ( barBeats));
1849
- _chunkS5BKZZGMcjs.assertRange.call(void 0, longNoteBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase longNoteBeats");
1396
+ _chunkS3QELJUEcjs.assertRange.call(void 0, longNoteBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase longNoteBeats");
1850
1397
  const minPhraseBeats = _nullishCoalesce(opts.minPhraseBeats, () => ( barBeats));
1851
- _chunkS5BKZZGMcjs.assertRange.call(void 0, minPhraseBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase minPhraseBeats");
1852
- const key = keyContextFor(opts, sounding, meters, spanEnd);
1853
- const cadences = _chunkSGLEQVJHcjs.detectCadences.call(void 0, timeline, key);
1854
- const grouping = _nullishCoalesce(opts.hypermeter, () => ( hypermeter(sounding, meters, {
1855
- totalBeats: spanEnd,
1856
- cadenceBeats: cadences.map((hit) => hit.atBeat),
1857
- budget: opts.budget
1858
- })));
1859
- const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, Math.max(0, spanStart), meters);
1398
+ _chunkS3QELJUEcjs.assertRange.call(void 0, minPhraseBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase minPhraseBeats");
1399
+ const key = keyContextFor(opts.key, timeline, meters, spanEnd, opts.budget);
1400
+ const { hits: cadences, spans } = boundaryCadences(timeline, key);
1401
+ const grouping = _nullishCoalesce(opts.hypermeter, () => ( hypermeterOver(sounding, meters, { totalBeats: spanEnd, budget: opts.budget }, () => spans)));
1402
+ const firstBar = _chunkS3QELJUEcjs.barIndexAt.call(void 0, Math.max(0, spanStart), meters);
1860
1403
  const hyperBeats = Math.max(
1861
1404
  barBeats,
1862
- _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: firstBar + grouping.groupBars, beat: 0 }, meters) - _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: firstBar, beat: 0 }, meters)
1405
+ _chunkS3QELJUEcjs.barPositionToBeat.call(void 0, { bar: firstBar + grouping.groupBars, beat: 0 }, meters) - _chunkS3QELJUEcjs.barPositionToBeat.call(void 0, { bar: firstBar, beat: 0 }, meters)
1863
1406
  );
1864
1407
  const expected = _nullishCoalesce(opts.expectedPhraseBeats, () => ( hyperBeats));
1865
- _chunkS5BKZZGMcjs.assertRange.call(void 0, expected, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase expectedPhraseBeats");
1408
+ _chunkS3QELJUEcjs.assertRange.call(void 0, expected, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase expectedPhraseBeats");
1866
1409
  const boundaries = /* @__PURE__ */ new Map();
1867
- for (const hit of cadences) {
1868
- const type = hit.cadence.type;
1869
- if (type === null) {
1870
- continue;
1871
- }
1872
- const strength = CADENCE_BOUNDARY_STRENGTH[type];
1873
- const chordEnd = arrivalEnd(timeline, hit.atBeat);
1874
- addSignal(boundaries, chordEnd, "cadence", strength, hit);
1875
- const barEnd = _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: _chunkS5BKZZGMcjs.barIndexAt.call(void 0, hit.atBeat, meters) + 1, beat: 0 }, meters);
1876
- if (chordEnd > barEnd + _chunkS5BKZZGMcjs.BEAT_EPS) {
1877
- addSignal(boundaries, barEnd, "cadence", strength, hit);
1878
- }
1879
- }
1410
+ cadences.forEach((hit, index) => {
1411
+ const span = spans[index];
1412
+ addSignal(boundaries, span.endBeat, "cadence", span.strength, hit);
1413
+ heldCadenceBoundaries(boundaries, hit, span, meters, spanEnd);
1414
+ });
1880
1415
  restBoundaries(boundaries, sounding, meters, restBeats);
1881
1416
  for (const note of sounding) {
1882
- if (note.durationBeat >= longNoteBeats - _chunkS5BKZZGMcjs.BEAT_EPS) {
1417
+ if (note.durationBeat >= longNoteBeats - _chunkS3QELJUEcjs.BEAT_EPS) {
1883
1418
  addSignal(boundaries, note.startBeat + note.durationBeat, "longNote", LONG_NOTE_STRENGTH);
1884
1419
  }
1885
1420
  }
@@ -1889,9 +1424,9 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1889
1424
  }
1890
1425
  addSignal(boundaries, spanEnd, "pieceEnd", PIECE_END_STRENGTH);
1891
1426
  const candidates = [...boundaries.values()].filter(
1892
- (boundary) => boundary.beat > spanStart + _chunkS5BKZZGMcjs.BEAT_EPS && boundary.beat < spanEnd - _chunkS5BKZZGMcjs.BEAT_EPS
1427
+ (boundary) => boundary.beat > spanStart + _chunkS3QELJUEcjs.BEAT_EPS && boundary.beat < spanEnd - _chunkS3QELJUEcjs.BEAT_EPS
1893
1428
  ).filter((boundary) => readBoundary(boundary).strength > 0).sort((a, b) => a.beat - b.beat);
1894
- const endBoundary = _nullishCoalesce(boundaries.get(Math.round(spanEnd / _chunkS5BKZZGMcjs.BEAT_EPS)), () => ( {
1429
+ const endBoundary = _nullishCoalesce(boundaries.get(Math.round(spanEnd / _chunkS3QELJUEcjs.BEAT_EPS)), () => ( {
1895
1430
  beat: spanEnd,
1896
1431
  strengths: /* @__PURE__ */ new Map(),
1897
1432
  cadence: null
@@ -1901,40 +1436,45 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1901
1436
  ...candidates,
1902
1437
  endBoundary
1903
1438
  ];
1904
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, stops.length * stops.length, "form phrase boundary pairs", opts.budget);
1439
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, stops.length * stops.length, "form phrase boundary pairs", opts.budget);
1905
1440
  const readings = stops.map(readBoundary);
1906
1441
  const path = choosePhrasePath(
1907
1442
  stops.map((stop) => stop.beat),
1908
1443
  readings.map((reading) => reading.strength),
1909
1444
  minPhraseBeats,
1910
- expected
1445
+ expected,
1446
+ {
1447
+ arrivals: stops.map((stop) => _nullishCoalesce(_optionalChain([stop, 'access', _46 => _46.cadence, 'optionalAccess', _47 => _47.atBeat]), () => ( stop.beat))),
1448
+ uncredited: readings.map((reading) => reading.uncredited)
1449
+ }
1911
1450
  );
1912
1451
  const downbeatKeys = new Set(
1913
- grouping.downbeats.map((downbeat) => Math.round(downbeat / _chunkS5BKZZGMcjs.BEAT_EPS))
1452
+ grouping.downbeats.map((downbeat) => Math.round(downbeat / _chunkS3QELJUEcjs.BEAT_EPS))
1914
1453
  );
1915
1454
  const phrases = [];
1916
1455
  let from = 0;
1917
1456
  for (let n = 0; n < path.length; n += 1) {
1918
1457
  const to = _nullishCoalesce(path[n], () => ( stops.length - 1));
1919
- const startBeat = _nullishCoalesce(_optionalChain([stops, 'access', _96 => _96[from], 'optionalAccess', _97 => _97.beat]), () => ( spanStart));
1458
+ const startBeat = _nullishCoalesce(_optionalChain([stops, 'access', _48 => _48[from], 'optionalAccess', _49 => _49.beat]), () => ( spanStart));
1920
1459
  const stop = stops[to];
1921
- const endBeat = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _98 => _98.beat]), () => ( spanEnd));
1922
- const reading = _nullishCoalesce(readings[to], () => ( { strength: 0, signals: [] }));
1460
+ const endBeat = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _50 => _50.beat]), () => ( spanEnd));
1461
+ const reading = _nullishCoalesce(readings[to], () => ( { strength: 0, uncredited: 0, signals: [] }));
1923
1462
  const length = endBeat - startBeat;
1924
- const confidence = _chunkSGLEQVJHcjs.clamp01.call(void 0,
1925
- CLOSING_SHARE * reading.strength + (1 - CLOSING_SHARE) * Math.max(0, lengthFit(length, expected))
1926
- );
1927
1463
  const isLast = n === path.length - 1;
1928
- const closing = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _99 => _99.cadence]), () => ( null));
1929
- const cadence = closing !== null && closing.atBeat >= startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && closing.atBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS ? closing : null;
1464
+ const closing = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _51 => _51.cadence]), () => ( null));
1465
+ const cadence = closing !== null && closesPhrase(closing.atBeat, startBeat, endBeat) ? closing : null;
1466
+ const closingStrength = cadence === null ? reading.uncredited : reading.strength;
1467
+ const confidence = _chunkLQZQDBCDcjs.clamp01.call(void 0,
1468
+ CLOSING_SHARE * closingStrength + (1 - CLOSING_SHARE) * Math.max(0, lengthFit(length, expected))
1469
+ );
1930
1470
  const structuralWeight = structuralWeightOf(
1931
1471
  cadence,
1932
1472
  confidence,
1933
- downbeatKeys.has(Math.round(endBeat / _chunkS5BKZZGMcjs.BEAT_EPS)),
1473
+ downbeatKeys.has(Math.round(endBeat / _chunkS3QELJUEcjs.BEAT_EPS)),
1934
1474
  isLast
1935
1475
  );
1936
1476
  const signals = cadence === null ? reading.signals.filter((signal) => signal !== "cadence") : reading.signals;
1937
- const bars = barSpanOf(startBeat, endBeat, meters);
1477
+ const bars = Math.round(_chunkLQZQDBCDcjs.barSpanOf.call(void 0, startBeat, endBeat, meters) * 100) / 100;
1938
1478
  phrases.push({
1939
1479
  startBeat,
1940
1480
  endBeat,
@@ -1949,10 +1489,10 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1949
1489
  return phrases;
1950
1490
  }
1951
1491
  function structuralCadences(phrases) {
1952
- _chunkS5BKZZGMcjs.assertArray.call(void 0, phrases, "phrases");
1492
+ _chunkS3QELJUEcjs.assertArray.call(void 0, phrases, "phrases");
1953
1493
  const ranked = [];
1954
1494
  for (let index = 0; index < phrases.length; index += 1) {
1955
- const phrase = _chunkS5BKZZGMcjs.assertRecord.call(void 0, phrases[index], `phrases[${index}]`);
1495
+ const phrase = _chunkS3QELJUEcjs.assertRecord.call(void 0, phrases[index], `phrases[${index}]`);
1956
1496
  if (phrase.cadence === null || phrase.cadence === void 0 || phrase.cadence.cadence.type === null) {
1957
1497
  continue;
1958
1498
  }
@@ -1986,50 +1526,55 @@ function isSilent(unit) {
1986
1526
  return unit.notes.length === 0;
1987
1527
  }
1988
1528
  function unitSimilarity(a, b) {
1989
- const harmony = _chunkSGLEQVJHcjs.weightSimilarity.call(void 0, a.weights, b.weights);
1529
+ const harmony = _chunkLQZQDBCDcjs.weightSimilarity.call(void 0, a.weights, b.weights);
1990
1530
  if (a.notes.length < 2 || b.notes.length < 2) {
1991
1531
  return a.notes.length === b.notes.length ? harmony : 0;
1992
1532
  }
1993
- const melody = melodicSimilarity(a.notes, b.notes);
1994
- return _chunkSGLEQVJHcjs.clamp01.call(void 0, MELODY_SHARE * melody + HARMONY_SHARE * harmony);
1533
+ const melody = _chunkLQZQDBCDcjs.melodicSimilarity.call(void 0, a.notes, b.notes);
1534
+ return _chunkLQZQDBCDcjs.clamp01.call(void 0, MELODY_SHARE * melody + HARMONY_SHARE * harmony);
1995
1535
  }
1996
1536
  function sectionsFromNotes(notes, opts = {}) {
1997
- const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "section meters");
1998
- _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "section notes", {
1537
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, opts, "section meters");
1538
+ _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, "section notes", {
1999
1539
  allowNonPositiveDuration: true,
2000
1540
  budget: opts.budget
2001
1541
  });
2002
1542
  const sounding = notes.filter((note) => note.durationBeat > 0);
1543
+ const firstSounding = _chunkLQZQDBCDcjs.firstSoundingBeat.call(void 0, sounding);
1544
+ const firstOnset = _chunkLQZQDBCDcjs.barGridStart.call(void 0, firstSounding, meters);
1545
+ const spanEnd = _chunkLQZQDBCDcjs.resolveSpanEnd.call(void 0,
1546
+ opts.totalBeats,
1547
+ firstOnset,
1548
+ _chunkLQZQDBCDcjs.latestSoundingEnd.call(void 0, sounding),
1549
+ "section totalBeats"
1550
+ );
2003
1551
  if (sounding.length === 0) {
2004
1552
  return [];
2005
1553
  }
2006
- const firstOnset = _chunkSGLEQVJHcjs.barGridStart.call(void 0, _chunkSGLEQVJHcjs.firstSoundingBeat.call(void 0, sounding), meters);
2007
- const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
2008
- const spanEnd = Math.max(firstOnset, _nullishCoalesce(opts.totalBeats, () => ( lastEnd)));
2009
- _chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "section totalBeats");
2010
1554
  const threshold = _nullishCoalesce(opts.threshold, () => ( DEFAULT_THRESHOLD));
2011
- _chunkS5BKZZGMcjs.assertRange.call(void 0, threshold, 0, 1, "section threshold");
1555
+ _chunkS3QELJUEcjs.assertRange.call(void 0, threshold, 0, 1, "section threshold");
2012
1556
  const unitBars = _nullishCoalesce(opts.unitBars, () => ( hypermeter(sounding, meters, { totalBeats: spanEnd, budget: opts.budget }).groupBars));
2013
- _chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, unitBars, "section unitBars");
2014
- const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, firstOnset, meters);
2015
- const lastBar = _chunkSGLEQVJHcjs.lastBarOf.call(void 0, meters, firstOnset, spanEnd);
1557
+ _chunkS3QELJUEcjs.assertPositiveInt.call(void 0, unitBars, "section unitBars");
1558
+ const firstBar = _chunkS3QELJUEcjs.barIndexAt.call(void 0, firstOnset, meters);
1559
+ const lastBar = _chunkLQZQDBCDcjs.lastBarOf.call(void 0, meters, firstOnset, spanEnd);
2016
1560
  const startBar = Math.max(0, firstBar);
2017
1561
  const unitCount = Math.max(1, Math.ceil((lastBar - startBar + 1) / unitBars));
2018
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, unitCount * unitCount, "form section comparisons", opts.budget);
2019
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, unitCount * sounding.length, "form section unit notes", opts.budget);
1562
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, unitCount * unitCount, "form section comparisons", opts.budget);
1563
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, unitCount * sounding.length, "form section unit notes", opts.budget);
2020
1564
  const units = [];
2021
1565
  for (let index = 0; index < unitCount; index += 1) {
2022
1566
  const barStart = startBar + index * unitBars;
2023
- const startBeat = index === 0 ? firstOnset : _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: barStart, beat: 0 }, meters);
2024
- const endBeat = index === unitCount - 1 ? spanEnd : _chunkS5BKZZGMcjs.barPositionToBeat.call(void 0, { bar: barStart + unitBars, beat: 0 }, meters);
1567
+ const startBeat = index === 0 ? firstOnset : _chunkS3QELJUEcjs.barPositionToBeat.call(void 0, { bar: barStart, beat: 0 }, meters);
1568
+ const endBeat = index === unitCount - 1 ? spanEnd : _chunkS3QELJUEcjs.barPositionToBeat.call(void 0, { bar: barStart + unitBars, beat: 0 }, meters);
1569
+ const fromBeat = index === 0 ? Math.min(firstSounding, startBeat) : startBeat;
2025
1570
  const unitNotes = sounding.filter(
2026
- (note) => note.startBeat >= startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && note.startBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
1571
+ (note) => note.startBeat >= fromBeat - _chunkS3QELJUEcjs.BEAT_EPS && note.startBeat < endBeat - _chunkS3QELJUEcjs.BEAT_EPS
2027
1572
  ).sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
2028
1573
  units.push({
2029
1574
  startBeat,
2030
1575
  endBeat,
2031
1576
  notes: unitNotes,
2032
- weights: _chunkSGLEQVJHcjs.windowWeights.call(void 0, unitNotes, startBeat, endBeat, meters).weights
1577
+ weights: _chunkLQZQDBCDcjs.windowWeights.call(void 0, unitNotes, startBeat, endBeat, meters).weights
2033
1578
  });
2034
1579
  }
2035
1580
  let comparisons = 0;
@@ -2038,7 +1583,7 @@ function sectionsFromNotes(notes, opts = {}) {
2038
1583
  comparisons += unit.notes.length * earlierNotes;
2039
1584
  earlierNotes += unit.notes.length;
2040
1585
  }
2041
- _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, comparisons, "form section melody comparisons", opts.budget);
1586
+ _chunkS3QELJUEcjs.assertGenerationBudget.call(void 0, comparisons, "form section melody comparisons", opts.budget);
2042
1587
  const labels = [];
2043
1588
  const scores = [];
2044
1589
  let distinct = 0;
@@ -2093,8 +1638,8 @@ function sectionsFromNotes(notes, opts = {}) {
2093
1638
  while (end < units.length && labels[end] === null) {
2094
1639
  end += 1;
2095
1640
  }
2096
- const startBeat = _nullishCoalesce(_optionalChain([units, 'access', _100 => _100[index], 'optionalAccess', _101 => _101.startBeat]), () => ( 0));
2097
- const endBeat = _nullishCoalesce(_optionalChain([units, 'access', _102 => _102[end - 1], 'optionalAccess', _103 => _103.endBeat]), () => ( spanEnd));
1641
+ const startBeat = _nullishCoalesce(_optionalChain([units, 'access', _52 => _52[index], 'optionalAccess', _53 => _53.startBeat]), () => ( 0));
1642
+ const endBeat = _nullishCoalesce(_optionalChain([units, 'access', _54 => _54[end - 1], 'optionalAccess', _55 => _55.endBeat]), () => ( spanEnd));
2098
1643
  const sectionFirstBar = startBar + index * unitBars;
2099
1644
  const sectionLastBar = Math.max(
2100
1645
  sectionFirstBar,
@@ -2105,7 +1650,7 @@ function sectionsFromNotes(notes, opts = {}) {
2105
1650
  if (first === void 0) {
2106
1651
  firstByLabel.set(label, sectionIndex);
2107
1652
  }
2108
- const similarity = first === void 0 ? 1 : _chunkSGLEQVJHcjs.clamp01.call(void 0, scoreSum / Math.max(1, sounded));
1653
+ const similarity = first === void 0 ? 1 : _chunkLQZQDBCDcjs.clamp01.call(void 0, scoreSum / Math.max(1, sounded));
2109
1654
  sections.push({
2110
1655
  label,
2111
1656
  startBeat,
@@ -2113,163 +1658,280 @@ function sectionsFromNotes(notes, opts = {}) {
2113
1658
  bars: sectionLastBar - sectionFirstBar + 1,
2114
1659
  firstOccurrence: _nullishCoalesce(first, () => ( sectionIndex)),
2115
1660
  similarity,
2116
- rationale: first === void 0 ? `Section ${label}: material first heard at beat ${startBeat}` : `Section ${label}: ${Math.round(similarity * 100)}% alike the ${label} at beat ${_nullishCoalesce(_optionalChain([sections, 'access', _104 => _104[first], 'optionalAccess', _105 => _105.startBeat]), () => ( 0))}`
1661
+ rationale: first === void 0 ? `Section ${label}: material first heard at beat ${startBeat}` : `Section ${label}: ${Math.round(similarity * 100)}% alike the ${label} at beat ${_nullishCoalesce(_optionalChain([sections, 'access', _56 => _56[first], 'optionalAccess', _57 => _57.startBeat]), () => ( 0))}`
2117
1662
  });
2118
1663
  index = end;
2119
1664
  }
2120
1665
  return sections;
2121
1666
  }
2122
1667
 
2123
- // src/analyze/reduction/index.ts
2124
- var TONIC_ARRIVING = /* @__PURE__ */ new Set(["authentic", "plagal", "modal", "deceptive"]);
2125
- function signedStep(fromPc, toPc) {
2126
- return (_chunkS5BKZZGMcjs.pitchClassOf.call(void 0, toPc) - _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, fromPc) + 18) % 12 - 6;
1668
+ // src/analyze/reference/compare.ts
1669
+ var RATIONALE_FIELDS = 2;
1670
+ function rationaleOf(comparison) {
1671
+ const measured = [];
1672
+ for (const group of [comparison.form, comparison.harmony, comparison.melody, comparison.rhythm]) {
1673
+ for (const [name, value] of Object.entries(group)) {
1674
+ if (value !== null) {
1675
+ measured.push({ name, value, order: measured.length });
1676
+ }
1677
+ }
1678
+ }
1679
+ if (measured.length === 0) {
1680
+ return "Nothing to compare: neither profile has material for any field";
1681
+ }
1682
+ const describe = (entries) => entries.map((entry) => `${entry.name} ${Math.round(entry.value * 100)}%`).join(", ");
1683
+ const highest = [...measured].sort((x, y) => y.value - x.value || x.order - y.order);
1684
+ const lowest = [...measured].sort((x, y) => x.value - y.value || x.order - y.order);
1685
+ return `Most alike: ${describe(highest.slice(0, RATIONALE_FIELDS))}; least alike: ${describe(lowest.slice(0, RATIONALE_FIELDS))}`;
1686
+ }
1687
+ function compareReferences(a, b, opts) {
1688
+ _chunkLQZQDBCDcjs.assertReferenceProfile.call(void 0, a, "reference profile a");
1689
+ _chunkLQZQDBCDcjs.assertReferenceProfile.call(void 0, b, "reference profile b");
1690
+ const { budget } = _chunkS3QELJUEcjs.assertOptions.call(void 0, opts, "compare options");
1691
+ const structural = (profile) => profile.harmony.chords.filter((chord) => chord.level === "structural");
1692
+ const cadences = (profile) => profile.form.phrases.map((phrase) => _nullishCoalesce(_optionalChain([phrase, 'access', _58 => _58.cadence, 'optionalAccess', _59 => _59.type]), () => ( null)));
1693
+ const phraseMelodies = (profile) => profile.form.phrases.map((phrase) => phrase.melody);
1694
+ const comparison = {
1695
+ form: {
1696
+ sectionSequenceSimilarity: _chunkLQZQDBCDcjs.sectionSequenceSimilarity.call(void 0,
1697
+ a.form.sections.map((section) => section.label),
1698
+ b.form.sections.map((section) => section.label)
1699
+ ),
1700
+ phraseLengthSimilarity: _chunkLQZQDBCDcjs.phraseLengthSimilarity.call(void 0,
1701
+ a.form.phrases.map((phrase) => phrase.bars),
1702
+ b.form.phrases.map((phrase) => phrase.bars)
1703
+ )
1704
+ },
1705
+ harmony: {
1706
+ progressionSimilarity: _chunkLQZQDBCDcjs.progressionSimilarity.call(void 0, structural(a), structural(b), budget),
1707
+ functionSimilarity: _chunkLQZQDBCDcjs.functionSimilarity.call(void 0, a.harmony.chords, b.harmony.chords),
1708
+ cadenceSimilarity: _chunkLQZQDBCDcjs.cadenceSimilarity.call(void 0, cadences(a), cadences(b)),
1709
+ keyPlanSimilarity: _chunkLQZQDBCDcjs.keyPlanSimilarity.call(void 0,
1710
+ a.harmony.keys.map((region) => region.key),
1711
+ b.harmony.keys.map((region) => region.key)
1712
+ ),
1713
+ harmonicRhythmOnsetSimilarity: _chunkLQZQDBCDcjs.onsetSimilarity.call(void 0, a.harmony.rhythm, b.harmony.rhythm),
1714
+ harmonicRhythmDensitySimilarity: _chunkLQZQDBCDcjs.densitySimilarity.call(void 0, a.harmony.rhythm, b.harmony.rhythm)
1715
+ },
1716
+ melody: {
1717
+ surfaceSimilarity: _chunkLQZQDBCDcjs.surfaceSimilarity.call(void 0, a.melody.motifs, b.melody.motifs),
1718
+ motifStructureSimilarity: _chunkLQZQDBCDcjs.motifStructureSimilarity.call(void 0,
1719
+ { graph: a.melody.graph, spanBeats: a.span.endBeat - a.span.startBeat },
1720
+ { graph: b.melody.graph, spanBeats: b.span.endBeat - b.span.startBeat }
1721
+ ),
1722
+ contourSimilarity: _chunkLQZQDBCDcjs.contourSimilarity.call(void 0, phraseMelodies(a), phraseMelodies(b)),
1723
+ registerSimilarity: _chunkLQZQDBCDcjs.registerSimilarity.call(void 0,
1724
+ { phrases: phraseMelodies(a), mean: _nullishCoalesce(_optionalChain([a, 'access', _60 => _60.melody, 'access', _61 => _61.register, 'optionalAccess', _62 => _62.mean]), () => ( null)) },
1725
+ { phrases: phraseMelodies(b), mean: _nullishCoalesce(_optionalChain([b, 'access', _63 => _63.melody, 'access', _64 => _64.register, 'optionalAccess', _65 => _65.mean]), () => ( null)) }
1726
+ )
1727
+ },
1728
+ rhythm: {
1729
+ onsetSimilarity: _chunkLQZQDBCDcjs.onsetSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
1730
+ densitySimilarity: _chunkLQZQDBCDcjs.densitySimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
1731
+ durationSimilarity: _chunkLQZQDBCDcjs.durationSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
1732
+ syncopationSimilarity: _chunkLQZQDBCDcjs.syncopationSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm)
1733
+ }
1734
+ };
1735
+ return { ...comparison, rationale: rationaleOf(comparison) };
2127
1736
  }
2128
- function isStep(distance) {
2129
- const size = Math.abs(distance);
2130
- return size === 1 || size === 2;
1737
+
1738
+ // src/analyze/reference/profile.ts
1739
+ function spanOf(sounding, timeline, meters, totalBeats) {
1740
+ const segments = timeline.segments;
1741
+ const firstSegment = segments[0];
1742
+ const lastSegment = segments[segments.length - 1];
1743
+ const firstOnset = _chunkLQZQDBCDcjs.firstSoundingBeat.call(void 0, sounding, _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _66 => _66.startBeat]), () => ( 0)));
1744
+ const startBeat = _chunkLQZQDBCDcjs.barGridStart.call(void 0,
1745
+ Math.min(firstOnset, _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _67 => _67.startBeat]), () => ( firstOnset))),
1746
+ meters
1747
+ );
1748
+ const latestEnd = Math.max(
1749
+ _nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _68 => _68.endBeat]), () => ( Number.NEGATIVE_INFINITY)),
1750
+ _chunkLQZQDBCDcjs.latestSoundingEnd.call(void 0, sounding, Number.NEGATIVE_INFINITY)
1751
+ );
1752
+ const notesEnd = Number.isFinite(latestEnd) ? latestEnd : 0;
1753
+ return { startBeat, endBeat: Math.max(startBeat, _nullishCoalesce(totalBeats, () => ( notesEnd))) };
2131
1754
  }
2132
- function touches(before, after) {
2133
- return Math.abs(after.startBeat - before.endBeat) <= _chunkS5BKZZGMcjs.BEAT_EPS;
1755
+ function sameKey(a, b) {
1756
+ return _chunkS3QELJUEcjs.pitchClassOf.call(void 0, a.scale.rootPc) === _chunkS3QELJUEcjs.pitchClassOf.call(void 0, b.scale.rootPc) && a.scale.modeMask12 === b.scale.modeMask12;
2134
1757
  }
2135
- function salienceFloors(durations) {
2136
- const order = durations.map((_, index) => index).sort((a, b) => {
2137
- const left = _nullishCoalesce(durations[a], () => ( 0));
2138
- const right = _nullishCoalesce(durations[b], () => ( 0));
2139
- return left - right || a - b;
1758
+ function keyIndexAt(regions, beat, prevailing) {
1759
+ const holding = regions.findIndex((region) => region.startBeat <= beat && beat < region.endBeat);
1760
+ if (holding >= 0) {
1761
+ return holding;
1762
+ }
1763
+ return Math.max(
1764
+ 0,
1765
+ regions.findIndex((region) => sameKey(region.key, prevailing))
1766
+ );
1767
+ }
1768
+ function sectionAt(sections, beat) {
1769
+ const index = sections.findIndex(
1770
+ (section) => beat >= section.startBeat - _chunkS3QELJUEcjs.BEAT_EPS && beat < section.endBeat - _chunkS3QELJUEcjs.BEAT_EPS
1771
+ );
1772
+ return index < 0 ? null : index;
1773
+ }
1774
+ function readingsFor(notes, opts = {}) {
1775
+ const asked = _chunkS3QELJUEcjs.assertOptions.call(void 0, opts, "reference options");
1776
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, asked, "reference meters");
1777
+ const budget = asked.budget;
1778
+ if (asked.timeline === void 0) {
1779
+ return _chunkLQZQDBCDcjs.chordTimelineFromNotes.call(void 0, notes, {
1780
+ meters,
1781
+ key: asked.key,
1782
+ totalBeats: asked.totalBeats,
1783
+ budget
1784
+ });
1785
+ }
1786
+ const timeline = _chunkLQZQDBCDcjs.assertChordTimeline.call(void 0, asked.timeline, "reference timeline");
1787
+ let keys;
1788
+ if (asked.key === void 0) {
1789
+ keys = _chunkLQZQDBCDcjs.keyTimelineFromNotes.call(void 0, notes, { meters, totalBeats: asked.totalBeats, budget });
1790
+ } else {
1791
+ const sounding = _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, "reference notes", {
1792
+ allowNonPositiveDuration: true,
1793
+ budget
1794
+ }).filter((note) => note.durationBeat > 0);
1795
+ const span = spanOf(sounding, timeline, meters, asked.totalBeats);
1796
+ keys = _chunkLQZQDBCDcjs.statedKeyRegions.call(void 0, _chunkYDGMWH43cjs.resolveKey.call(void 0, asked.key), span.startBeat, span.endBeat);
1797
+ }
1798
+ return { timeline, keys, prevailingKey: _nullishCoalesce(_chunkLQZQDBCDcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunkYDGMWH43cjs.resolveKey.call(void 0, _chunkYDGMWH43cjs.majorKey.call(void 0, 0)))) };
1799
+ }
1800
+ function referenceFromReadings(notes, readings, opts = {}) {
1801
+ const asked = _chunkS3QELJUEcjs.assertOptions.call(void 0, opts, "reference options");
1802
+ const meters = _chunkS3QELJUEcjs.resolveMeters.call(void 0, asked, "reference meters");
1803
+ const budget = asked.budget;
1804
+ const sounding = _chunkS3QELJUEcjs.assertNoteEvents.call(void 0, notes, "reference notes", {
1805
+ allowNonPositiveDuration: true,
1806
+ budget
1807
+ }).filter((note) => note.durationBeat > 0);
1808
+ if (asked.totalBeats !== void 0) {
1809
+ _chunkS3QELJUEcjs.assertRange.call(void 0, asked.totalBeats, 0, Number.MAX_SAFE_INTEGER, "reference totalBeats");
1810
+ }
1811
+ const line = _chunkLQZQDBCDcjs.orderedNotes.call(void 0, _nullishCoalesce(asked.melody, () => ( notes)), "reference melody", budget).map(
1812
+ (note) => Object.is(note.pitch, -0) ? { ...note, pitch: 0 } : note
1813
+ );
1814
+ const { timeline, prevailingKey } = readings;
1815
+ const span = spanOf(sounding, timeline, meters, asked.totalBeats);
1816
+ const spanEnd = span.endBeat;
1817
+ const keyAt = _chunkLQZQDBCDcjs.keyLookup.call(void 0, readings.keys, prevailingKey);
1818
+ const scaleAt = (beat) => _chunkYDGMWH43cjs.scaleOf.call(void 0, keyAt(beat));
1819
+ const cadenceBeats = _chunkLQZQDBCDcjs.detectCadences.call(void 0, timeline, scaleAt).map((hit) => hit.atBeat);
1820
+ const grouping = hypermeter(sounding, meters, { cadenceBeats, totalBeats: spanEnd, budget });
1821
+ const phrases = phrasesFromTimeline(timeline, notes, {
1822
+ meters,
1823
+ key: scaleAt,
1824
+ hypermeter: grouping,
1825
+ totalBeats: spanEnd,
1826
+ budget
2140
1827
  });
2141
- const rankOf = new Array(durations.length).fill(0);
2142
- for (let rank = 0; rank < order.length; rank += 1) {
2143
- rankOf[_nullishCoalesce(order[rank], () => ( 0))] = rank;
2144
- }
2145
- const floors = [];
2146
- const remaining = durations.length - 1;
2147
- for (let index = 0; index < durations.length; index += 1) {
2148
- if (remaining <= 0) {
2149
- floors.push(0);
2150
- continue;
2151
- }
2152
- const skipped = _nullishCoalesce(rankOf[index], () => ( 0));
2153
- const at = (position) => _nullishCoalesce(durations[_nullishCoalesce(order[position < skipped ? position : position + 1], () => ( 0))], () => ( 0));
2154
- const middle = remaining >> 1;
2155
- floors.push(remaining % 2 === 1 ? at(middle) : (at(middle - 1) + at(middle)) / 2);
2156
- }
2157
- return floors;
2158
- }
2159
- function isTonicChord(chord, key) {
2160
- return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, chord.rootPc - key.rootPc) === 0 && !_chunkSGLEQVJHcjs.isAppliedDominant.call(void 0, chord, key);
2161
- }
2162
- function functionalReason(segment, before, after, keyAt) {
2163
- const key = keyAt(segment.startBeat);
2164
- if (after !== void 0 && _chunkSGLEQVJHcjs.isCadentialSixFour.call(void 0, segment.chord, after.chord, key)) {
2165
- return { kind: "cadentialSixFour" };
2166
- }
2167
- if (isTonicChord(segment.chord, key)) {
2168
- return { kind: "tonic" };
2169
- }
2170
- if (_chunk3R3QVKY5cjs.isDominantChordOf.call(void 0, segment.chord, key)) {
2171
- return { kind: "dominant" };
2172
- }
2173
- if (before !== void 0) {
2174
- const arrival = _chunkSGLEQVJHcjs.detectCadence.call(void 0, before.chord, segment.chord, key).type;
2175
- if (arrival !== null) {
2176
- return { kind: "cadenceArrival", cadence: arrival };
2177
- }
2178
- }
2179
- if (after !== void 0) {
2180
- const agent = _chunkSGLEQVJHcjs.detectCadence.call(void 0, segment.chord, after.chord, keyAt(after.startBeat)).type;
2181
- if (agent !== null && TONIC_ARRIVING.has(agent)) {
2182
- return { kind: "cadenceAgent", cadence: agent };
2183
- }
2184
- }
2185
- return null;
2186
- }
2187
- function figureReason(segment, before, after) {
2188
- if (before === void 0 || after === void 0) {
2189
- return null;
2190
- }
2191
- const incoming = signedStep(before.chord.rootPc, segment.chord.rootPc);
2192
- const outgoing = signedStep(segment.chord.rootPc, after.chord.rootPc);
2193
- if (!isStep(incoming) || !isStep(outgoing)) {
2194
- return null;
2195
- }
2196
- if (incoming > 0 === outgoing > 0) {
2197
- return { kind: "passing" };
2198
- }
2199
- return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, before.chord.rootPc) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, after.chord.rootPc) ? { kind: "neighbor" } : null;
2200
- }
2201
- function describe(reason) {
2202
- switch (reason.kind) {
2203
- case "tonic":
2204
- return "Structural: the tonic of the key, which the progression is heard against";
2205
- case "dominant":
2206
- return "Structural: the dominant of the key, the chord its tonic is approached from";
2207
- case "cadentialSixFour":
2208
- return "Structural: a tonic six-four on the bass of the dominant that follows, which prolongs that dominant rather than framing the tonic";
2209
- case "cadenceArrival":
2210
- return `Structural: the arrival of a ${reason.cadence} cadence`;
2211
- case "cadenceAgent":
2212
- return `Structural: the chord that drives a ${reason.cadence} cadence`;
2213
- case "salient":
2214
- return "Structural: it holds the harmony longer than the chords around it";
2215
- case "first":
2216
- return "Structural: the first chord of the progression frames it";
2217
- case "last":
2218
- return "Structural: the last chord of the progression frames it";
2219
- case "unfigured":
2220
- return "Structural: its root neither steps through to the next chord nor returns to the last, so it embellishes nothing";
2221
- case "passing":
2222
- return "Passing: its root steps in from the previous chord and on to the next in the same direction";
2223
- case "neighbor":
2224
- return "Neighbor: its root steps away from the surrounding harmony and returns to it";
2225
- }
2226
- }
2227
- var BASIS_READINGS = {
2228
- function: ({ segment, before, after, keyAt }) => functionalReason(segment, before, after, keyAt),
2229
- duration: ({ segment, salienceFloor }) => segment.endBeat - segment.startBeat > salienceFloor ? { kind: "salient" } : null
2230
- };
2231
- var REDUCTION_BASES = Object.keys(BASIS_READINGS);
2232
- function levelOf(reason) {
2233
- if (reason.kind === "passing") {
2234
- return "passing";
2235
- }
2236
- return reason.kind === "neighbor" ? "neighbor" : "structural";
2237
- }
2238
- function reduceProgression(timeline, key, opts = {}) {
2239
- _chunkSGLEQVJHcjs.assertChordTimeline.call(void 0, timeline);
2240
- const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "opts");
2241
- const keyAt = _chunkSGLEQVJHcjs.keyScaleAt.call(void 0, key);
2242
- const basis = _chunkS5BKZZGMcjs.assertOneOf.call(void 0, _nullishCoalesce(asked.basis, () => ( "function")), REDUCTION_BASES, "reduction basis");
2243
- const reading = BASIS_READINGS[basis];
2244
- const segments = timeline.segments;
2245
- const floors = basis === "duration" ? salienceFloors(segments.map((segment) => segment.endBeat - segment.startBeat)) : [];
2246
- const result = [];
2247
- for (let index = 0; index < segments.length; index += 1) {
2248
- const segment = segments[index];
2249
- if (segment === void 0) {
2250
- continue;
1828
+ const cadences = /* @__PURE__ */ new Map();
1829
+ for (const closing of structuralCadences(phrases)) {
1830
+ const type = closing.cadence.type;
1831
+ if (type !== null) {
1832
+ cadences.set(closing.phraseIndex, {
1833
+ atBeat: closing.atBeat,
1834
+ type,
1835
+ weight: closing.weight
1836
+ });
2251
1837
  }
2252
- const previous = segments[index - 1];
2253
- const following = segments[index + 1];
2254
- const before = previous !== void 0 && touches(previous, segment) ? previous : void 0;
2255
- const after = following !== void 0 && touches(segment, following) ? following : void 0;
2256
- let reason = reading({ segment, before, after, keyAt, salienceFloor: _nullishCoalesce(floors[index], () => ( 0)) });
2257
- if (reason === null && index === 0) {
2258
- reason = { kind: "first" };
2259
- }
2260
- if (reason === null && index === segments.length - 1) {
2261
- reason = { kind: "last" };
2262
- }
2263
- reason ??= _nullishCoalesce(figureReason(segment, before, after), () => ( { kind: "unfigured" }));
2264
- result.push({
2265
- chord: segment.chord,
2266
- startBeat: segment.startBeat,
2267
- endBeat: segment.endBeat,
2268
- level: levelOf(reason),
2269
- rationale: describe(reason)
2270
- });
2271
1838
  }
2272
- return result;
1839
+ const sections = sectionsFromNotes(notes, {
1840
+ meters,
1841
+ unitBars: grouping.groupBars,
1842
+ totalBeats: spanEnd,
1843
+ budget
1844
+ });
1845
+ const keys = readings.keys.length === 0 && timeline.segments.length > 0 ? [
1846
+ {
1847
+ startBeat: span.startBeat,
1848
+ endBeat: spanEnd,
1849
+ key: _chunkYDGMWH43cjs.resolveKey.call(void 0, prevailingKey),
1850
+ confidence: 0
1851
+ }
1852
+ ] : readings.keys.map((region) => ({
1853
+ startBeat: region.startBeat,
1854
+ endBeat: region.endBeat,
1855
+ key: _chunkYDGMWH43cjs.resolveKey.call(void 0, region.key),
1856
+ confidence: region.confidence
1857
+ }));
1858
+ const chords = _chunkLQZQDBCDcjs.reduceProgression.call(void 0, timeline, scaleAt).map((reduced) => {
1859
+ const index = keyIndexAt(keys, (reduced.startBeat + reduced.endBeat) / 2, prevailingKey);
1860
+ const key = keys[index].key;
1861
+ return {
1862
+ startBeat: reduced.startBeat,
1863
+ endBeat: reduced.endBeat,
1864
+ key: index,
1865
+ roman: _chunkLQZQDBCDcjs.chordToRoman.call(void 0, reduced.chord, key),
1866
+ function: _chunkLQZQDBCDcjs.functionOf.call(void 0, reduced.chord, key),
1867
+ level: reduced.level
1868
+ };
1869
+ });
1870
+ const chordOnsets = timeline.segments.map((segment) => ({
1871
+ pitch: 60,
1872
+ startBeat: segment.startBeat,
1873
+ durationBeat: segment.endBeat - segment.startBeat
1874
+ }));
1875
+ const harmonyRhythm = _chunkLQZQDBCDcjs.analyzeRhythm.call(void 0, chordOnsets, { meters, totalBeats: spanEnd, budget });
1876
+ const motifs = _chunkLQZQDBCDcjs.extractMotifs.call(void 0, line, { budget });
1877
+ const graph = _chunkLQZQDBCDcjs.motifGraph.call(void 0, line, motifs, { key: prevailingKey, budget });
1878
+ let register = null;
1879
+ if (line.length > 0) {
1880
+ const pitches = line.map((note) => note.pitch);
1881
+ register = {
1882
+ low: Math.min(...pitches),
1883
+ high: Math.max(...pitches),
1884
+ mean: _chunkLQZQDBCDcjs.weightedMean.call(void 0, line)
1885
+ };
1886
+ }
1887
+ const lineMean = _nullishCoalesce(_optionalChain([register, 'optionalAccess', _69 => _69.mean]), () => ( 0));
1888
+ const referenceMotifs = motifs.map((motif) => {
1889
+ const first = motif.occurrences[0];
1890
+ return {
1891
+ intervals: motif.intervals,
1892
+ rhythm: motif.rhythm,
1893
+ unitBeats: motif.notes.length < 2 ? 0 : motif.notes[1].startBeat - motif.notes[0].startBeat,
1894
+ spanBeats: first === void 0 ? 0 : first.endBeat - first.startBeat,
1895
+ occurrences: motif.occurrences.length
1896
+ };
1897
+ });
1898
+ const referencePhrases = phrases.map((phrase, index) => ({
1899
+ startBeat: phrase.startBeat,
1900
+ endBeat: phrase.endBeat,
1901
+ bars: _chunkLQZQDBCDcjs.barSpanOf.call(void 0, phrase.startBeat, phrase.endBeat, meters),
1902
+ section: sectionAt(sections, phrase.startBeat),
1903
+ confidence: phrase.confidence,
1904
+ cadence: _nullishCoalesce(cadences.get(index), () => ( null)),
1905
+ melody: _chunkLQZQDBCDcjs.phraseMelody.call(void 0, line, phrase.startBeat, phrase.endBeat, lineMean, meters, budget),
1906
+ motifNodes: graph.nodes.flatMap(
1907
+ (node, nodeIndex) => node.startBeat >= phrase.startBeat - _chunkS3QELJUEcjs.BEAT_EPS && node.startBeat < phrase.endBeat - _chunkS3QELJUEcjs.BEAT_EPS ? [nodeIndex] : []
1908
+ )
1909
+ }));
1910
+ return {
1911
+ profileVersion: _chunkLQZQDBCDcjs.REFERENCE_PROFILE_VERSION,
1912
+ span: {
1913
+ startBeat: span.startBeat,
1914
+ endBeat: spanEnd,
1915
+ bars: _chunkLQZQDBCDcjs.barSpanOf.call(void 0, span.startBeat, spanEnd, meters)
1916
+ },
1917
+ meters,
1918
+ form: {
1919
+ hypermeter: { groupBars: grouping.groupBars, confidence: grouping.confidence },
1920
+ sections,
1921
+ phrases: referencePhrases
1922
+ },
1923
+ harmony: { keys, chords, rhythm: harmonyRhythm },
1924
+ melody: {
1925
+ register,
1926
+ shape: line.length < 2 ? null : _chunkLQZQDBCDcjs.melodicContour.call(void 0, line).shape,
1927
+ motifs: referenceMotifs,
1928
+ graph,
1929
+ rhythm: _chunkLQZQDBCDcjs.rhythmOver.call(void 0, line, { meters, totalBeats: spanEnd, budget }, span.startBeat)
1930
+ }
1931
+ };
1932
+ }
1933
+ function analyzeReference(notes, opts) {
1934
+ return referenceFromReadings(notes, readingsFor(notes, opts), opts);
2273
1935
  }
2274
1936
 
2275
1937
 
@@ -2290,5 +1952,7 @@ function reduceProgression(timeline, key, opts = {}) {
2290
1952
 
2291
1953
 
2292
1954
 
2293
- exports.trackRoleOf = trackRoleOf; exports.analyzePolyphony = analyzePolyphony; exports.analyzeArrangement = analyzeArrangement; exports.createArrangementSession = createArrangementSession; exports.tensionCurve = tensionCurve; exports.tensionCurveFrom = tensionCurveFrom; exports.hypermeter = hypermeter; exports.melodicContour = melodicContour; exports.extractMotifs = extractMotifs; exports.motifFromNotes = motifFromNotes; exports.relateMotifs = relateMotifs; exports.compareMelodies = compareMelodies; exports.melodicSimilarity = melodicSimilarity; exports.phrasesFromTimeline = phrasesFromTimeline; exports.structuralCadences = structuralCadences; exports.sectionsFromNotes = sectionsFromNotes; exports.reduceProgression = reduceProgression;
2294
- //# sourceMappingURL=chunk-HPVSH7SF.cjs.map
1955
+
1956
+
1957
+ exports.trackRoleOf = trackRoleOf; exports.arrangementProfile = arrangementProfile; exports.assertTrackNotes = assertTrackNotes; exports.harmonyTrackSet = harmonyTrackSet; exports.analyzePolyphony = analyzePolyphony; exports.analyzeArrangement = analyzeArrangement; exports.createArrangementSession = createArrangementSession; exports.tensionCurve = tensionCurve; exports.tensionCurveFrom = tensionCurveFrom; exports.hypermeter = hypermeter; exports.hypermeterOver = hypermeterOver; exports.boundaryCadences = boundaryCadences; exports.keyContextFor = keyContextFor; exports.phrasesFromTimeline = phrasesFromTimeline; exports.structuralCadences = structuralCadences; exports.sectionsFromNotes = sectionsFromNotes; exports.compareReferences = compareReferences; exports.referenceFromReadings = referenceFromReadings; exports.analyzeReference = analyzeReference;
1958
+ //# sourceMappingURL=chunk-3MDOEFIQ.cjs.map