@libraz/libcantus 0.9.4 → 0.9.5

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 (120) hide show
  1. package/README.md +27 -18
  2. package/README_ja.md +268 -0
  3. package/dist/analyze/index.cjs +7 -7
  4. package/dist/analyze/index.d.cts +9 -99
  5. package/dist/analyze/index.d.ts +9 -99
  6. package/dist/analyze/index.js +6 -6
  7. package/dist/{chunk-36N73KZ3.cjs → chunk-2A2QMA74.cjs} +80 -65
  8. package/dist/chunk-2A2QMA74.cjs.map +1 -0
  9. package/dist/{chunk-YLEMGEFH.js → chunk-2PTFGVD7.js} +50 -35
  10. package/dist/chunk-2PTFGVD7.js.map +1 -0
  11. package/dist/{chunk-ZEJR4TKL.cjs → chunk-357ZDPVU.cjs} +36 -47
  12. package/dist/chunk-357ZDPVU.cjs.map +1 -0
  13. package/dist/{chunk-EHVEBWM5.js → chunk-3KX76RLG.js} +136 -37
  14. package/dist/chunk-3KX76RLG.js.map +1 -0
  15. package/dist/{chunk-RC6GBMCD.cjs → chunk-4VZ5MZAM.cjs} +155 -111
  16. package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
  17. package/dist/{chunk-RK35RIN7.js → chunk-7EONYVI5.js} +115 -28
  18. package/dist/chunk-7EONYVI5.js.map +1 -0
  19. package/dist/{chunk-GQSGS2N4.js → chunk-7JEP3MFY.js} +5 -16
  20. package/dist/chunk-7JEP3MFY.js.map +1 -0
  21. package/dist/{chunk-7NKDPF74.js → chunk-AID75RN3.js} +203 -104
  22. package/dist/chunk-AID75RN3.js.map +1 -0
  23. package/dist/{chunk-FNDWXRRI.cjs → chunk-BSLTLGM3.cjs} +55 -37
  24. package/dist/chunk-BSLTLGM3.cjs.map +1 -0
  25. package/dist/{chunk-NP4NA2FB.cjs → chunk-CU6VG2YM.cjs} +39 -36
  26. package/dist/chunk-CU6VG2YM.cjs.map +1 -0
  27. package/dist/{chunk-NYYWMV2C.js → chunk-DZMHIGI4.js} +27 -17
  28. package/dist/chunk-DZMHIGI4.js.map +1 -0
  29. package/dist/{chunk-I24MV65U.js → chunk-EDOTEABK.js} +18 -15
  30. package/dist/{chunk-I24MV65U.js.map → chunk-EDOTEABK.js.map} +1 -1
  31. package/dist/{chunk-NKIBQ6PT.js → chunk-HN2OEUN2.js} +7 -4
  32. package/dist/chunk-HN2OEUN2.js.map +1 -0
  33. package/dist/{chunk-D2FAJBYL.js → chunk-KBTTEX7Q.js} +30 -12
  34. package/dist/chunk-KBTTEX7Q.js.map +1 -0
  35. package/dist/{chunk-CAN76NCU.cjs → chunk-KQN2ZYND.cjs} +147 -102
  36. package/dist/chunk-KQN2ZYND.cjs.map +1 -0
  37. package/dist/{chunk-4HLLZPXT.cjs → chunk-MPTVJIVG.cjs} +350 -251
  38. package/dist/chunk-MPTVJIVG.cjs.map +1 -0
  39. package/dist/{chunk-RUMU3UA6.cjs → chunk-MTMZTMWM.cjs} +12 -12
  40. package/dist/{chunk-RUMU3UA6.cjs.map → chunk-MTMZTMWM.cjs.map} +1 -1
  41. package/dist/{chunk-KMHOICIY.js → chunk-NNB67XA2.js} +90 -45
  42. package/dist/chunk-NNB67XA2.js.map +1 -0
  43. package/dist/{chunk-LW53AHQU.cjs → chunk-OL3IHHSJ.cjs} +228 -141
  44. package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
  45. package/dist/{chunk-7MGZ7D62.cjs → chunk-PI3UXRWK.cjs} +43 -13
  46. package/dist/chunk-PI3UXRWK.cjs.map +1 -0
  47. package/dist/{chunk-XEBG3G53.js → chunk-R2VVFAIN.js} +42 -12
  48. package/dist/chunk-R2VVFAIN.js.map +1 -0
  49. package/dist/{chunk-Q6OP4U43.cjs → chunk-WFDXOLUB.cjs} +50 -40
  50. package/dist/chunk-WFDXOLUB.cjs.map +1 -0
  51. package/dist/{chunk-NVE34YGZ.cjs → chunk-WTAN4ACG.cjs} +212 -113
  52. package/dist/chunk-WTAN4ACG.cjs.map +1 -0
  53. package/dist/{chunk-FCANXBAT.cjs → chunk-YFJXS3AU.cjs} +8 -5
  54. package/dist/chunk-YFJXS3AU.cjs.map +1 -0
  55. package/dist/{chunk-Q2XUP227.js → chunk-YUE6TJ4M.js} +89 -45
  56. package/dist/chunk-YUE6TJ4M.js.map +1 -0
  57. package/dist/{chunk-HKGYCQ34.js → chunk-ZGLJM4A4.js} +2 -2
  58. package/dist/core/index.cjs +15 -5
  59. package/dist/core/index.cjs.map +1 -1
  60. package/dist/core/index.d.cts +14 -4
  61. package/dist/core/index.d.ts +14 -4
  62. package/dist/core/index.js +14 -4
  63. package/dist/{function-DNugAOrt.d.cts → function-CxixTB02.d.cts} +98 -7
  64. package/dist/{function-BtiZb9i9.d.ts → function-DTCt675z.d.ts} +98 -7
  65. package/dist/generate/index.cjs +10 -10
  66. package/dist/generate/index.d.cts +21 -21
  67. package/dist/generate/index.d.ts +21 -21
  68. package/dist/generate/index.js +9 -9
  69. package/dist/{index-tJhkmQgE.d.cts → index-B2Ulxxpo.d.cts} +3 -2
  70. package/dist/{index-Be74mcon.d.cts → index-BkYJ0xMq.d.cts} +2 -2
  71. package/dist/{index-6VR8-kit.d.ts → index-BuaB-ypt.d.ts} +3 -2
  72. package/dist/{index-70p168VM.d.ts → index-CAN7v-kw.d.ts} +9 -2
  73. package/dist/{index-C83mZI1P.d.ts → index-COw1Ky9g.d.ts} +1 -1
  74. package/dist/{index-tSQh-xwY.d.ts → index-Cfx8xgH0.d.ts} +1 -1
  75. package/dist/{index-C5UBbQ4G.d.ts → index-DRtIhlJj.d.ts} +2 -2
  76. package/dist/{index-BtXT6UQ0.d.cts → index-alrRninU.d.cts} +9 -2
  77. package/dist/{index-y_ak9_Hi.d.cts → index-oTQbYYIs.d.cts} +1 -1
  78. package/dist/{index-OG65ocOr.d.ts → index-tR9jTn26.d.cts} +4 -1
  79. package/dist/{index-OG65ocOr.d.cts → index-tR9jTn26.d.ts} +4 -1
  80. package/dist/{index-D6b6na69.d.cts → index-x2vcsduE.d.cts} +1 -1
  81. package/dist/index.cjs +24 -14
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.d.cts +11 -11
  84. package/dist/index.d.ts +11 -11
  85. package/dist/index.js +23 -13
  86. package/dist/model/index.cjs +7 -7
  87. package/dist/model/index.d.cts +48 -16
  88. package/dist/model/index.d.ts +48 -16
  89. package/dist/model/index.js +6 -6
  90. package/dist/{styled-DIRkk9Uz.d.ts → styled-Bl0ZK28h.d.ts} +40 -3
  91. package/dist/{styled-DJbkJQfj.d.cts → styled-Crv6SA4S.d.cts} +40 -3
  92. package/dist/theory/index.cjs +6 -6
  93. package/dist/theory/index.d.cts +9 -9
  94. package/dist/theory/index.d.ts +9 -9
  95. package/dist/theory/index.js +5 -5
  96. package/package.json +12 -9
  97. package/dist/chunk-36N73KZ3.cjs.map +0 -1
  98. package/dist/chunk-4HLLZPXT.cjs.map +0 -1
  99. package/dist/chunk-7MGZ7D62.cjs.map +0 -1
  100. package/dist/chunk-7NKDPF74.js.map +0 -1
  101. package/dist/chunk-CAN76NCU.cjs.map +0 -1
  102. package/dist/chunk-D2FAJBYL.js.map +0 -1
  103. package/dist/chunk-EHVEBWM5.js.map +0 -1
  104. package/dist/chunk-FCANXBAT.cjs.map +0 -1
  105. package/dist/chunk-FNDWXRRI.cjs.map +0 -1
  106. package/dist/chunk-GQSGS2N4.js.map +0 -1
  107. package/dist/chunk-KMHOICIY.js.map +0 -1
  108. package/dist/chunk-LW53AHQU.cjs.map +0 -1
  109. package/dist/chunk-NKIBQ6PT.js.map +0 -1
  110. package/dist/chunk-NP4NA2FB.cjs.map +0 -1
  111. package/dist/chunk-NVE34YGZ.cjs.map +0 -1
  112. package/dist/chunk-NYYWMV2C.js.map +0 -1
  113. package/dist/chunk-Q2XUP227.js.map +0 -1
  114. package/dist/chunk-Q6OP4U43.cjs.map +0 -1
  115. package/dist/chunk-RC6GBMCD.cjs.map +0 -1
  116. package/dist/chunk-RK35RIN7.js.map +0 -1
  117. package/dist/chunk-XEBG3G53.js.map +0 -1
  118. package/dist/chunk-YLEMGEFH.js.map +0 -1
  119. package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
  120. /package/dist/{chunk-HKGYCQ34.js.map → chunk-ZGLJM4A4.js.map} +0 -0
@@ -4,17 +4,17 @@
4
4
 
5
5
 
6
6
 
7
- var _chunk36N73KZ3cjs = require('./chunk-36N73KZ3.cjs');
7
+ var _chunk2A2QMA74cjs = require('./chunk-2A2QMA74.cjs');
8
8
 
9
9
 
10
10
 
11
11
 
12
12
 
13
+ var _chunkKQN2ZYNDcjs = require('./chunk-KQN2ZYND.cjs');
13
14
 
14
- var _chunkCAN76NCUcjs = require('./chunk-CAN76NCU.cjs');
15
15
 
16
+ var _chunkWFDXOLUBcjs = require('./chunk-WFDXOLUB.cjs');
16
17
 
17
- var _chunkQ6OP4U43cjs = require('./chunk-Q6OP4U43.cjs');
18
18
 
19
19
 
20
20
 
@@ -24,8 +24,8 @@ var _chunkQ6OP4U43cjs = require('./chunk-Q6OP4U43.cjs');
24
24
 
25
25
 
26
26
 
27
+ var _chunkBSLTLGM3cjs = require('./chunk-BSLTLGM3.cjs');
27
28
 
28
- var _chunkFNDWXRRIcjs = require('./chunk-FNDWXRRI.cjs');
29
29
 
30
30
 
31
31
 
@@ -33,16 +33,15 @@ var _chunkFNDWXRRIcjs = require('./chunk-FNDWXRRI.cjs');
33
33
 
34
34
 
35
35
 
36
-
37
- var _chunk7MGZ7D62cjs = require('./chunk-7MGZ7D62.cjs');
36
+ var _chunkPI3UXRWKcjs = require('./chunk-PI3UXRWK.cjs');
38
37
 
39
38
  // src/analyze/timeline/index.ts
40
39
  function chordTimelineFromChords(chords, totalBeats) {
41
- _chunk7MGZ7D62cjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
42
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, chords.length, "timeline chords");
40
+ _chunkPI3UXRWKcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
41
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, chords.length, "timeline chords");
43
42
  for (let index = 0; index < chords.length; index += 1) {
44
- _chunk7MGZ7D62cjs.assertRange.call(void 0,
45
- _nullishCoalesce(_optionalChain([chords, 'access', _2 => _2[index], 'optionalAccess', _3 => _3.startBeat]), () => ( Number.NaN)),
43
+ _chunkPI3UXRWKcjs.assertRange.call(void 0,
44
+ _nullishCoalesce(_optionalChain([chords, 'access', _ => _[index], 'optionalAccess', _2 => _2.startBeat]), () => ( Number.NaN)),
46
45
  0,
47
46
  Number.MAX_SAFE_INTEGER,
48
47
  `chords[${index}].startBeat`
@@ -56,19 +55,19 @@ function chordTimelineFromChords(chords, totalBeats) {
56
55
  return {
57
56
  startBeat: gc.startBeat,
58
57
  endBeat,
59
- chord: _chunkFNDWXRRIcjs.makeChord.call(void 0, gc.rootPc, gc.quality, gc.bassPc)
58
+ chord: _chunkBSLTLGM3cjs.makeChord.call(void 0, gc.rootPc, gc.quality, gc.bassPc)
60
59
  };
61
60
  }).filter((seg) => seg.endBeat > seg.startBeat);
62
61
  return { at: segmentLookup(segments), segments };
63
62
  }
64
63
  function segmentLookup(segments) {
65
64
  return (beat) => {
66
- _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, beat, "timeline query beat");
65
+ _chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, beat, "timeline query beat");
67
66
  let low = 0;
68
67
  let high = segments.length;
69
68
  while (low < high) {
70
69
  const middle = Math.floor((low + high) / 2);
71
- if ((_nullishCoalesce(_optionalChain([segments, 'access', _4 => _4[middle], 'optionalAccess', _5 => _5.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat) {
70
+ if ((_nullishCoalesce(_optionalChain([segments, 'access', _3 => _3[middle], 'optionalAccess', _4 => _4.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat) {
72
71
  low = middle + 1;
73
72
  } else {
74
73
  high = middle;
@@ -81,16 +80,15 @@ function segmentLookup(segments) {
81
80
  var EPS = 1e-9;
82
81
  var NOISE_THRESHOLD_RATIO = 0.2;
83
82
  var MAX_DETECTION_PCS = 6;
84
- var FALLBACK_PCS = 3;
85
83
  var INEXACT_CONFIDENCE_FACTOR = 0.85;
86
84
  var DIATONIC_BONUS = 0.5;
87
85
  var EXACT_BONUS = 0.5;
88
86
  var MISMATCH_PENALTY = 0.3;
89
87
  function scoreMatch(match, weights, maxWeight, key) {
90
88
  const rootWeight = maxWeight > 0 ? (_nullishCoalesce(weights[match.rootPc], () => ( 0))) / maxWeight : 0;
91
- const tones = _chunkFNDWXRRIcjs.chordPitchClasses.call(void 0, _chunkFNDWXRRIcjs.makeChord.call(void 0, match.rootPc, match.quality));
89
+ const tones = _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, _chunkBSLTLGM3cjs.makeChord.call(void 0, match.rootPc, match.quality));
92
90
  let score = rootWeight;
93
- if (tones.every((pc) => _chunkFNDWXRRIcjs.isScaleTone.call(void 0, pc, key))) {
91
+ if (tones.every((pc) => _chunkBSLTLGM3cjs.isScaleTone.call(void 0, pc, key))) {
94
92
  score += DIATONIC_BONUS;
95
93
  }
96
94
  if (match.exact) {
@@ -100,7 +98,7 @@ function scoreMatch(match, weights, maxWeight, key) {
100
98
  return score;
101
99
  }
102
100
  function chordConfidence(chord, weights, totalWeight, exact) {
103
- const tones = new Set(_chunkFNDWXRRIcjs.chordPitchClasses.call(void 0, chord));
101
+ const tones = new Set(_chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, chord));
104
102
  let chordWeight = 0;
105
103
  for (let pc = 0; pc < 12; pc += 1) {
106
104
  if (tones.has(pc)) {
@@ -122,8 +120,8 @@ function analyzeWindow(notes, windowStart, windowEnd, ts, key) {
122
120
  }
123
121
  const velocityFactor = note.velocity !== void 0 ? note.velocity / 127 : 1;
124
122
  const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;
125
- const accent = onsetInWindow ? 1 + _chunk36N73KZ3cjs.metricWeight.call(void 0, note.startBeat, ts) / 3 : 1;
126
- const pc = _chunk7MGZ7D62cjs.pitchClassOf.call(void 0, note.pitch);
123
+ const accent = onsetInWindow ? 1 + _chunk2A2QMA74cjs.metricWeight.call(void 0, note.startBeat, ts) / 3 : 1;
124
+ const pc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, note.pitch);
127
125
  weights[pc] = (_nullishCoalesce(weights[pc], () => ( 0))) + overlap * velocityFactor * accent;
128
126
  if (note.pitch < lowestPitch) {
129
127
  lowestPitch = note.pitch;
@@ -140,10 +138,10 @@ function analyzeWindow(notes, windowStart, windowEnd, ts, key) {
140
138
  }
141
139
  const ranked = weights.map((weight, pc) => ({ pc, weight })).filter(({ weight }) => weight > 0).sort((a, b) => b.weight - a.weight || a.pc - b.pc);
142
140
  const selected = ranked.filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO).slice(0, MAX_DETECTION_PCS).map(({ pc }) => pc);
143
- const bassPc = _chunk7MGZ7D62cjs.pitchClassOf.call(void 0, lowestPitch);
144
- const anchorPc = selected.includes(bassPc) ? bassPc : _nullishCoalesce(selected[0], () => ( bassPc));
145
- const detectionPitches = selected.map((pc) => pc === anchorPc ? pc : pc + 12);
146
- const matches = _chunkCAN76NCUcjs.detectChord.call(void 0, detectionPitches);
141
+ const bassPc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, lowestPitch);
142
+ const hasBass = selected.includes(bassPc);
143
+ const detectionPitches = hasBass ? selected.map((pc) => pc === bassPc ? pc : pc + 12) : selected;
144
+ const matches = _chunkKQN2ZYNDcjs.detectChord.call(void 0, detectionPitches, { input: hasBass ? "midi" : "pitchClass" });
147
145
  let bestMatch = null;
148
146
  let bestScore = Number.NEGATIVE_INFINITY;
149
147
  for (const match of matches) {
@@ -154,47 +152,43 @@ function analyzeWindow(notes, windowStart, windowEnd, ts, key) {
154
152
  }
155
153
  }
156
154
  if (bestMatch) {
157
- const chord = _chunkFNDWXRRIcjs.makeChord.call(void 0, bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);
155
+ const chord = _chunkBSLTLGM3cjs.makeChord.call(void 0, bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);
158
156
  return { chord, confidence: chordConfidence(chord, weights, totalWeight, bestMatch.exact) };
159
157
  }
160
- const fallback = _chunkCAN76NCUcjs.detectChordBest.call(void 0, selected.slice(0, FALLBACK_PCS));
161
- if (fallback) {
162
- return { chord: fallback, confidence: chordConfidence(fallback, weights, totalWeight, false) };
163
- }
164
158
  return null;
165
159
  }
166
160
  function sameChord(a, b) {
167
161
  return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;
168
162
  }
169
163
  function chordTimelineFromNotes(notes, opts = {}) {
170
- const ts = _nullishCoalesce(opts.ts, () => ( _chunk36N73KZ3cjs.parseTimeSignature.call(void 0, "4/4")));
171
- _chunk7MGZ7D62cjs.assertTimeSignature.call(void 0, ts);
172
- const harmonicRhythm = _nullishCoalesce(opts.harmonicRhythm, () => ( _chunk36N73KZ3cjs.beatsPerBar.call(void 0, ts)));
173
- _chunk7MGZ7D62cjs.assertRange.call(void 0, harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "harmonic rhythm");
164
+ const ts = _nullishCoalesce(opts.ts, () => ( _chunk2A2QMA74cjs.parseTimeSignature.call(void 0, "4/4")));
165
+ _chunkPI3UXRWKcjs.assertTimeSignature.call(void 0, ts);
166
+ const harmonicRhythm = _nullishCoalesce(opts.harmonicRhythm, () => ( _chunk2A2QMA74cjs.beatsPerBar.call(void 0, ts)));
167
+ _chunkPI3UXRWKcjs.assertRange.call(void 0, harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "harmonic rhythm");
174
168
  const budget = opts.budget;
175
- _chunk7MGZ7D62cjs.assertNoteEvents.call(void 0, notes, "timeline notes", { allowNonPositiveDuration: true, budget });
176
- const soundingIndex = _chunk36N73KZ3cjs.createNoteEventIndex.call(void 0,
169
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, notes, "timeline notes", { allowNonPositiveDuration: true, budget });
170
+ const soundingIndex = _chunk2A2QMA74cjs.createNoteEventIndex.call(void 0,
177
171
  notes.filter((note) => note.durationBeat > 0),
178
172
  { name: "timeline notes", budget }
179
173
  );
180
174
  const sounding = soundingIndex.notes.map(({ note }) => note);
181
175
  const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
182
176
  const totalBeats = _nullishCoalesce(opts.totalBeats, () => ( lastNoteEnd));
183
- _chunk7MGZ7D62cjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
184
- const key = _nullishCoalesce(_nullishCoalesce(opts.key, () => ( _optionalChain([_chunkCAN76NCUcjs.detectKeyFromNotes.call(void 0, sounding), 'access', _6 => _6[0], 'optionalAccess', _7 => _7.key]))), () => ( _chunkFNDWXRRIcjs.majorKey.call(void 0, 0)));
177
+ _chunkPI3UXRWKcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
178
+ const key = _nullishCoalesce(_nullishCoalesce(opts.key, () => ( _optionalChain([_chunkKQN2ZYNDcjs.detectKeyFromNotes.call(void 0, sounding, { budget }), 'access', _5 => _5[0], 'optionalAccess', _6 => _6.key]))), () => ( _chunkBSLTLGM3cjs.majorKey.call(void 0, 0)));
185
179
  const segments = [];
186
180
  const segmentConfidence = [];
187
181
  const windowCount = Math.max(0, Math.ceil(totalBeats / harmonicRhythm - EPS));
188
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, windowCount, "timeline windows", budget);
182
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, windowCount, "timeline windows", budget);
189
183
  const windowNotes = Array.from({ length: windowCount }, () => []);
190
184
  let memberships = 0;
191
185
  for (const indexed of soundingIndex.notes) {
192
186
  const first = Math.max(0, Math.floor(indexed.note.startBeat / harmonicRhythm));
193
187
  const lastExclusive = Math.min(windowCount, Math.ceil(indexed.endBeat / harmonicRhythm));
194
188
  memberships += Math.max(0, lastExclusive - first);
195
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, memberships, "timeline note-to-window memberships", budget);
189
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, memberships, "timeline note-to-window memberships", budget);
196
190
  for (let window = first; window < lastExclusive; window += 1) {
197
- _optionalChain([windowNotes, 'access', _8 => _8[window], 'optionalAccess', _9 => _9.push, 'call', _10 => _10(indexed.note)]);
191
+ _optionalChain([windowNotes, 'access', _7 => _7[window], 'optionalAccess', _8 => _8.push, 'call', _9 => _9(indexed.note)]);
198
192
  }
199
193
  }
200
194
  for (let i = 0; i < windowCount; i += 1) {
@@ -233,7 +227,7 @@ function detectCadences(timeline, key) {
233
227
  if (Math.abs(cur.startBeat - prev.endBeat) > EPS) {
234
228
  continue;
235
229
  }
236
- const type = _chunkCAN76NCUcjs.detectCadence.call(void 0, prev.chord, cur.chord, key);
230
+ const type = _chunkKQN2ZYNDcjs.detectCadence.call(void 0, prev.chord, cur.chord, key);
237
231
  if (type !== null) {
238
232
  hits.push({ atBeat: cur.startBeat, type, from: prev.chord, to: cur.chord });
239
233
  }
@@ -270,12 +264,15 @@ function poolNotes(tracks, only) {
270
264
  }
271
265
  function splitIntoSubVoices(ordered) {
272
266
  const lanes = [];
273
- for (const note of ordered) {
267
+ const attachNearest = (note, excluded = /* @__PURE__ */ new Set()) => {
274
268
  let best = -1;
275
269
  let bestDistance = Number.POSITIVE_INFINITY;
276
270
  let bestEnd = Number.NEGATIVE_INFINITY;
277
271
  for (let lane = 0; lane < lanes.length; lane += 1) {
278
- const last = _optionalChain([lanes, 'access', _11 => _11[lane], 'optionalAccess', _12 => _12[(_nullishCoalesce(_optionalChain([lanes, 'access', _13 => _13[lane], 'optionalAccess', _14 => _14.length]), () => ( 0))) - 1]]);
272
+ if (excluded.has(lane)) {
273
+ continue;
274
+ }
275
+ const last = _optionalChain([lanes, 'access', _10 => _10[lane], 'optionalAccess', _11 => _11[(_nullishCoalesce(_optionalChain([lanes, 'access', _12 => _12[lane], 'optionalAccess', _13 => _13.length]), () => ( 0))) - 1]]);
279
276
  if (last === void 0) {
280
277
  continue;
281
278
  }
@@ -294,10 +291,40 @@ function splitIntoSubVoices(ordered) {
294
291
  }
295
292
  }
296
293
  if (best >= 0) {
297
- _optionalChain([lanes, 'access', _15 => _15[best], 'optionalAccess', _16 => _16.push, 'call', _17 => _17(note)]);
294
+ _optionalChain([lanes, 'access', _14 => _14[best], 'optionalAccess', _15 => _15.push, 'call', _16 => _16(note)]);
295
+ return true;
296
+ }
297
+ return false;
298
+ };
299
+ for (let start = 0; start < ordered.length; ) {
300
+ const onset = _nullishCoalesce(_optionalChain([ordered, 'access', _17 => _17[start], 'optionalAccess', _18 => _18.startBeat]), () => ( 0));
301
+ let end = start + 1;
302
+ while (end < ordered.length && Math.abs((_nullishCoalesce(_optionalChain([ordered, 'access', _19 => _19[end], 'optionalAccess', _20 => _20.startBeat]), () => ( 0))) - onset) <= EPS2) {
303
+ end += 1;
304
+ }
305
+ const block = ordered.slice(start, end);
306
+ if (block.length > 1) {
307
+ const freeLanes = lanes.map((lane, index) => ({ index, last: lane[lane.length - 1] })).filter(
308
+ (entry) => entry.last !== void 0 && entry.last.startBeat + entry.last.durationBeat <= onset + EPS2
309
+ ).sort((a, b) => a.last.pitch - b.last.pitch);
310
+ const paired = /* @__PURE__ */ new Set();
311
+ for (let index = 0; index < block.length; index += 1) {
312
+ const note = block[index];
313
+ const lane = freeLanes[index];
314
+ if (note !== void 0 && lane !== void 0 && Math.abs(lane.last.pitch - note.pitch) <= MAX_LANE_LEAP) {
315
+ _optionalChain([lanes, 'access', _21 => _21[lane.index], 'optionalAccess', _22 => _22.push, 'call', _23 => _23(note)]);
316
+ paired.add(lane.index);
317
+ } else if (note !== void 0 && !attachNearest(note, paired)) {
318
+ lanes.push([note]);
319
+ }
320
+ }
298
321
  } else {
299
- lanes.push([note]);
322
+ const note = block[0];
323
+ if (note !== void 0 && !attachNearest(note)) {
324
+ lanes.push([note]);
325
+ }
300
326
  }
327
+ start = end;
301
328
  }
302
329
  return lanes;
303
330
  }
@@ -306,7 +333,7 @@ function lastStartingAtOrBefore(sounding, beat) {
306
333
  let high = sounding.length;
307
334
  while (low < high) {
308
335
  const middle = Math.floor((low + high) / 2);
309
- if ((_nullishCoalesce(_optionalChain([sounding, 'access', _18 => _18[middle], 'optionalAccess', _19 => _19.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + EPS2) {
336
+ if ((_nullishCoalesce(_optionalChain([sounding, 'access', _24 => _24[middle], 'optionalAccess', _25 => _25.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + EPS2) {
310
337
  low = middle + 1;
311
338
  } else {
312
339
  high = middle;
@@ -325,7 +352,7 @@ function prepareTracks(tracks) {
325
352
  const contiguous = prevEnd !== void 0 && Math.abs(prevEnd - note.startBeat) <= EPS2;
326
353
  return {
327
354
  pitch: note.pitch,
328
- prevPitch: contiguous ? _optionalChain([prev, 'optionalAccess', _20 => _20.pitch]) : void 0,
355
+ prevPitch: contiguous ? _optionalChain([prev, 'optionalAccess', _26 => _26.pitch]) : void 0,
329
356
  startBeat: note.startBeat,
330
357
  endBeat: note.startBeat + note.durationBeat
331
358
  };
@@ -362,19 +389,19 @@ function clamp01(value) {
362
389
  return Math.min(1, Math.max(0, value));
363
390
  }
364
391
  function tensionCurve(tracks, opts = {}) {
365
- const ts = _nullishCoalesce(opts.ts, () => ( _chunk36N73KZ3cjs.parseTimeSignature.call(void 0, "4/4")));
366
- _chunk7MGZ7D62cjs.assertTimeSignature.call(void 0, ts);
392
+ const ts = _nullishCoalesce(opts.ts, () => ( _chunk2A2QMA74cjs.parseTimeSignature.call(void 0, "4/4")));
393
+ _chunkPI3UXRWKcjs.assertTimeSignature.call(void 0, ts);
367
394
  const budget = opts.budget;
368
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
395
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
369
396
  for (let index = 0; index < tracks.length; index += 1) {
370
- _chunk7MGZ7D62cjs.assertNoteEvents.call(void 0, _nullishCoalesce(_optionalChain([tracks, 'access', _21 => _21[index], 'optionalAccess', _22 => _22.notes]), () => ( [])), `tracks[${index}].notes`, {
397
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, _nullishCoalesce(_optionalChain([tracks, 'access', _27 => _27[index], 'optionalAccess', _28 => _28.notes]), () => ( [])), `tracks[${index}].notes`, {
371
398
  allowNonPositiveDuration: true,
372
399
  budget
373
400
  });
374
401
  }
375
402
  const profile = _nullishCoalesce(opts.profile, () => ( "pop"));
376
403
  const step = _nullishCoalesce(opts.step, () => ( 1));
377
- _chunk7MGZ7D62cjs.assertRange.call(void 0, step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
404
+ _chunkPI3UXRWKcjs.assertRange.call(void 0, step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
378
405
  const harmonyTracks = opts.harmonyTracks === void 0 ? void 0 : new Set(opts.harmonyTracks.map(Math.trunc));
379
406
  const pooled = poolNotes(tracks, harmonyTracks);
380
407
  const totalBeats = poolNotes(tracks).reduce(
@@ -388,14 +415,18 @@ function tensionCurve(tracks, opts = {}) {
388
415
  budget
389
416
  }) : {
390
417
  timeline: opts.timeline,
391
- key: _nullishCoalesce(_nullishCoalesce(opts.key, () => ( _optionalChain([_chunkCAN76NCUcjs.detectKeyFromNotes.call(void 0, pooled), 'access', _23 => _23[0], 'optionalAccess', _24 => _24.key]))), () => ( _chunkFNDWXRRIcjs.majorKey.call(void 0, 0)))
418
+ key: _nullishCoalesce(_nullishCoalesce(opts.key, () => ( _optionalChain([_chunkKQN2ZYNDcjs.detectKeyFromNotes.call(void 0, pooled, { budget }), 'access', _29 => _29[0], 'optionalAccess', _30 => _30.key]))), () => ( _chunkBSLTLGM3cjs.majorKey.call(void 0, 0)))
392
419
  };
393
420
  const prepared = prepareTracks(tracks);
394
421
  const points = [];
395
422
  const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS2));
396
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, sampleCount, "tension samples", budget);
423
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, sampleCount, "tension samples", budget);
397
424
  const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
398
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, sampleCount * voiceCount, "tension sample-voice lookups", budget);
425
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0,
426
+ sampleCount * Math.min(128, voiceCount) * voiceCount,
427
+ "tension sample-voice comparisons",
428
+ budget
429
+ );
399
430
  for (let i = 0; i < sampleCount; i += 1) {
400
431
  const beat = i * step;
401
432
  points.push({ beat, tension: sampleTension(prepared, timeline, key, ts, profile, beat) });
@@ -427,24 +458,28 @@ function sampleTension(prepared, timeline, key, ts, profile, beat) {
427
458
  return 0;
428
459
  }
429
460
  const chord = timeline.at(beat);
430
- const functionScore = chord ? FUNCTION_TENSION[_chunkCAN76NCUcjs.functionOf.call(void 0, chord, key)] : 0;
431
- const chordPcs = chord ? new Set(_chunkFNDWXRRIcjs.chordPitchClasses.call(void 0, chord)) : null;
432
- const strongBeat = _chunk36N73KZ3cjs.isStrongBeat.call(void 0, beat, ts);
461
+ const functionScore = chord ? FUNCTION_TENSION[_chunkKQN2ZYNDcjs.functionOf.call(void 0, chord, key)] : 0;
462
+ const chordPcs = chord ? new Set(_chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, chord)) : null;
463
+ const strongBeat = _chunk2A2QMA74cjs.isStrongBeat.call(void 0, beat, ts);
433
464
  let nonChord = 0;
434
465
  let dissonant = 0;
435
- for (let i = 0; i < sounding.length; i += 1) {
436
- const voice = sounding[i];
437
- if (!voice) {
438
- continue;
439
- }
440
- if (chordPcs && !chordPcs.has(_chunk7MGZ7D62cjs.pitchClassOf.call(void 0, voice.pitch))) {
441
- nonChord += 1;
466
+ const pitches = /* @__PURE__ */ new Map();
467
+ for (const voice of sounding) {
468
+ pitches.set(voice.pitch, (_nullishCoalesce(pitches.get(voice.pitch), () => ( 0))) + 1);
469
+ }
470
+ for (const [pitch, count] of pitches) {
471
+ if (chordPcs && !chordPcs.has(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch))) {
472
+ nonChord += count;
442
473
  }
443
- const others = sounding.filter((_, j) => j !== i).map((s) => ({ pitch: s.pitch }));
444
- const result = _chunkQ6OP4U43cjs.evaluateSafety.call(void 0,
474
+ const others = [...pitches].flatMap(
475
+ ([otherPitch, otherCount]) => Array.from({ length: otherCount - (otherPitch === pitch ? 1 : 0) }, () => ({
476
+ pitch: otherPitch
477
+ }))
478
+ );
479
+ const result = _chunkWFDXOLUBcjs.evaluateSafety.call(void 0,
445
480
  {
446
481
  profile,
447
- candidatePitch: voice.pitch,
482
+ candidatePitch: pitch,
448
483
  chord,
449
484
  key,
450
485
  otherVoices: others,
@@ -453,7 +488,7 @@ function sampleTension(prepared, timeline, key, ts, profile, beat) {
453
488
  { suggestions: false }
454
489
  );
455
490
  if (result.safety === 2 /* Dissonant */) {
456
- dissonant += 1;
491
+ dissonant += count;
457
492
  }
458
493
  }
459
494
  const dissonanceScore = Math.max(nonChord, dissonant) / sounding.length;
@@ -473,7 +508,7 @@ function sampleTension(prepared, timeline, key, ts, profile, beat) {
473
508
  function toVoiceNotes(events) {
474
509
  return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));
475
510
  }
476
- var EPS3 = 1e-9;
511
+ var HUMANIZE_ADJACENCY = 0.05;
477
512
  function intervalAboveBass(pitch, others, chord) {
478
513
  let bass = Number.POSITIVE_INFINITY;
479
514
  for (const ov of others) {
@@ -483,7 +518,7 @@ function intervalAboveBass(pitch, others, chord) {
483
518
  return ((pitch - bass) % 12 + 12) % 12;
484
519
  }
485
520
  const ref = _nullishCoalesce(chord.bassPc, () => ( chord.rootPc));
486
- return ((_chunk7MGZ7D62cjs.pitchClassOf.call(void 0, pitch) - _chunk7MGZ7D62cjs.pitchClassOf.call(void 0, ref)) % 12 + 12) % 12;
521
+ return ((_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch) - _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, ref)) % 12 + 12) % 12;
487
522
  }
488
523
  function tensionDegree(ic) {
489
524
  if (ic === 1 || ic === 2) {
@@ -518,14 +553,14 @@ function suspensionType(ic, delta) {
518
553
  }
519
554
  function stepResolution(pitch, chord) {
520
555
  for (let delta = 1; delta <= 2; delta += 1) {
521
- if (_chunkFNDWXRRIcjs.isChordMember.call(void 0, pitch - delta, chord)) {
556
+ if (_chunkBSLTLGM3cjs.isChordMember.call(void 0, pitch - delta, chord)) {
522
557
  return pitch - delta;
523
558
  }
524
559
  }
525
560
  return void 0;
526
561
  }
527
562
  function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
528
- _chunk7MGZ7D62cjs.assertNoteEvents.call(void 0, voice, "voice notes", { allowNonPositiveDuration: true });
563
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, voice, "voice notes", { allowNonPositiveDuration: true });
529
564
  const result = [];
530
565
  for (let i = 0; i < voice.length; i += 1) {
531
566
  const note = voice[i];
@@ -536,29 +571,29 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
536
571
  const nextNote = i + 1 < voice.length ? voice[i + 1] : void 0;
537
572
  const prevEnd = prevNote === void 0 ? Number.NEGATIVE_INFINITY : prevNote.startBeat + prevNote.durationBeat;
538
573
  const noteEnd = note.startBeat + note.durationBeat;
539
- const prev = prevNote !== void 0 && Math.abs(prevEnd - note.startBeat) <= EPS3 ? prevNote : void 0;
540
- const next = nextNote !== void 0 && Math.abs(noteEnd - nextNote.startBeat) <= EPS3 ? nextNote : void 0;
574
+ const prev = prevNote !== void 0 && Math.abs(prevEnd - note.startBeat) <= HUMANIZE_ADJACENCY ? prevNote : void 0;
575
+ const next = nextNote !== void 0 && Math.abs(noteEnd - nextNote.startBeat) <= HUMANIZE_ADJACENCY ? nextNote : void 0;
541
576
  const chord = chordAtBeat(note.startBeat);
542
577
  const labels = [];
543
- const member = _chunkFNDWXRRIcjs.isChordMember.call(void 0, note.pitch, chord);
578
+ const member = _chunkBSLTLGM3cjs.isChordMember.call(void 0, note.pitch, chord);
544
579
  let handled = false;
545
580
  if (chord && member) {
546
- const role = _chunkFNDWXRRIcjs.chordToneRole.call(void 0, note.pitch, chord);
581
+ const role = _chunkBSLTLGM3cjs.chordToneRole.call(void 0, note.pitch, chord);
547
582
  if (role) {
548
583
  labels.push({ kind: "chordTone", role });
549
584
  } else {
550
- const ic = _chunkFNDWXRRIcjs.intervalAboveRoot.call(void 0, note.pitch, chord);
585
+ const ic = _chunkBSLTLGM3cjs.intervalAboveRoot.call(void 0, note.pitch, chord);
551
586
  labels.push({ kind: "tension", degree: tensionDegree(ic) });
552
587
  }
553
588
  handled = true;
554
589
  }
555
590
  if (!handled && chord && prev && next) {
556
591
  const prevChord = chordAtBeat(prev.startBeat);
557
- const prepared = prev.pitch === note.pitch && _chunkFNDWXRRIcjs.isChordMember.call(void 0, prev.pitch, prevChord);
592
+ const prepared = prev.pitch === note.pitch && _chunkBSLTLGM3cjs.isChordMember.call(void 0, prev.pitch, prevChord);
558
593
  const resolves = isStep(next.pitch, note.pitch);
559
594
  const others = otherVoicesAtBeat(note.startBeat);
560
595
  const verticallyDissonant = others.some(
561
- (ov) => _chunkFNDWXRRIcjs.createsVerticalDissonance.call(void 0, note.pitch, ov.pitch, true)
596
+ (ov) => _chunkBSLTLGM3cjs.createsVerticalDissonance.call(void 0, note.pitch, ov.pitch, true)
562
597
  );
563
598
  if (prepared && verticallyDissonant && resolves) {
564
599
  const ic = intervalAboveBass(note.pitch, others, chord);
@@ -570,8 +605,8 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
570
605
  }
571
606
  }
572
607
  if (!handled && chord && prev && next) {
573
- const prevMember = _chunkFNDWXRRIcjs.isChordMember.call(void 0, prev.pitch, chordAtBeat(prev.startBeat));
574
- const nextMember = _chunkFNDWXRRIcjs.isChordMember.call(void 0, next.pitch, chordAtBeat(next.startBeat));
608
+ const prevMember = _chunkBSLTLGM3cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat(prev.startBeat));
609
+ const nextMember = _chunkBSLTLGM3cjs.isChordMember.call(void 0, next.pitch, chordAtBeat(next.startBeat));
575
610
  const up = note.pitch - prev.pitch > 0;
576
611
  const contInto = next.pitch - note.pitch > 0;
577
612
  const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;
@@ -580,20 +615,20 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
580
615
  handled = true;
581
616
  }
582
617
  }
583
- if (!handled && chord && prev && next && !member && prev.pitch === next.pitch && _chunkFNDWXRRIcjs.isChordMember.call(void 0, prev.pitch, chordAtBeat(prev.startBeat)) && isStep(note.pitch, prev.pitch)) {
618
+ if (!handled && chord && prev && next && !member && prev.pitch === next.pitch && _chunkBSLTLGM3cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat(prev.startBeat)) && isStep(note.pitch, prev.pitch)) {
584
619
  labels.push({ kind: "neighbor" });
585
620
  handled = true;
586
621
  }
587
622
  if (!handled && chord && next && !member && note.pitch === next.pitch) {
588
623
  const nextChord = chordAtBeat(next.startBeat);
589
- if (_chunkFNDWXRRIcjs.isChordMember.call(void 0, next.pitch, nextChord)) {
624
+ if (_chunkBSLTLGM3cjs.isChordMember.call(void 0, next.pitch, nextChord)) {
590
625
  labels.push({ kind: "anticipation" });
591
626
  handled = true;
592
627
  }
593
628
  }
594
629
  if (!handled && chord && prev && next && !member) {
595
- const prevMember = _chunkFNDWXRRIcjs.isChordMember.call(void 0, prev.pitch, chordAtBeat(prev.startBeat));
596
- const nextMember = _chunkFNDWXRRIcjs.isChordMember.call(void 0, next.pitch, chordAtBeat(next.startBeat));
630
+ const prevMember = _chunkBSLTLGM3cjs.isChordMember.call(void 0, prev.pitch, chordAtBeat(prev.startBeat));
631
+ const nextMember = _chunkBSLTLGM3cjs.isChordMember.call(void 0, next.pitch, chordAtBeat(next.startBeat));
597
632
  const stepFromPrev = isStep(note.pitch, prev.pitch);
598
633
  const leapToNext = Math.abs(next.pitch - note.pitch) >= 3;
599
634
  const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;
@@ -603,7 +638,7 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
603
638
  }
604
639
  }
605
640
  if (!handled && chord && !member) {
606
- const ic = _chunkFNDWXRRIcjs.intervalAboveRoot.call(void 0, note.pitch, chord);
641
+ const ic = _chunkBSLTLGM3cjs.intervalAboveRoot.call(void 0, note.pitch, chord);
607
642
  const isTension = ic === 2 || ic === 5 || ic === 9;
608
643
  const avoid = ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5) || ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10);
609
644
  if (avoid) {
@@ -621,7 +656,7 @@ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
621
656
  }
622
657
  }
623
658
  }
624
- if (next && _chunkFNDWXRRIcjs.isLeadingToneResolution.call(void 0, note.pitch, next.pitch, key)) {
659
+ if (next && _chunkBSLTLGM3cjs.isLeadingToneResolution.call(void 0, note.pitch, next.pitch, key)) {
625
660
  labels.push({ kind: "leadingTone", resolveTo: next.pitch });
626
661
  }
627
662
  const analyzed = {
@@ -669,7 +704,7 @@ function describe(labels) {
669
704
  }
670
705
 
671
706
  // src/analyze/arrange/tracks.ts
672
- function otherVoicesSounding(prepared, excludeTrack, excludeVoice, beat) {
707
+ function soundingVoicesAt(prepared, beat) {
673
708
  const out = [];
674
709
  for (let t = 0; t < prepared.length; t += 1) {
675
710
  const track = prepared[t];
@@ -677,10 +712,7 @@ function otherVoicesSounding(prepared, excludeTrack, excludeVoice, beat) {
677
712
  continue;
678
713
  }
679
714
  for (let v = 0; v < track.voices.length; v += 1) {
680
- if (t === excludeTrack && v === excludeVoice) {
681
- continue;
682
- }
683
- const note = _optionalChain([track, 'access', _25 => _25.voices, 'access', _26 => _26[v], 'optionalAccess', _27 => _27.at, 'call', _28 => _28(beat)]);
715
+ const note = _optionalChain([track, 'access', _31 => _31.voices, 'access', _32 => _32[v], 'optionalAccess', _33 => _33.at, 'call', _34 => _34(beat)]);
684
716
  if (note === void 0) {
685
717
  continue;
686
718
  }
@@ -689,17 +721,22 @@ function otherVoicesSounding(prepared, excludeTrack, excludeVoice, beat) {
689
721
  if (previous !== void 0) {
690
722
  snap.prevPitch = previous;
691
723
  }
692
- out.push(snap);
724
+ out.push({ track: t, voice: v, snapshot: snap });
693
725
  }
694
726
  }
695
727
  return out;
696
728
  }
729
+ function otherVoicesSounding(prepared, excludeTrack, excludeVoice, beat, cache) {
730
+ const sounding = _nullishCoalesce(cache.get(beat), () => ( soundingVoicesAt(prepared, beat)));
731
+ cache.set(beat, sounding);
732
+ return sounding.filter(({ track, voice }) => track !== excludeTrack || voice !== excludeVoice).map(({ snapshot }) => snapshot);
733
+ }
697
734
  function firstSegmentAfter(timeline, beat) {
698
735
  let low = 0;
699
736
  let high = timeline.segments.length;
700
737
  while (low < high) {
701
738
  const middle = Math.floor((low + high) / 2);
702
- if ((_nullishCoalesce(_optionalChain([timeline, 'access', _29 => _29.segments, 'access', _30 => _30[middle], 'optionalAccess', _31 => _31.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + EPS2) {
739
+ if ((_nullishCoalesce(_optionalChain([timeline, 'access', _35 => _35.segments, 'access', _36 => _36[middle], 'optionalAccess', _37 => _37.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + EPS2) {
703
740
  low = middle + 1;
704
741
  } else {
705
742
  high = middle;
@@ -711,7 +748,7 @@ function evaluationBeats(note, timeline) {
711
748
  const noteEnd = note.startBeat + note.durationBeat;
712
749
  const beats = [note.startBeat];
713
750
  for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {
714
- const start = _optionalChain([timeline, 'access', _32 => _32.segments, 'access', _33 => _33[i], 'optionalAccess', _34 => _34.startBeat]);
751
+ const start = _optionalChain([timeline, 'access', _38 => _38.segments, 'access', _39 => _39[i], 'optionalAccess', _40 => _40.startBeat]);
715
752
  if (start === void 0 || start >= noteEnd - EPS2) {
716
753
  break;
717
754
  }
@@ -720,25 +757,25 @@ function evaluationBeats(note, timeline) {
720
757
  return beats;
721
758
  }
722
759
  function analyzeArrangement(tracks, opts = {}) {
723
- const ts = _nullishCoalesce(opts.ts, () => ( _chunk36N73KZ3cjs.parseTimeSignature.call(void 0, "4/4")));
724
- _chunk7MGZ7D62cjs.assertTimeSignature.call(void 0, ts);
760
+ const ts = _nullishCoalesce(opts.ts, () => ( _chunk2A2QMA74cjs.parseTimeSignature.call(void 0, "4/4")));
761
+ _chunkPI3UXRWKcjs.assertTimeSignature.call(void 0, ts);
725
762
  const budget = opts.budget;
726
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
763
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
727
764
  let noteCount = 0;
728
765
  for (let index = 0; index < tracks.length; index += 1) {
729
- const notes = _optionalChain([tracks, 'access', _35 => _35[index], 'optionalAccess', _36 => _36.notes]);
766
+ const notes = _optionalChain([tracks, 'access', _41 => _41[index], 'optionalAccess', _42 => _42.notes]);
730
767
  if (!Array.isArray(notes)) {
731
- throw new (0, _chunk7MGZ7D62cjs.InvalidInputError)(
768
+ throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
732
769
  `tracks[${index}].notes must be an array; received ${typeof notes}`
733
770
  );
734
771
  }
735
- _chunk7MGZ7D62cjs.assertNoteEvents.call(void 0, notes, `tracks[${index}].notes`, {
772
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, notes, `tracks[${index}].notes`, {
736
773
  allowNonPositiveDuration: true,
737
774
  budget
738
775
  });
739
776
  noteCount += notes.length;
740
777
  }
741
- _chunk7MGZ7D62cjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
778
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
742
779
  const profile = _nullishCoalesce(opts.profile, () => ( "pop"));
743
780
  const minSeverity = _nullishCoalesce(opts.minSeverity, () => ( 1)) /* Warning */;
744
781
  const harmonyTracks = opts.harmonyTracks === void 0 ? void 0 : new Set(opts.harmonyTracks.map(Math.trunc));
@@ -746,15 +783,20 @@ function analyzeArrangement(tracks, opts = {}) {
746
783
  const inferred = opts.timeline === void 0 ? chordTimelineFromNotes(pooled, {
747
784
  key: opts.key,
748
785
  ts,
749
- harmonicRhythm: opts.harmonicRhythm
786
+ harmonicRhythm: opts.harmonicRhythm,
787
+ budget
750
788
  }) : {
751
789
  timeline: opts.timeline,
752
- key: _nullishCoalesce(_nullishCoalesce(opts.key, () => ( _optionalChain([_chunkCAN76NCUcjs.detectKeyFromNotes.call(void 0, pooled), 'access', _37 => _37[0], 'optionalAccess', _38 => _38.key]))), () => ( _chunkFNDWXRRIcjs.majorKey.call(void 0, 0))),
753
- segmentConfidence: opts.timeline.segments.map(() => 1)
790
+ key: _nullishCoalesce(_nullishCoalesce(opts.key, () => ( _optionalChain([_chunkKQN2ZYNDcjs.detectKeyFromNotes.call(void 0, pooled, { budget }), 'access', _43 => _43[0], 'optionalAccess', _44 => _44.key]))), () => ( _chunkBSLTLGM3cjs.majorKey.call(void 0, 0))),
791
+ // A caller-provided timeline was not inferred from these notes, so
792
+ // it has no measured confidence. Reporting 1 would turn confidence
793
+ // gates into unconditional passes.
794
+ segmentConfidence: opts.timeline.segments.map(() => 0)
754
795
  };
755
796
  const { timeline, key, segmentConfidence } = inferred;
756
797
  const cadences = detectCadences(timeline, key);
757
798
  const prepared = prepareTracks(tracks);
799
+ const soundingCache = /* @__PURE__ */ new Map();
758
800
  const trackAnalyses = [];
759
801
  const conflicts = [];
760
802
  for (let t = 0; t < prepared.length; t += 1) {
@@ -776,7 +818,7 @@ function analyzeArrangement(tracks, opts = {}) {
776
818
  subVoice.voice,
777
819
  timeline.at,
778
820
  key,
779
- (beat) => otherVoicesSounding(prepared, t, v, beat)
821
+ (beat) => otherVoicesSounding(prepared, t, v, beat, soundingCache)
780
822
  );
781
823
  notes.push(...analyzed.map((note) => ({ ...note, trackIndex: track.trackIndex })));
782
824
  const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));
@@ -788,16 +830,18 @@ function analyzeArrangement(tracks, opts = {}) {
788
830
  }
789
831
  for (const beat of evaluationBeats(note, timeline)) {
790
832
  const atOnset = Math.abs(beat - note.startBeat) <= EPS2;
791
- const result = _chunkQ6OP4U43cjs.evaluateSafety.call(void 0, {
833
+ const safetyQuery = {
792
834
  profile,
793
835
  candidatePitch: note.pitch,
794
836
  prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,
795
837
  chord: timeline.at(beat),
796
838
  key,
797
- otherVoices: otherVoicesSounding(prepared, t, v, beat),
798
- strongBeat: _chunk36N73KZ3cjs.isStrongBeat.call(void 0, beat, ts)
799
- });
839
+ otherVoices: otherVoicesSounding(prepared, t, v, beat, soundingCache),
840
+ strongBeat: _chunk2A2QMA74cjs.isStrongBeat.call(void 0, beat, ts)
841
+ };
842
+ let result = _chunkWFDXOLUBcjs.evaluateSafety.call(void 0, safetyQuery, { suggestions: false });
800
843
  if (result.safety !== 0 /* Safe */ && result.safety >= minSeverity) {
844
+ result = _chunkWFDXOLUBcjs.evaluateSafety.call(void 0, safetyQuery);
801
845
  const conflict = {
802
846
  beat,
803
847
  trackName: track.name,
@@ -842,4 +886,4 @@ function analyzeArrangement(tracks, opts = {}) {
842
886
 
843
887
 
844
888
  exports.chordTimelineFromChords = chordTimelineFromChords; exports.chordTimelineFromNotes = chordTimelineFromNotes; exports.detectCadences = detectCadences; exports.tensionCurve = tensionCurve; exports.tensionCurveFrom = tensionCurveFrom; exports.toVoiceNotes = toVoiceNotes; exports.analyzeVoice = analyzeVoice; exports.analyzeArrangement = analyzeArrangement;
845
- //# sourceMappingURL=chunk-RC6GBMCD.cjs.map
889
+ //# sourceMappingURL=chunk-4VZ5MZAM.cjs.map