@libraz/libcantus 1.2.0 → 1.3.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 (56) hide show
  1. package/README.md +4 -0
  2. package/README_ja.md +4 -0
  3. package/dist/analyze/index.cjs +28 -4
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +151 -5
  6. package/dist/analyze/index.d.ts +151 -5
  7. package/dist/analyze/index.js +34 -10
  8. package/dist/{chunk-DSMJRXCZ.cjs → chunk-4R2LWV7E.cjs} +384 -107
  9. package/dist/chunk-4R2LWV7E.cjs.map +1 -0
  10. package/dist/{chunk-J7FHVZII.cjs → chunk-B2PW6EHN.cjs} +2000 -30
  11. package/dist/chunk-B2PW6EHN.cjs.map +1 -0
  12. package/dist/{chunk-Q6LFSAKV.js → chunk-CPMB7P75.js} +1973 -3
  13. package/dist/chunk-CPMB7P75.js.map +1 -0
  14. package/dist/{chunk-H2KJHBW4.js → chunk-GNVN4PPJ.js} +1871 -5
  15. package/dist/chunk-GNVN4PPJ.js.map +1 -0
  16. package/dist/{chunk-SGLEQVJH.cjs → chunk-IFZDWIKM.cjs} +1868 -2
  17. package/dist/chunk-IFZDWIKM.cjs.map +1 -0
  18. package/dist/{chunk-FBH75DAY.js → chunk-IPEF6NWB.js} +287 -10
  19. package/dist/chunk-IPEF6NWB.js.map +1 -0
  20. package/dist/{chunk-HYJJ2V2I.js → chunk-MLBPXA4Z.js} +300 -720
  21. package/dist/chunk-MLBPXA4Z.js.map +1 -0
  22. package/dist/{chunk-HWHGOVO6.cjs → chunk-PWC4CRWT.cjs} +1 -1
  23. package/dist/{chunk-HWHGOVO6.cjs.map → chunk-PWC4CRWT.cjs.map} +1 -1
  24. package/dist/{chunk-HPVSH7SF.cjs → chunk-YFA7X2F6.cjs} +350 -770
  25. package/dist/chunk-YFA7X2F6.cjs.map +1 -0
  26. package/dist/{chunk-NFJ4P6ZZ.js → chunk-ZBUROTJQ.js} +1 -1
  27. package/dist/generate/index.cjs +15 -3
  28. package/dist/generate/index.cjs.map +1 -1
  29. package/dist/generate/index.d.cts +84 -6
  30. package/dist/generate/index.d.ts +84 -6
  31. package/dist/generate/index.js +14 -2
  32. package/dist/{index-BNSM-DSk.d.cts → index-CY-3NVfy.d.cts} +484 -3
  33. package/dist/{index-BJgP97N1.d.ts → index-zaZdmnU4.d.ts} +484 -3
  34. package/dist/index.cjs +43 -7
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +5 -5
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.js +48 -12
  39. package/dist/model/index.cjs +5 -5
  40. package/dist/model/index.d.cts +203 -7
  41. package/dist/model/index.d.ts +203 -7
  42. package/dist/model/index.js +4 -4
  43. package/dist/{index-DGCNKAUr.d.cts → profile-CQMEqdH9.d.cts} +65 -598
  44. package/dist/{index-BRMbPkHE.d.ts → profile-DLdlIpS9.d.ts} +65 -598
  45. package/dist/{index-BBWNZS0f.d.cts → types-D-XgxGvp.d.cts} +1169 -13
  46. package/dist/{index-CJBBiAmU.d.ts → types-Dh7C064C.d.ts} +1169 -13
  47. package/package.json +5 -5
  48. package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
  49. package/dist/chunk-FBH75DAY.js.map +0 -1
  50. package/dist/chunk-H2KJHBW4.js.map +0 -1
  51. package/dist/chunk-HPVSH7SF.cjs.map +0 -1
  52. package/dist/chunk-HYJJ2V2I.js.map +0 -1
  53. package/dist/chunk-J7FHVZII.cjs.map +0 -1
  54. package/dist/chunk-Q6LFSAKV.js.map +0 -1
  55. package/dist/chunk-SGLEQVJH.cjs.map +0 -1
  56. /package/dist/{chunk-NFJ4P6ZZ.js.map → chunk-ZBUROTJQ.js.map} +0 -0
@@ -30,7 +30,6 @@
30
30
 
31
31
 
32
32
 
33
- var _chunkSGLEQVJHcjs = require('./chunk-SGLEQVJH.cjs');
34
33
 
35
34
 
36
35
 
@@ -41,9 +40,27 @@ var _chunkSGLEQVJHcjs = require('./chunk-SGLEQVJH.cjs');
41
40
 
42
41
 
43
42
 
44
- var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
45
43
 
46
44
 
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+ var _chunkIFZDWIKMcjs = require('./chunk-IFZDWIKM.cjs');
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+ var _chunk3R3QVKY5cjs = require('./chunk-3R3QVKY5.cjs');
47
64
 
48
65
 
49
66
 
@@ -108,7 +125,7 @@ function assertTrackNotes(tracks, options) {
108
125
  }
109
126
  function assertGivenTimeline(timeline, name) {
110
127
  if (timeline !== void 0) {
111
- _chunkSGLEQVJHcjs.assertChordTimeline.call(void 0, timeline, name);
128
+ _chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, timeline, name);
112
129
  }
113
130
  }
114
131
  function assertGivenKeys(keys, name) {
@@ -195,7 +212,7 @@ function splitIntoSubVoices(ordered) {
195
212
  continue;
196
213
  }
197
214
  const end = last.startBeat + last.durationBeat;
198
- if (!_chunkSGLEQVJHcjs.hasEnded.call(void 0, end, note.startBeat)) {
215
+ if (!_chunkIFZDWIKMcjs.hasEnded.call(void 0, end, note.startBeat)) {
199
216
  continue;
200
217
  }
201
218
  const distance = Math.abs(last.pitch - note.pitch);
@@ -216,13 +233,13 @@ function splitIntoSubVoices(ordered) {
216
233
  for (let start = 0; start < ordered.length; ) {
217
234
  const onset = _nullishCoalesce(_optionalChain([ordered, 'access', _13 => _13[start], 'optionalAccess', _14 => _14.startBeat]), () => ( 0));
218
235
  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)) {
236
+ while (end < ordered.length && _chunkIFZDWIKMcjs.sameInstant.call(void 0, _nullishCoalesce(_optionalChain([ordered, 'access', _15 => _15[end], 'optionalAccess', _16 => _16.startBeat]), () => ( 0)), onset)) {
220
237
  end += 1;
221
238
  }
222
239
  const block = ordered.slice(start, end);
223
240
  if (block.length > 1) {
224
241
  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)
242
+ (entry) => entry.last !== void 0 && _chunkIFZDWIKMcjs.hasEnded.call(void 0, entry.last.startBeat + entry.last.durationBeat, onset)
226
243
  ).sort((a, b) => a.last.pitch - b.last.pitch);
227
244
  const assignment = pairWithFreeLanes(block, freeLanes);
228
245
  const taken = new Set(
@@ -326,7 +343,7 @@ function prepareTracks(tracks) {
326
343
  const sounding = voice.map((note, i) => {
327
344
  const prev = i > 0 ? voice[i - 1] : void 0;
328
345
  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);
346
+ const contiguous = prevEnd !== void 0 && _chunkIFZDWIKMcjs.adjacent.call(void 0, prevEnd, note.startBeat);
330
347
  return {
331
348
  id: note.id,
332
349
  originalIndex: note.originalIndex,
@@ -359,7 +376,7 @@ function withGivenKeys(result, given) {
359
376
  if (given === void 0) {
360
377
  return result;
361
378
  }
362
- return { ...result, keys: given, prevailingKey: _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, given), () => ( result.prevailingKey)) };
379
+ return { ...result, keys: given, prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, given), () => ( result.prevailingKey)) };
363
380
  }
364
381
  function callerTimeline(timeline, key, given, pooled, meters, budget) {
365
382
  const chartEnd = timeline.segments.reduce((end, segment) => Math.max(end, segment.endBeat), 0);
@@ -368,15 +385,15 @@ function callerTimeline(timeline, key, given, pooled, meters, budget) {
368
385
  0
369
386
  );
370
387
  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 }),
388
+ const { startBeat: musicStart } = _chunkIFZDWIKMcjs.gridForNotes.call(void 0, pooled, _chunkS5BKZZGMcjs.beatsPerBarAt.call(void 0, 0, meters));
389
+ const keys = _nullishCoalesce(given, () => ( _chunkIFZDWIKMcjs.attachPivots.call(void 0,
390
+ key !== void 0 ? [{ startBeat: musicStart, endBeat: totalBeats, key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, key), confidence: 1 }] : _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }),
374
391
  timeline.segments
375
392
  )));
376
393
  return {
377
394
  timeline,
378
395
  keys,
379
- prevailingKey: _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))),
396
+ prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))),
380
397
  segmentConfidence: timeline.segments.map(() => 0)
381
398
  };
382
399
  }
@@ -393,7 +410,7 @@ function soundingVoicesAt(prepared, beat) {
393
410
  continue;
394
411
  }
395
412
  const snap = { pitch: note.pitch };
396
- const previous = _chunkSGLEQVJHcjs.sameInstant.call(void 0, note.startBeat, beat) ? note.prevPitch : note.pitch;
413
+ const previous = _chunkIFZDWIKMcjs.sameInstant.call(void 0, note.startBeat, beat) ? note.prevPitch : note.pitch;
397
414
  if (previous !== void 0) {
398
415
  snap.prevPitch = previous;
399
416
  }
@@ -437,7 +454,7 @@ function analyzePolyphony(notes, chordAtBeat, key) {
437
454
  if (subVoice === void 0) {
438
455
  continue;
439
456
  }
440
- const analyzed = _chunkSGLEQVJHcjs.analyzeVoice.call(void 0,
457
+ const analyzed = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0,
441
458
  subVoice.voice,
442
459
  chordAtBeat,
443
460
  key,
@@ -455,7 +472,7 @@ function analyzePolyphony(notes, chordAtBeat, key) {
455
472
  if (analyzed !== void 0) {
456
473
  return analyzed;
457
474
  }
458
- const [only] = _chunkSGLEQVJHcjs.analyzeVoice.call(void 0, [{ ...note, id: index }], chordAtBeat, key);
475
+ const [only] = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0, [{ ...note, id: index }], chordAtBeat, key);
459
476
  return _nullishCoalesce(only, () => ( {
460
477
  noteId: index,
461
478
  pitch: note.pitch,
@@ -522,7 +539,7 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
522
539
  let evidence;
523
540
  let inferred;
524
541
  if (opts.timeline === void 0) {
525
- const run = _chunkSGLEQVJHcjs.analyzeTimeline.call(void 0,
542
+ const run = _chunkIFZDWIKMcjs.analyzeTimeline.call(void 0,
526
543
  pooled,
527
544
  {
528
545
  key: opts.key,
@@ -540,9 +557,9 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
540
557
  inferred = callerTimeline(opts.timeline, opts.key, opts.keys, pooled, meters, budget);
541
558
  }
542
559
  const { timeline, keys, prevailingKey, segmentConfidence } = inferred;
543
- const keyAt = _chunkSGLEQVJHcjs.keyLookup.call(void 0, keys, prevailingKey);
560
+ const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, keys, prevailingKey);
544
561
  const scaleAt = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
545
- const cadences = _chunkSGLEQVJHcjs.detectCadences.call(void 0, timeline, scaleAt);
562
+ const cadences = _chunkIFZDWIKMcjs.detectCadences.call(void 0, timeline, scaleAt);
546
563
  const soundingCache = /* @__PURE__ */ new Map();
547
564
  const trackAnalyses = [];
548
565
  const conflicts = [];
@@ -561,7 +578,7 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
561
578
  if (!subVoice) {
562
579
  continue;
563
580
  }
564
- const analyzed = _chunkSGLEQVJHcjs.analyzeVoice.call(void 0,
581
+ const analyzed = _chunkIFZDWIKMcjs.analyzeVoice.call(void 0,
565
582
  subVoice.voice,
566
583
  timeline.at,
567
584
  scaleAt,
@@ -578,7 +595,7 @@ function analyzeArrangementWith(tracks, opts = {}, previous, dirty) {
578
595
  continue;
579
596
  }
580
597
  for (const beat of evaluationBeats(note, timeline)) {
581
- const atOnset = _chunkSGLEQVJHcjs.sameInstant.call(void 0, beat, note.startBeat);
598
+ const atOnset = _chunkIFZDWIKMcjs.sameInstant.call(void 0, beat, note.startBeat);
582
599
  const others = otherVoicesSounding(prepared, t, v, beat, soundingCache);
583
600
  const safetyQuery = {
584
601
  profile,
@@ -735,7 +752,7 @@ function sessionOf(tracks, opts, analysis, evidence) {
735
752
  if (edited === null && !reordered) {
736
753
  return sessionOf(next, opts, analysis, evidence);
737
754
  }
738
- const dirty = evidence === void 0 ? void 0 : edited === null ? { from: 0, to: 0 } : _chunkSGLEQVJHcjs.dirtySlotsFor.call(void 0, evidence, edited.startBeat, edited.endBeat);
755
+ const dirty = evidence === void 0 ? void 0 : edited === null ? { from: 0, to: 0 } : _chunkIFZDWIKMcjs.dirtySlotsFor.call(void 0, evidence, edited.startBeat, edited.endBeat);
739
756
  const run = analyzeArrangementWith(next, opts, evidence, dirty);
740
757
  return sessionOf(next, opts, run.analysis, run.evidence);
741
758
  }
@@ -788,7 +805,7 @@ function tensionCurve(tracks, opts = {}) {
788
805
  const pooled = poolNotes(tracks, harmonyTracks);
789
806
  const all = poolNotes(tracks);
790
807
  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);
808
+ const { origin: sampleStart, startBeat: musicStart } = _chunkIFZDWIKMcjs.gridForNotes.call(void 0, all, step);
792
809
  const suppliedKeys = opts.timeline === void 0 ? void 0 : _nullishCoalesce(opts.keys, () => ( (opts.key !== void 0 ? [
793
810
  {
794
811
  // The one region a named key holds starts where the music does,
@@ -799,8 +816,8 @@ function tensionCurve(tracks, opts = {}) {
799
816
  key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, opts.key),
800
817
  confidence: 1
801
818
  }
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, {
819
+ ] : _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, pooled, { meters, totalBeats, budget }))));
820
+ const { timeline, keys, prevailingKey } = opts.timeline === void 0 || suppliedKeys === void 0 ? _chunkIFZDWIKMcjs.chordTimelineFromNotes.call(void 0, pooled, {
804
821
  key: opts.key,
805
822
  meters,
806
823
  pickupBeats: opts.pickupBeats,
@@ -809,10 +826,10 @@ function tensionCurve(tracks, opts = {}) {
809
826
  }) : {
810
827
  timeline: opts.timeline,
811
828
  keys: suppliedKeys,
812
- prevailingKey: _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, suppliedKeys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))))
829
+ prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, suppliedKeys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0))))
813
830
  };
814
831
  const regions = _nullishCoalesce(opts.keys, () => ( keys));
815
- const keyAt = _chunkSGLEQVJHcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkSGLEQVJHcjs.prevailingKeyOf.call(void 0, regions), () => ( prevailingKey)));
832
+ const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, regions), () => ( prevailingKey)));
816
833
  const prepared = prepareTracks(tracks);
817
834
  const points = [];
818
835
  const sampleCount = Math.max(0, Math.ceil((totalBeats - sampleStart) / step - _chunkS5BKZZGMcjs.BEAT_EPS));
@@ -870,7 +887,7 @@ function sampleTension(prepared, timeline, key, meters, profile, beat) {
870
887
  return 0;
871
888
  }
872
889
  const chord = timeline.at(beat);
873
- const functionScore = chord ? FUNCTION_TENSION[_chunkSGLEQVJHcjs.functionOf.call(void 0, chord, key)] : 0;
890
+ const functionScore = chord ? FUNCTION_TENSION[_chunkIFZDWIKMcjs.functionOf.call(void 0, chord, key)] : 0;
874
891
  const chordPcs = chord ? new Set(_chunk3R3QVKY5cjs.chordPitchClasses.call(void 0, chord)) : null;
875
892
  const strongBeat = _chunkS5BKZZGMcjs.isStrongBeat.call(void 0, beat, meters);
876
893
  let nonChord = 0;
@@ -950,7 +967,7 @@ function accentContrast(novelty, slices, groupBars, phase) {
950
967
  continue;
951
968
  }
952
969
  const value = _nullishCoalesce(novelty[i], () => ( 0));
953
- if (_chunkSGLEQVJHcjs.floorMod.call(void 0, slice.index - phase, groupBars) === 0) {
970
+ if (_chunkIFZDWIKMcjs.floorMod.call(void 0, slice.index - phase, groupBars) === 0) {
954
971
  headSum += value;
955
972
  headCount += 1;
956
973
  } else {
@@ -968,7 +985,7 @@ function cadenceFitOf(cadenceBars, slices, groupBars, phase) {
968
985
  let ends = 0;
969
986
  let fit = 0;
970
987
  for (const slice of slices) {
971
- if (slice.index < 0 || _chunkSGLEQVJHcjs.floorMod.call(void 0, slice.index - phase, groupBars) !== groupBars - 1) {
988
+ if (slice.index < 0 || _chunkIFZDWIKMcjs.floorMod.call(void 0, slice.index - phase, groupBars) !== groupBars - 1) {
972
989
  continue;
973
990
  }
974
991
  ends += 1;
@@ -978,7 +995,7 @@ function cadenceFitOf(cadenceBars, slices, groupBars, phase) {
978
995
  fit += ELIDED_FIT;
979
996
  }
980
997
  }
981
- return ends === 0 ? 0 : _chunkSGLEQVJHcjs.clamp01.call(void 0, fit / ends);
998
+ return ends === 0 ? 0 : _chunkIFZDWIKMcjs.clamp01.call(void 0, fit / ends);
982
999
  }
983
1000
  function undecidedReading(slices) {
984
1001
  const fullBars = slices.filter((slice) => slice.index >= 0);
@@ -999,12 +1016,12 @@ function hypermeter(notes, meter, opts = {}) {
999
1016
  budget: asked.budget
1000
1017
  });
1001
1018
  const sounding = notes.filter((note) => note.durationBeat > 0);
1002
- const firstOnset = _chunkSGLEQVJHcjs.firstSoundingBeat.call(void 0, sounding);
1019
+ const firstOnset = _chunkIFZDWIKMcjs.firstSoundingBeat.call(void 0, sounding);
1003
1020
  const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
1004
1021
  const spanEnd = _nullishCoalesce(asked.totalBeats, () => ( lastEnd));
1005
1022
  _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);
1023
+ const spanStart = _chunkIFZDWIKMcjs.barGridStart.call(void 0, firstOnset, meters);
1024
+ const slices = _chunkIFZDWIKMcjs.sliceBars.call(void 0, sounding, meters, spanStart, spanEnd, asked.budget);
1008
1025
  const fullBars = slices.reduce((count, slice) => slice.index >= 0 ? count + 1 : count, 0);
1009
1026
  const candidates = GROUP_CANDIDATES.filter((groupBars) => fullBars >= groupBars * MIN_GROUPS);
1010
1027
  if (candidates.length === 0) {
@@ -1015,7 +1032,7 @@ function hypermeter(notes, meter, opts = {}) {
1015
1032
  "hypermeter readings",
1016
1033
  asked.budget
1017
1034
  );
1018
- const novelty = slices.map((_, index) => _chunkSGLEQVJHcjs.harmonicNovelty.call(void 0, slices, index));
1035
+ const novelty = slices.map((_, index) => _chunkIFZDWIKMcjs.harmonicNovelty.call(void 0, slices, index));
1019
1036
  const cadenceBars = new Set((_nullishCoalesce(asked.cadenceBeats, () => ( []))).map((beat) => _chunkS5BKZZGMcjs.barIndexAt.call(void 0, beat, meters)));
1020
1037
  const hasCadences = cadenceBars.size > 0;
1021
1038
  const readings = [];
@@ -1030,14 +1047,14 @@ function hypermeter(notes, meter, opts = {}) {
1030
1047
  if (hasCadences && cadenceFit < bestFit - _chunkS5BKZZGMcjs.BEAT_EPS) {
1031
1048
  continue;
1032
1049
  }
1033
- const evidence = _chunkSGLEQVJHcjs.clamp01.call(void 0,
1050
+ const evidence = _chunkIFZDWIKMcjs.clamp01.call(void 0,
1034
1051
  hasCadences ? (
1035
1052
  // Floored at zero rather than summed raw: group heads that restate
1036
1053
  // the harmony their group opened with — the commonest way a phrase
1037
1054
  // begins — change less than the bars between them, and a negative
1038
1055
  // contrast subtracted from the cadence term would count that
1039
1056
  // restatement as evidence against the very phase it confirms.
1040
- CONTRAST_SHARE * _chunkSGLEQVJHcjs.clamp01.call(void 0, contrast) + CADENCE_SHARE * cadenceFit
1057
+ CONTRAST_SHARE * _chunkIFZDWIKMcjs.clamp01.call(void 0, contrast) + CADENCE_SHARE * cadenceFit
1041
1058
  ) : contrast
1042
1059
  );
1043
1060
  readings.push({
@@ -1058,8 +1075,8 @@ function hypermeter(notes, meter, opts = {}) {
1058
1075
  return undecidedReading(slices);
1059
1076
  }
1060
1077
  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);
1078
+ const margin = runnerUp === void 0 || best.score <= _chunkS5BKZZGMcjs.BEAT_EPS ? 1 : _chunkIFZDWIKMcjs.clamp01.call(void 0, (best.score - runnerUp.score) / best.score);
1079
+ const downbeats = slices.filter((slice) => slice.index >= 0 && _chunkIFZDWIKMcjs.floorMod.call(void 0, slice.index - best.phase, best.groupBars) === 0).map((slice) => slice.startBeat);
1063
1080
  const cadencePart = hasCadences ? `${Math.round(best.cadenceFit * 100)}% of group ends closed by a cadence` : "no cadences given";
1064
1081
  return {
1065
1082
  groupBars: best.groupBars,
@@ -1069,554 +1086,11 @@ function hypermeter(notes, meter, opts = {}) {
1069
1086
  // runner-up is nearly always a multiple or a divisor of the winner — four
1070
1087
  // bars nest inside eight — and a reading of the same accents at another
1071
1088
  // 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)),
1089
+ confidence: _chunkIFZDWIKMcjs.clamp01.call(void 0, best.evidence * (MARGIN_FLOOR + (1 - MARGIN_FLOOR) * margin)),
1073
1090
  rationale: `${best.groupBars}-bar groups from bar ${best.phase}: harmonic change ${Math.round(best.contrast * 100)}% stronger on group heads, ${cadencePart}`
1074
1091
  };
1075
1092
  }
1076
1093
 
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
1094
  // src/analyze/form/phrase.ts
1621
1095
  var CADENCE_BOUNDARY_STRENGTH = {
1622
1096
  authentic: 0.9,
@@ -1668,7 +1142,7 @@ function readBoundary(boundary) {
1668
1142
  (a, b) => b[1] - a[1] || SIGNAL_ORDER.indexOf(a[0]) - SIGNAL_ORDER.indexOf(b[0])
1669
1143
  );
1670
1144
  return {
1671
- strength: _chunkSGLEQVJHcjs.combineEvidence.call(void 0, signals.map(([, strength]) => strength)),
1145
+ strength: _chunkIFZDWIKMcjs.combineEvidence.call(void 0, signals.map(([, strength]) => strength)),
1672
1146
  signals: signals.map(([signal]) => signal)
1673
1147
  };
1674
1148
  }
@@ -1712,29 +1186,18 @@ function choosePhrasePath(beats, strengths, minPhraseBeats, expected) {
1712
1186
  return path;
1713
1187
  }
1714
1188
  function arrivalEnd(timeline, atBeat) {
1715
- return _nullishCoalesce(_optionalChain([_chunkSGLEQVJHcjs.segmentStartingAt.call(void 0, timeline.segments, atBeat), 'optionalAccess', _93 => _93.endBeat]), () => ( atBeat));
1716
- }
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;
1189
+ return _nullishCoalesce(_optionalChain([_chunkIFZDWIKMcjs.segmentStartingAt.call(void 0, timeline.segments, atBeat), 'optionalAccess', _43 => _43.endBeat]), () => ( atBeat));
1727
1190
  }
1728
1191
  function keyContextFor(opts, notes, meters, totalBeats) {
1729
1192
  if (opts.key !== void 0) {
1730
1193
  return opts.key;
1731
1194
  }
1732
- const regions = _chunkSGLEQVJHcjs.keyTimelineFromNotes.call(void 0, notes, {
1195
+ const regions = _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, notes, {
1733
1196
  meters,
1734
1197
  totalBeats,
1735
1198
  budget: opts.budget
1736
1199
  });
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)))));
1200
+ const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, regions, _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, regions), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))));
1738
1201
  return (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
1739
1202
  }
1740
1203
  function restBoundaries(boundaries, notes, meters, restBeats) {
@@ -1748,7 +1211,7 @@ function restBoundaries(boundaries, notes, meters, restBeats) {
1748
1211
  boundaries,
1749
1212
  note.startBeat,
1750
1213
  "rest",
1751
- REST_STRENGTH * _chunkSGLEQVJHcjs.clamp01.call(void 0, bar > 0 ? gap / bar : 1)
1214
+ REST_STRENGTH * _chunkIFZDWIKMcjs.clamp01.call(void 0, bar > 0 ? gap / bar : 1)
1752
1215
  );
1753
1216
  }
1754
1217
  covered = Math.max(covered, note.startBeat + note.durationBeat);
@@ -1758,7 +1221,7 @@ function repetitionBoundaries(boundaries, notes, meters, spanStart, spanEnd, win
1758
1221
  if (!(windowBeats > 0)) {
1759
1222
  return;
1760
1223
  }
1761
- const slices = _chunkSGLEQVJHcjs.sliceBars.call(void 0, notes, meters, spanStart, spanEnd, budget);
1224
+ const slices = _chunkIFZDWIKMcjs.sliceBars.call(void 0, notes, meters, spanStart, spanEnd, budget);
1762
1225
  _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, slices.length * notes.length, "form repetition comparisons", budget);
1763
1226
  const onsetsIn = (from, to) => notes.filter((note) => note.startBeat >= from - _chunkS5BKZZGMcjs.BEAT_EPS && note.startBeat < to - _chunkS5BKZZGMcjs.BEAT_EPS);
1764
1227
  let comparisons = 0;
@@ -1774,7 +1237,7 @@ function repetitionBoundaries(boundaries, notes, meters, spanStart, spanEnd, win
1774
1237
  }
1775
1238
  comparisons += before.length * after.length;
1776
1239
  _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, comparisons, "form repetition melody comparisons", budget);
1777
- const similarity = melodicSimilarity(before, after);
1240
+ const similarity = _chunkIFZDWIKMcjs.melodicSimilarity.call(void 0, before, after);
1778
1241
  if (similarity >= REPETITION_THRESHOLD) {
1779
1242
  addSignal(boundaries, beat, "repetition", REPETITION_STRENGTH * similarity);
1780
1243
  }
@@ -1803,7 +1266,7 @@ function structuralWeightOf(cadence, confidence, aligned, isLast) {
1803
1266
  if (aligned) {
1804
1267
  weight += HYPERMETRIC_CLOSE_BONUS;
1805
1268
  }
1806
- return _chunkSGLEQVJHcjs.clamp01.call(void 0, weight);
1269
+ return _chunkIFZDWIKMcjs.clamp01.call(void 0, weight);
1807
1270
  }
1808
1271
  function describeCadence(cadence) {
1809
1272
  if (cadence === null || cadence.cadence.type === null) {
@@ -1814,7 +1277,7 @@ function describeCadence(cadence) {
1814
1277
  return `a ${graded} cadence at beat ${cadence.atBeat}`;
1815
1278
  }
1816
1279
  function phrasesFromTimeline(timeline, notes, opts = {}) {
1817
- _chunkSGLEQVJHcjs.assertChordTimeline.call(void 0, timeline);
1280
+ _chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, timeline);
1818
1281
  const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "phrase meters");
1819
1282
  _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "phrase notes", {
1820
1283
  allowNonPositiveDuration: true,
@@ -1830,12 +1293,12 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1830
1293
  const lastSegment = segments[segments.length - 1];
1831
1294
  const firstOnset = sounding.reduce(
1832
1295
  (first, n) => Math.min(first, n.startBeat),
1833
- _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _94 => _94.startBeat]), () => ( 0))
1296
+ _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _44 => _44.startBeat]), () => ( 0))
1834
1297
  );
1835
- const spanStart = _chunkSGLEQVJHcjs.barGridStart.call(void 0, firstOnset, meters);
1298
+ const spanStart = _chunkIFZDWIKMcjs.barGridStart.call(void 0, firstOnset, meters);
1836
1299
  const notesEnd = sounding.reduce(
1837
1300
  (end, n) => Math.max(end, n.startBeat + n.durationBeat),
1838
- _nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _95 => _95.endBeat]), () => ( 0))
1301
+ _nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _45 => _45.endBeat]), () => ( 0))
1839
1302
  );
1840
1303
  const spanEnd = Math.max(spanStart, _nullishCoalesce(opts.totalBeats, () => ( notesEnd)));
1841
1304
  _chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "phrase totalBeats");
@@ -1850,7 +1313,7 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1850
1313
  const minPhraseBeats = _nullishCoalesce(opts.minPhraseBeats, () => ( barBeats));
1851
1314
  _chunkS5BKZZGMcjs.assertRange.call(void 0, minPhraseBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "phrase minPhraseBeats");
1852
1315
  const key = keyContextFor(opts, sounding, meters, spanEnd);
1853
- const cadences = _chunkSGLEQVJHcjs.detectCadences.call(void 0, timeline, key);
1316
+ const cadences = _chunkIFZDWIKMcjs.detectCadences.call(void 0, timeline, key);
1854
1317
  const grouping = _nullishCoalesce(opts.hypermeter, () => ( hypermeter(sounding, meters, {
1855
1318
  totalBeats: spanEnd,
1856
1319
  cadenceBeats: cadences.map((hit) => hit.atBeat),
@@ -1916,16 +1379,16 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1916
1379
  let from = 0;
1917
1380
  for (let n = 0; n < path.length; n += 1) {
1918
1381
  const to = _nullishCoalesce(path[n], () => ( stops.length - 1));
1919
- const startBeat = _nullishCoalesce(_optionalChain([stops, 'access', _96 => _96[from], 'optionalAccess', _97 => _97.beat]), () => ( spanStart));
1382
+ const startBeat = _nullishCoalesce(_optionalChain([stops, 'access', _46 => _46[from], 'optionalAccess', _47 => _47.beat]), () => ( spanStart));
1920
1383
  const stop = stops[to];
1921
- const endBeat = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _98 => _98.beat]), () => ( spanEnd));
1384
+ const endBeat = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _48 => _48.beat]), () => ( spanEnd));
1922
1385
  const reading = _nullishCoalesce(readings[to], () => ( { strength: 0, signals: [] }));
1923
1386
  const length = endBeat - startBeat;
1924
- const confidence = _chunkSGLEQVJHcjs.clamp01.call(void 0,
1387
+ const confidence = _chunkIFZDWIKMcjs.clamp01.call(void 0,
1925
1388
  CLOSING_SHARE * reading.strength + (1 - CLOSING_SHARE) * Math.max(0, lengthFit(length, expected))
1926
1389
  );
1927
1390
  const isLast = n === path.length - 1;
1928
- const closing = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _99 => _99.cadence]), () => ( null));
1391
+ const closing = _nullishCoalesce(_optionalChain([stop, 'optionalAccess', _49 => _49.cadence]), () => ( null));
1929
1392
  const cadence = closing !== null && closing.atBeat >= startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && closing.atBeat < endBeat - _chunkS5BKZZGMcjs.BEAT_EPS ? closing : null;
1930
1393
  const structuralWeight = structuralWeightOf(
1931
1394
  cadence,
@@ -1934,7 +1397,7 @@ function phrasesFromTimeline(timeline, notes, opts = {}) {
1934
1397
  isLast
1935
1398
  );
1936
1399
  const signals = cadence === null ? reading.signals.filter((signal) => signal !== "cadence") : reading.signals;
1937
- const bars = barSpanOf(startBeat, endBeat, meters);
1400
+ const bars = Math.round(_chunkIFZDWIKMcjs.barSpanOf.call(void 0, startBeat, endBeat, meters) * 100) / 100;
1938
1401
  phrases.push({
1939
1402
  startBeat,
1940
1403
  endBeat,
@@ -1986,12 +1449,12 @@ function isSilent(unit) {
1986
1449
  return unit.notes.length === 0;
1987
1450
  }
1988
1451
  function unitSimilarity(a, b) {
1989
- const harmony = _chunkSGLEQVJHcjs.weightSimilarity.call(void 0, a.weights, b.weights);
1452
+ const harmony = _chunkIFZDWIKMcjs.weightSimilarity.call(void 0, a.weights, b.weights);
1990
1453
  if (a.notes.length < 2 || b.notes.length < 2) {
1991
1454
  return a.notes.length === b.notes.length ? harmony : 0;
1992
1455
  }
1993
- const melody = melodicSimilarity(a.notes, b.notes);
1994
- return _chunkSGLEQVJHcjs.clamp01.call(void 0, MELODY_SHARE * melody + HARMONY_SHARE * harmony);
1456
+ const melody = _chunkIFZDWIKMcjs.melodicSimilarity.call(void 0, a.notes, b.notes);
1457
+ return _chunkIFZDWIKMcjs.clamp01.call(void 0, MELODY_SHARE * melody + HARMONY_SHARE * harmony);
1995
1458
  }
1996
1459
  function sectionsFromNotes(notes, opts = {}) {
1997
1460
  const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, opts, "section meters");
@@ -2003,7 +1466,7 @@ function sectionsFromNotes(notes, opts = {}) {
2003
1466
  if (sounding.length === 0) {
2004
1467
  return [];
2005
1468
  }
2006
- const firstOnset = _chunkSGLEQVJHcjs.barGridStart.call(void 0, _chunkSGLEQVJHcjs.firstSoundingBeat.call(void 0, sounding), meters);
1469
+ const firstOnset = _chunkIFZDWIKMcjs.barGridStart.call(void 0, _chunkIFZDWIKMcjs.firstSoundingBeat.call(void 0, sounding), meters);
2007
1470
  const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
2008
1471
  const spanEnd = Math.max(firstOnset, _nullishCoalesce(opts.totalBeats, () => ( lastEnd)));
2009
1472
  _chunkS5BKZZGMcjs.assertRange.call(void 0, spanEnd, 0, Number.MAX_SAFE_INTEGER, "section totalBeats");
@@ -2012,7 +1475,7 @@ function sectionsFromNotes(notes, opts = {}) {
2012
1475
  const unitBars = _nullishCoalesce(opts.unitBars, () => ( hypermeter(sounding, meters, { totalBeats: spanEnd, budget: opts.budget }).groupBars));
2013
1476
  _chunkS5BKZZGMcjs.assertPositiveInt.call(void 0, unitBars, "section unitBars");
2014
1477
  const firstBar = _chunkS5BKZZGMcjs.barIndexAt.call(void 0, firstOnset, meters);
2015
- const lastBar = _chunkSGLEQVJHcjs.lastBarOf.call(void 0, meters, firstOnset, spanEnd);
1478
+ const lastBar = _chunkIFZDWIKMcjs.lastBarOf.call(void 0, meters, firstOnset, spanEnd);
2016
1479
  const startBar = Math.max(0, firstBar);
2017
1480
  const unitCount = Math.max(1, Math.ceil((lastBar - startBar + 1) / unitBars));
2018
1481
  _chunkS5BKZZGMcjs.assertGenerationBudget.call(void 0, unitCount * unitCount, "form section comparisons", opts.budget);
@@ -2029,7 +1492,7 @@ function sectionsFromNotes(notes, opts = {}) {
2029
1492
  startBeat,
2030
1493
  endBeat,
2031
1494
  notes: unitNotes,
2032
- weights: _chunkSGLEQVJHcjs.windowWeights.call(void 0, unitNotes, startBeat, endBeat, meters).weights
1495
+ weights: _chunkIFZDWIKMcjs.windowWeights.call(void 0, unitNotes, startBeat, endBeat, meters).weights
2033
1496
  });
2034
1497
  }
2035
1498
  let comparisons = 0;
@@ -2093,8 +1556,8 @@ function sectionsFromNotes(notes, opts = {}) {
2093
1556
  while (end < units.length && labels[end] === null) {
2094
1557
  end += 1;
2095
1558
  }
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));
1559
+ const startBeat = _nullishCoalesce(_optionalChain([units, 'access', _50 => _50[index], 'optionalAccess', _51 => _51.startBeat]), () => ( 0));
1560
+ const endBeat = _nullishCoalesce(_optionalChain([units, 'access', _52 => _52[end - 1], 'optionalAccess', _53 => _53.endBeat]), () => ( spanEnd));
2098
1561
  const sectionFirstBar = startBar + index * unitBars;
2099
1562
  const sectionLastBar = Math.max(
2100
1563
  sectionFirstBar,
@@ -2105,7 +1568,7 @@ function sectionsFromNotes(notes, opts = {}) {
2105
1568
  if (first === void 0) {
2106
1569
  firstByLabel.set(label, sectionIndex);
2107
1570
  }
2108
- const similarity = first === void 0 ? 1 : _chunkSGLEQVJHcjs.clamp01.call(void 0, scoreSum / Math.max(1, sounded));
1571
+ const similarity = first === void 0 ? 1 : _chunkIFZDWIKMcjs.clamp01.call(void 0, scoreSum / Math.max(1, sounded));
2109
1572
  sections.push({
2110
1573
  label,
2111
1574
  startBeat,
@@ -2113,163 +1576,284 @@ function sectionsFromNotes(notes, opts = {}) {
2113
1576
  bars: sectionLastBar - sectionFirstBar + 1,
2114
1577
  firstOccurrence: _nullishCoalesce(first, () => ( sectionIndex)),
2115
1578
  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))}`
1579
+ 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', _54 => _54[first], 'optionalAccess', _55 => _55.startBeat]), () => ( 0))}`
2117
1580
  });
2118
1581
  index = end;
2119
1582
  }
2120
1583
  return sections;
2121
1584
  }
2122
1585
 
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;
1586
+ // src/analyze/reference/compare.ts
1587
+ var RATIONALE_FIELDS = 2;
1588
+ function rationaleOf(comparison) {
1589
+ const measured = [];
1590
+ for (const group of [comparison.form, comparison.harmony, comparison.melody, comparison.rhythm]) {
1591
+ for (const [name, value] of Object.entries(group)) {
1592
+ if (value !== null) {
1593
+ measured.push({ name, value, order: measured.length });
1594
+ }
1595
+ }
1596
+ }
1597
+ if (measured.length === 0) {
1598
+ return "Nothing to compare: neither profile has material for any field";
1599
+ }
1600
+ const describe = (entries) => entries.map((entry) => `${entry.name} ${Math.round(entry.value * 100)}%`).join(", ");
1601
+ const highest = [...measured].sort((x, y) => y.value - x.value || x.order - y.order);
1602
+ const lowest = [...measured].sort((x, y) => x.value - y.value || x.order - y.order);
1603
+ return `Most alike: ${describe(highest.slice(0, RATIONALE_FIELDS))}; least alike: ${describe(lowest.slice(0, RATIONALE_FIELDS))}`;
1604
+ }
1605
+ function compareReferences(a, b, opts) {
1606
+ _chunkIFZDWIKMcjs.assertReferenceProfile.call(void 0, a, "reference profile a");
1607
+ _chunkIFZDWIKMcjs.assertReferenceProfile.call(void 0, b, "reference profile b");
1608
+ const { budget } = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "compare options");
1609
+ const structural = (profile) => profile.harmony.chords.filter((chord) => chord.level === "structural");
1610
+ const cadences = (profile) => profile.form.phrases.map((phrase) => _nullishCoalesce(_optionalChain([phrase, 'access', _56 => _56.cadence, 'optionalAccess', _57 => _57.type]), () => ( null)));
1611
+ const phraseMelodies = (profile) => profile.form.phrases.map((phrase) => phrase.melody);
1612
+ const comparison = {
1613
+ form: {
1614
+ sectionSequenceSimilarity: _chunkIFZDWIKMcjs.sectionSequenceSimilarity.call(void 0,
1615
+ a.form.sections.map((section) => section.label),
1616
+ b.form.sections.map((section) => section.label)
1617
+ ),
1618
+ phraseLengthSimilarity: _chunkIFZDWIKMcjs.phraseLengthSimilarity.call(void 0,
1619
+ a.form.phrases.map((phrase) => phrase.bars),
1620
+ b.form.phrases.map((phrase) => phrase.bars)
1621
+ )
1622
+ },
1623
+ harmony: {
1624
+ progressionSimilarity: _chunkIFZDWIKMcjs.progressionSimilarity.call(void 0, structural(a), structural(b), budget),
1625
+ functionSimilarity: _chunkIFZDWIKMcjs.functionSimilarity.call(void 0, a.harmony.chords, b.harmony.chords),
1626
+ cadenceSimilarity: _chunkIFZDWIKMcjs.cadenceSimilarity.call(void 0, cadences(a), cadences(b)),
1627
+ keyPlanSimilarity: _chunkIFZDWIKMcjs.keyPlanSimilarity.call(void 0,
1628
+ a.harmony.keys.map((region) => region.key),
1629
+ b.harmony.keys.map((region) => region.key)
1630
+ ),
1631
+ harmonicRhythmOnsetSimilarity: _chunkIFZDWIKMcjs.onsetSimilarity.call(void 0, a.harmony.rhythm, b.harmony.rhythm),
1632
+ harmonicRhythmDensitySimilarity: _chunkIFZDWIKMcjs.densitySimilarity.call(void 0, a.harmony.rhythm, b.harmony.rhythm)
1633
+ },
1634
+ melody: {
1635
+ surfaceSimilarity: _chunkIFZDWIKMcjs.surfaceSimilarity.call(void 0, a.melody.motifs, b.melody.motifs),
1636
+ motifStructureSimilarity: _chunkIFZDWIKMcjs.motifStructureSimilarity.call(void 0,
1637
+ { graph: a.melody.graph, spanBeats: a.span.endBeat - a.span.startBeat },
1638
+ { graph: b.melody.graph, spanBeats: b.span.endBeat - b.span.startBeat }
1639
+ ),
1640
+ contourSimilarity: _chunkIFZDWIKMcjs.contourSimilarity.call(void 0, phraseMelodies(a), phraseMelodies(b)),
1641
+ registerSimilarity: _chunkIFZDWIKMcjs.registerSimilarity.call(void 0,
1642
+ { phrases: phraseMelodies(a), mean: _nullishCoalesce(_optionalChain([a, 'access', _58 => _58.melody, 'access', _59 => _59.register, 'optionalAccess', _60 => _60.mean]), () => ( null)) },
1643
+ { phrases: phraseMelodies(b), mean: _nullishCoalesce(_optionalChain([b, 'access', _61 => _61.melody, 'access', _62 => _62.register, 'optionalAccess', _63 => _63.mean]), () => ( null)) }
1644
+ )
1645
+ },
1646
+ rhythm: {
1647
+ onsetSimilarity: _chunkIFZDWIKMcjs.onsetSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
1648
+ densitySimilarity: _chunkIFZDWIKMcjs.densitySimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
1649
+ durationSimilarity: _chunkIFZDWIKMcjs.durationSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm),
1650
+ syncopationSimilarity: _chunkIFZDWIKMcjs.syncopationSimilarity.call(void 0, a.melody.rhythm, b.melody.rhythm)
1651
+ }
1652
+ };
1653
+ return { ...comparison, rationale: rationaleOf(comparison) };
1654
+ }
1655
+
1656
+ // src/analyze/reference/profile.ts
1657
+ function spanOf(sounding, timeline, meters, totalBeats) {
1658
+ const segments = timeline.segments;
1659
+ const firstSegment = segments[0];
1660
+ const lastSegment = segments[segments.length - 1];
1661
+ const firstOnset = _chunkIFZDWIKMcjs.firstSoundingBeat.call(void 0, sounding, _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _64 => _64.startBeat]), () => ( 0)));
1662
+ const startBeat = _chunkIFZDWIKMcjs.barGridStart.call(void 0,
1663
+ Math.min(firstOnset, _nullishCoalesce(_optionalChain([firstSegment, 'optionalAccess', _65 => _65.startBeat]), () => ( firstOnset))),
1664
+ meters
1665
+ );
1666
+ const notesEnd = sounding.reduce(
1667
+ (end, note) => Math.max(end, note.startBeat + note.durationBeat),
1668
+ _nullishCoalesce(_optionalChain([lastSegment, 'optionalAccess', _66 => _66.endBeat]), () => ( 0))
1669
+ );
1670
+ return { startBeat, endBeat: Math.max(startBeat, _nullishCoalesce(totalBeats, () => ( notesEnd))) };
2127
1671
  }
2128
- function isStep(distance) {
2129
- const size = Math.abs(distance);
2130
- return size === 1 || size === 2;
1672
+ function sameKey(a, b) {
1673
+ return _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, a.scale.rootPc) === _chunkS5BKZZGMcjs.pitchClassOf.call(void 0, b.scale.rootPc) && a.scale.modeMask12 === b.scale.modeMask12;
2131
1674
  }
2132
- function touches(before, after) {
2133
- return Math.abs(after.startBeat - before.endBeat) <= _chunkS5BKZZGMcjs.BEAT_EPS;
1675
+ function keyIndexAt(regions, beat, prevailing) {
1676
+ const holding = regions.findIndex((region) => region.startBeat <= beat && beat < region.endBeat);
1677
+ if (holding >= 0) {
1678
+ return holding;
1679
+ }
1680
+ return Math.max(
1681
+ 0,
1682
+ regions.findIndex((region) => sameKey(region.key, prevailing))
1683
+ );
2134
1684
  }
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;
2140
- });
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";
1685
+ function sectionAt(sections, beat) {
1686
+ const index = sections.findIndex(
1687
+ (section) => beat >= section.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && beat < section.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS
1688
+ );
1689
+ return index < 0 ? null : index;
1690
+ }
1691
+ function readingsFor(notes, opts = {}) {
1692
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "reference options");
1693
+ const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, asked, "reference meters");
1694
+ const budget = asked.budget;
1695
+ if (asked.timeline === void 0) {
1696
+ return _chunkIFZDWIKMcjs.chordTimelineFromNotes.call(void 0, notes, {
1697
+ meters,
1698
+ key: asked.key,
1699
+ totalBeats: asked.totalBeats,
1700
+ budget
1701
+ });
2235
1702
  }
2236
- return reason.kind === "neighbor" ? "neighbor" : "structural";
1703
+ const timeline = _chunkIFZDWIKMcjs.assertChordTimeline.call(void 0, asked.timeline, "reference timeline");
1704
+ let keys;
1705
+ if (asked.key === void 0) {
1706
+ keys = _chunkIFZDWIKMcjs.keyTimelineFromNotes.call(void 0, notes, { meters, totalBeats: asked.totalBeats, budget });
1707
+ } else {
1708
+ const sounding = _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "reference notes", {
1709
+ allowNonPositiveDuration: true,
1710
+ budget
1711
+ }).filter((note) => note.durationBeat > 0);
1712
+ const span = spanOf(sounding, timeline, meters, asked.totalBeats);
1713
+ keys = [
1714
+ {
1715
+ startBeat: span.startBeat,
1716
+ endBeat: span.endBeat,
1717
+ key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, asked.key),
1718
+ confidence: 1
1719
+ }
1720
+ ];
1721
+ }
1722
+ return { timeline, keys, prevailingKey: _nullishCoalesce(_chunkIFZDWIKMcjs.prevailingKeyOf.call(void 0, keys), () => ( _chunk3R3QVKY5cjs.resolveKey.call(void 0, _chunk3R3QVKY5cjs.majorKey.call(void 0, 0)))) };
2237
1723
  }
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;
1724
+ function referenceFromReadings(notes, readings, opts = {}) {
1725
+ const asked = _chunkS5BKZZGMcjs.assertOptions.call(void 0, opts, "reference options");
1726
+ const meters = _chunkS5BKZZGMcjs.resolveMeters.call(void 0, asked, "reference meters");
1727
+ const budget = asked.budget;
1728
+ const sounding = _chunkS5BKZZGMcjs.assertNoteEvents.call(void 0, notes, "reference notes", {
1729
+ allowNonPositiveDuration: true,
1730
+ budget
1731
+ }).filter((note) => note.durationBeat > 0);
1732
+ if (asked.totalBeats !== void 0) {
1733
+ _chunkS5BKZZGMcjs.assertRange.call(void 0, asked.totalBeats, 0, Number.MAX_SAFE_INTEGER, "reference totalBeats");
1734
+ }
1735
+ const line = _chunkIFZDWIKMcjs.orderedNotes.call(void 0, _nullishCoalesce(asked.melody, () => ( notes)), "reference melody", budget);
1736
+ const { timeline, prevailingKey } = readings;
1737
+ const span = spanOf(sounding, timeline, meters, asked.totalBeats);
1738
+ const spanEnd = span.endBeat;
1739
+ const keyAt = _chunkIFZDWIKMcjs.keyLookup.call(void 0, readings.keys, prevailingKey);
1740
+ const scaleAt = (beat) => _chunk3R3QVKY5cjs.scaleOf.call(void 0, keyAt(beat));
1741
+ const cadenceBeats = _chunkIFZDWIKMcjs.detectCadences.call(void 0, timeline, scaleAt).map((hit) => hit.atBeat);
1742
+ const grouping = hypermeter(sounding, meters, { cadenceBeats, totalBeats: spanEnd, budget });
1743
+ const phrases = phrasesFromTimeline(timeline, notes, {
1744
+ meters,
1745
+ key: scaleAt,
1746
+ hypermeter: grouping,
1747
+ totalBeats: spanEnd,
1748
+ budget
1749
+ });
1750
+ const cadences = /* @__PURE__ */ new Map();
1751
+ for (const closing of structuralCadences(phrases)) {
1752
+ const type = closing.cadence.type;
1753
+ if (type !== null) {
1754
+ cadences.set(closing.phraseIndex, {
1755
+ atBeat: closing.atBeat,
1756
+ type,
1757
+ weight: closing.weight
1758
+ });
2251
1759
  }
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
1760
  }
2272
- return result;
1761
+ const sections = sectionsFromNotes(notes, {
1762
+ meters,
1763
+ unitBars: grouping.groupBars,
1764
+ totalBeats: spanEnd,
1765
+ budget
1766
+ });
1767
+ const keys = readings.keys.length === 0 && timeline.segments.length > 0 ? [
1768
+ {
1769
+ startBeat: span.startBeat,
1770
+ endBeat: spanEnd,
1771
+ key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, prevailingKey),
1772
+ confidence: 0
1773
+ }
1774
+ ] : readings.keys.map((region) => ({
1775
+ startBeat: region.startBeat,
1776
+ endBeat: region.endBeat,
1777
+ key: _chunk3R3QVKY5cjs.resolveKey.call(void 0, region.key),
1778
+ confidence: region.confidence
1779
+ }));
1780
+ const chords = _chunkIFZDWIKMcjs.reduceProgression.call(void 0, timeline, scaleAt).map((reduced) => {
1781
+ const index = keyIndexAt(keys, (reduced.startBeat + reduced.endBeat) / 2, prevailingKey);
1782
+ const key = keys[index].key;
1783
+ return {
1784
+ startBeat: reduced.startBeat,
1785
+ endBeat: reduced.endBeat,
1786
+ key: index,
1787
+ roman: _chunkIFZDWIKMcjs.chordToRoman.call(void 0, reduced.chord, key),
1788
+ function: _chunkIFZDWIKMcjs.functionOf.call(void 0, reduced.chord, key),
1789
+ level: reduced.level
1790
+ };
1791
+ });
1792
+ const chordOnsets = timeline.segments.map((segment) => ({
1793
+ pitch: 60,
1794
+ startBeat: segment.startBeat,
1795
+ durationBeat: segment.endBeat - segment.startBeat
1796
+ }));
1797
+ const harmonyRhythm = _chunkIFZDWIKMcjs.analyzeRhythm.call(void 0, chordOnsets, { meters, totalBeats: spanEnd, budget });
1798
+ const motifs = _chunkIFZDWIKMcjs.extractMotifs.call(void 0, line, { budget });
1799
+ const graph = _chunkIFZDWIKMcjs.motifGraph.call(void 0, line, motifs, { key: prevailingKey, budget });
1800
+ let register = null;
1801
+ if (line.length > 0) {
1802
+ const pitches = line.map((note) => note.pitch);
1803
+ register = {
1804
+ low: Math.min(...pitches),
1805
+ high: Math.max(...pitches),
1806
+ mean: _chunkIFZDWIKMcjs.weightedMean.call(void 0, line)
1807
+ };
1808
+ }
1809
+ const lineMean = _nullishCoalesce(_optionalChain([register, 'optionalAccess', _67 => _67.mean]), () => ( 0));
1810
+ const referenceMotifs = motifs.map((motif) => {
1811
+ const first = motif.occurrences[0];
1812
+ return {
1813
+ intervals: motif.intervals,
1814
+ rhythm: motif.rhythm,
1815
+ unitBeats: motif.notes.length < 2 ? 0 : motif.notes[1].startBeat - motif.notes[0].startBeat,
1816
+ spanBeats: first === void 0 ? 0 : first.endBeat - first.startBeat,
1817
+ occurrences: motif.occurrences.length
1818
+ };
1819
+ });
1820
+ const referencePhrases = phrases.map((phrase, index) => ({
1821
+ startBeat: phrase.startBeat,
1822
+ endBeat: phrase.endBeat,
1823
+ bars: _chunkIFZDWIKMcjs.barSpanOf.call(void 0, phrase.startBeat, phrase.endBeat, meters),
1824
+ section: sectionAt(sections, phrase.startBeat),
1825
+ confidence: phrase.confidence,
1826
+ cadence: _nullishCoalesce(cadences.get(index), () => ( null)),
1827
+ melody: _chunkIFZDWIKMcjs.phraseMelody.call(void 0, line, phrase.startBeat, phrase.endBeat, lineMean, meters, budget),
1828
+ motifNodes: graph.nodes.flatMap(
1829
+ (node, nodeIndex) => node.startBeat >= phrase.startBeat - _chunkS5BKZZGMcjs.BEAT_EPS && node.startBeat < phrase.endBeat - _chunkS5BKZZGMcjs.BEAT_EPS ? [nodeIndex] : []
1830
+ )
1831
+ }));
1832
+ return {
1833
+ profileVersion: _chunkIFZDWIKMcjs.REFERENCE_PROFILE_VERSION,
1834
+ span: {
1835
+ startBeat: span.startBeat,
1836
+ endBeat: spanEnd,
1837
+ bars: _chunkIFZDWIKMcjs.barSpanOf.call(void 0, span.startBeat, spanEnd, meters)
1838
+ },
1839
+ meters,
1840
+ form: {
1841
+ hypermeter: { groupBars: grouping.groupBars, confidence: grouping.confidence },
1842
+ sections,
1843
+ phrases: referencePhrases
1844
+ },
1845
+ harmony: { keys, chords, rhythm: harmonyRhythm },
1846
+ melody: {
1847
+ register,
1848
+ shape: line.length < 2 ? null : _chunkIFZDWIKMcjs.melodicContour.call(void 0, line).shape,
1849
+ motifs: referenceMotifs,
1850
+ graph,
1851
+ rhythm: _chunkIFZDWIKMcjs.analyzeRhythm.call(void 0, line, { meters, totalBeats: spanEnd, budget })
1852
+ }
1853
+ };
1854
+ }
1855
+ function analyzeReference(notes, opts) {
1856
+ return referenceFromReadings(notes, readingsFor(notes, opts), opts);
2273
1857
  }
2274
1858
 
2275
1859
 
@@ -2286,9 +1870,5 @@ function reduceProgression(timeline, key, opts = {}) {
2286
1870
 
2287
1871
 
2288
1872
 
2289
-
2290
-
2291
-
2292
-
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
1873
+ exports.trackRoleOf = trackRoleOf; exports.analyzePolyphony = analyzePolyphony; exports.analyzeArrangement = analyzeArrangement; exports.createArrangementSession = createArrangementSession; exports.tensionCurve = tensionCurve; exports.tensionCurveFrom = tensionCurveFrom; exports.hypermeter = hypermeter; exports.phrasesFromTimeline = phrasesFromTimeline; exports.structuralCadences = structuralCadences; exports.sectionsFromNotes = sectionsFromNotes; exports.compareReferences = compareReferences; exports.referenceFromReadings = referenceFromReadings; exports.analyzeReference = analyzeReference;
1874
+ //# sourceMappingURL=chunk-YFA7X2F6.cjs.map