@libraz/libcantus 0.9.3 → 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 (201) hide show
  1. package/README.md +29 -20
  2. package/README_ja.md +268 -0
  3. package/dist/analyze/index.cjs +39 -1947
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +161 -91
  6. package/dist/analyze/index.d.ts +161 -91
  7. package/dist/analyze/index.js +19 -8
  8. package/dist/chunk-2A2QMA74.cjs +261 -0
  9. package/dist/chunk-2A2QMA74.cjs.map +1 -0
  10. package/dist/chunk-2PTFGVD7.js +261 -0
  11. package/dist/chunk-2PTFGVD7.js.map +1 -0
  12. package/dist/chunk-357ZDPVU.cjs +162 -0
  13. package/dist/chunk-357ZDPVU.cjs.map +1 -0
  14. package/dist/chunk-3KX76RLG.js +1548 -0
  15. package/dist/chunk-3KX76RLG.js.map +1 -0
  16. package/dist/chunk-4VZ5MZAM.cjs +889 -0
  17. package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
  18. package/dist/{chunk-C5XYKUVG.js → chunk-7EONYVI5.js} +471 -171
  19. package/dist/chunk-7EONYVI5.js.map +1 -0
  20. package/dist/{chunk-IT2O4XFE.js → chunk-7JEP3MFY.js} +42 -34
  21. package/dist/chunk-7JEP3MFY.js.map +1 -0
  22. package/dist/{chunk-4MHA432G.js → chunk-AID75RN3.js} +572 -250
  23. package/dist/chunk-AID75RN3.js.map +1 -0
  24. package/dist/chunk-BSLTLGM3.cjs +497 -0
  25. package/dist/chunk-BSLTLGM3.cjs.map +1 -0
  26. package/dist/chunk-CU6VG2YM.cjs +109 -0
  27. package/dist/chunk-CU6VG2YM.cjs.map +1 -0
  28. package/dist/{chunk-EH6EM37U.js → chunk-DZMHIGI4.js} +81 -59
  29. package/dist/chunk-DZMHIGI4.js.map +1 -0
  30. package/dist/{chunk-AKLYPWIN.js → chunk-EDOTEABK.js} +45 -19
  31. package/dist/chunk-EDOTEABK.js.map +1 -0
  32. package/dist/{chunk-2ZWMSJH6.js → chunk-HN2OEUN2.js} +7 -4
  33. package/dist/chunk-HN2OEUN2.js.map +1 -0
  34. package/dist/{chunk-MIWO425M.js → chunk-KBTTEX7Q.js} +128 -43
  35. package/dist/chunk-KBTTEX7Q.js.map +1 -0
  36. package/dist/chunk-KQN2ZYND.cjs +724 -0
  37. package/dist/chunk-KQN2ZYND.cjs.map +1 -0
  38. package/dist/chunk-MPTVJIVG.cjs +3176 -0
  39. package/dist/chunk-MPTVJIVG.cjs.map +1 -0
  40. package/dist/chunk-MTMZTMWM.cjs +53 -0
  41. package/dist/chunk-MTMZTMWM.cjs.map +1 -0
  42. package/dist/{chunk-CHMXELDS.js → chunk-NNB67XA2.js} +372 -197
  43. package/dist/chunk-NNB67XA2.js.map +1 -0
  44. package/dist/chunk-OL3IHHSJ.cjs +1204 -0
  45. package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
  46. package/dist/chunk-PI3UXRWK.cjs +528 -0
  47. package/dist/chunk-PI3UXRWK.cjs.map +1 -0
  48. package/dist/chunk-R2VVFAIN.js +528 -0
  49. package/dist/chunk-R2VVFAIN.js.map +1 -0
  50. package/dist/chunk-T64C7ZD7.cjs +1 -0
  51. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  52. package/dist/chunk-WFDXOLUB.cjs +337 -0
  53. package/dist/chunk-WFDXOLUB.cjs.map +1 -0
  54. package/dist/chunk-WTAN4ACG.cjs +1548 -0
  55. package/dist/chunk-WTAN4ACG.cjs.map +1 -0
  56. package/dist/chunk-YFJXS3AU.cjs +33 -0
  57. package/dist/chunk-YFJXS3AU.cjs.map +1 -0
  58. package/dist/{chunk-GOAW4VYL.js → chunk-YUE6TJ4M.js} +315 -145
  59. package/dist/chunk-YUE6TJ4M.js.map +1 -0
  60. package/dist/{chunk-62QZKCJQ.js → chunk-ZGLJM4A4.js} +10 -6
  61. package/dist/chunk-ZGLJM4A4.js.map +1 -0
  62. package/dist/core/index.cjs +140 -647
  63. package/dist/core/index.cjs.map +1 -1
  64. package/dist/core/index.d.cts +313 -41
  65. package/dist/core/index.d.ts +313 -41
  66. package/dist/core/index.js +64 -16
  67. package/dist/{function-DVH4Eusr.d.ts → function-CxixTB02.d.cts} +122 -10
  68. package/dist/{function-DYt4iKsn.d.cts → function-DTCt675z.d.ts} +122 -10
  69. package/dist/generate/index.cjs +28 -4085
  70. package/dist/generate/index.cjs.map +1 -1
  71. package/dist/generate/index.d.cts +374 -66
  72. package/dist/generate/index.d.ts +374 -66
  73. package/dist/generate/index.js +23 -9
  74. package/dist/{index-Jc0Ryi79.d.ts → index-B2Ulxxpo.d.cts} +21 -5
  75. package/dist/{index-CpOsP1y9.d.cts → index-BkYJ0xMq.d.cts} +18 -17
  76. package/dist/{index-CxqBQtH1.d.cts → index-BuaB-ypt.d.ts} +21 -5
  77. package/dist/index-CAN7v-kw.d.ts +199 -0
  78. package/dist/{index-Bt4tOUD9.d.ts → index-COw1Ky9g.d.ts} +93 -8
  79. package/dist/{index-Dikst44v.d.cts → index-Cfx8xgH0.d.ts} +14 -11
  80. package/dist/{index-DxvARIP2.d.ts → index-DRtIhlJj.d.ts} +18 -17
  81. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  82. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  83. package/dist/index-alrRninU.d.cts +199 -0
  84. package/dist/{index-BNlGP86A.d.cts → index-oTQbYYIs.d.cts} +93 -8
  85. package/dist/index-tR9jTn26.d.cts +275 -0
  86. package/dist/index-tR9jTn26.d.ts +275 -0
  87. package/dist/{index-Bg5dYMmZ.d.ts → index-x2vcsduE.d.cts} +14 -11
  88. package/dist/index.cjs +429 -7383
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +15 -15
  91. package/dist/index.d.ts +15 -15
  92. package/dist/index.js +121 -27
  93. package/dist/model/index.cjs +8 -2562
  94. package/dist/model/index.cjs.map +1 -1
  95. package/dist/model/index.d.cts +471 -81
  96. package/dist/model/index.d.ts +471 -81
  97. package/dist/model/index.js +6 -7
  98. package/dist/styled-Bl0ZK28h.d.ts +543 -0
  99. package/dist/styled-Crv6SA4S.d.cts +543 -0
  100. package/dist/theory/index.cjs +172 -1822
  101. package/dist/theory/index.cjs.map +1 -1
  102. package/dist/theory/index.d.cts +151 -233
  103. package/dist/theory/index.d.ts +151 -233
  104. package/dist/theory/index.js +30 -7
  105. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  106. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  107. package/package.json +34 -13
  108. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  109. package/dist/chunk-4MHA432G.js.map +0 -1
  110. package/dist/chunk-62QZKCJQ.js.map +0 -1
  111. package/dist/chunk-AKLYPWIN.js.map +0 -1
  112. package/dist/chunk-C5XYKUVG.js.map +0 -1
  113. package/dist/chunk-CHMXELDS.js.map +0 -1
  114. package/dist/chunk-DYWFNC4A.js +0 -152
  115. package/dist/chunk-DYWFNC4A.js.map +0 -1
  116. package/dist/chunk-EH6EM37U.js.map +0 -1
  117. package/dist/chunk-FBQK3ZQN.js +0 -131
  118. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  119. package/dist/chunk-GOAW4VYL.js.map +0 -1
  120. package/dist/chunk-I5R7ZBJS.js +0 -202
  121. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  122. package/dist/chunk-IT2O4XFE.js.map +0 -1
  123. package/dist/chunk-MIWO425M.js.map +0 -1
  124. package/dist/chunk-SGU7SPLK.js +0 -924
  125. package/dist/chunk-SGU7SPLK.js.map +0 -1
  126. package/dist/index-CGMZNCx6.d.cts +0 -140
  127. package/dist/index-CGMZNCx6.d.ts +0 -140
  128. package/dist/index-CnZCo29u.d.cts +0 -108
  129. package/dist/index-JnF4v9bx.d.ts +0 -108
  130. package/dist/styled-DZ7GjShb.d.ts +0 -282
  131. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  132. package/src/analyze/arrange/index.ts +0 -21
  133. package/src/analyze/arrange/internal.ts +0 -127
  134. package/src/analyze/arrange/tension.ts +0 -199
  135. package/src/analyze/arrange/tracks.ts +0 -312
  136. package/src/analyze/detect/index.ts +0 -272
  137. package/src/analyze/functional/borrowed.ts +0 -84
  138. package/src/analyze/functional/cadence.ts +0 -66
  139. package/src/analyze/functional/function.ts +0 -184
  140. package/src/analyze/functional/index.ts +0 -23
  141. package/src/analyze/functional/internal.ts +0 -29
  142. package/src/analyze/functional/roman.ts +0 -402
  143. package/src/analyze/index.ts +0 -49
  144. package/src/analyze/timeline/index.ts +0 -451
  145. package/src/analyze/voice/index.ts +0 -339
  146. package/src/core/event-index/index.ts +0 -104
  147. package/src/core/index.ts +0 -61
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -302
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -76
  152. package/src/core/tuning/index.ts +0 -186
  153. package/src/core/types.ts +0 -29
  154. package/src/core/validation/index.ts +0 -119
  155. package/src/generate/bass/index.ts +0 -373
  156. package/src/generate/countermelody/index.ts +0 -470
  157. package/src/generate/drums/beat.ts +0 -266
  158. package/src/generate/drums/euclid.ts +0 -65
  159. package/src/generate/drums/fills.ts +0 -356
  160. package/src/generate/drums/ghost.ts +0 -119
  161. package/src/generate/drums/hihat.ts +0 -285
  162. package/src/generate/drums/hit.ts +0 -31
  163. package/src/generate/drums/index.ts +0 -361
  164. package/src/generate/drums/internal.ts +0 -257
  165. package/src/generate/drums/kick.ts +0 -160
  166. package/src/generate/drums/percussion.ts +0 -121
  167. package/src/generate/drums/rng.ts +0 -7
  168. package/src/generate/drums/swing.ts +0 -71
  169. package/src/generate/groove/index.ts +0 -351
  170. package/src/generate/harmonize/index.ts +0 -451
  171. package/src/generate/index.ts +0 -58
  172. package/src/generate/motif/index.ts +0 -422
  173. package/src/generate/progression/index.ts +0 -362
  174. package/src/generate/reharmony/index.ts +0 -323
  175. package/src/generate/rhythm/index.ts +0 -175
  176. package/src/index.ts +0 -13
  177. package/src/model/chord.ts +0 -442
  178. package/src/model/index.ts +0 -15
  179. package/src/model/interval.ts +0 -82
  180. package/src/model/key.ts +0 -266
  181. package/src/model/note.ts +0 -176
  182. package/src/model/progression.ts +0 -161
  183. package/src/model/shared.ts +0 -12
  184. package/src/theory/chord/index.ts +0 -375
  185. package/src/theory/chordscale/index.ts +0 -374
  186. package/src/theory/counterpoint/index.ts +0 -230
  187. package/src/theory/harmony/index.ts +0 -77
  188. package/src/theory/index.ts +0 -112
  189. package/src/theory/safety/index.ts +0 -488
  190. package/src/theory/scale/degrees.ts +0 -123
  191. package/src/theory/scale/index.ts +0 -28
  192. package/src/theory/scale/key.ts +0 -56
  193. package/src/theory/scale/masks.ts +0 -153
  194. package/src/theory/spelling/index.ts +0 -249
  195. package/src/theory/symbol/index.ts +0 -268
  196. package/src/theory/voicing/index.ts +0 -5
  197. package/src/theory/voicing/internal.ts +0 -168
  198. package/src/theory/voicing/leading.ts +0 -112
  199. package/src/theory/voicing/satb.ts +0 -216
  200. package/src/theory/voicing/styled.ts +0 -199
  201. package/tsconfig.json +0 -23
@@ -0,0 +1,889 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+ var _chunk2A2QMA74cjs = require('./chunk-2A2QMA74.cjs');
8
+
9
+
10
+
11
+
12
+
13
+ var _chunkKQN2ZYNDcjs = require('./chunk-KQN2ZYND.cjs');
14
+
15
+
16
+ var _chunkWFDXOLUBcjs = require('./chunk-WFDXOLUB.cjs');
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+ var _chunkBSLTLGM3cjs = require('./chunk-BSLTLGM3.cjs');
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+ var _chunkPI3UXRWKcjs = require('./chunk-PI3UXRWK.cjs');
37
+
38
+ // src/analyze/timeline/index.ts
39
+ function chordTimelineFromChords(chords, totalBeats) {
40
+ _chunkPI3UXRWKcjs.assertRange.call(void 0, totalBeats, 0, Number.MAX_SAFE_INTEGER, "timeline totalBeats");
41
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, chords.length, "timeline chords");
42
+ for (let index = 0; index < chords.length; index += 1) {
43
+ _chunkPI3UXRWKcjs.assertRange.call(void 0,
44
+ _nullishCoalesce(_optionalChain([chords, 'access', _ => _[index], 'optionalAccess', _2 => _2.startBeat]), () => ( Number.NaN)),
45
+ 0,
46
+ Number.MAX_SAFE_INTEGER,
47
+ `chords[${index}].startBeat`
48
+ );
49
+ }
50
+ const sorted = [...chords].filter((chord) => chord.startBeat < totalBeats).sort((a, b) => a.startBeat - b.startBeat);
51
+ const segments = sorted.map((gc, i) => {
52
+ const next = sorted[i + 1];
53
+ const nextBeat = next ? next.startBeat : totalBeats;
54
+ const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));
55
+ return {
56
+ startBeat: gc.startBeat,
57
+ endBeat,
58
+ chord: _chunkBSLTLGM3cjs.makeChord.call(void 0, gc.rootPc, gc.quality, gc.bassPc)
59
+ };
60
+ }).filter((seg) => seg.endBeat > seg.startBeat);
61
+ return { at: segmentLookup(segments), segments };
62
+ }
63
+ function segmentLookup(segments) {
64
+ return (beat) => {
65
+ _chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, beat, "timeline query beat");
66
+ let low = 0;
67
+ let high = segments.length;
68
+ while (low < high) {
69
+ const middle = Math.floor((low + high) / 2);
70
+ if ((_nullishCoalesce(_optionalChain([segments, 'access', _3 => _3[middle], 'optionalAccess', _4 => _4.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat) {
71
+ low = middle + 1;
72
+ } else {
73
+ high = middle;
74
+ }
75
+ }
76
+ const candidate = segments[low - 1];
77
+ return candidate !== void 0 && beat >= candidate.startBeat && beat < candidate.endBeat ? candidate.chord : null;
78
+ };
79
+ }
80
+ var EPS = 1e-9;
81
+ var NOISE_THRESHOLD_RATIO = 0.2;
82
+ var MAX_DETECTION_PCS = 6;
83
+ var INEXACT_CONFIDENCE_FACTOR = 0.85;
84
+ var DIATONIC_BONUS = 0.5;
85
+ var EXACT_BONUS = 0.5;
86
+ var MISMATCH_PENALTY = 0.3;
87
+ function scoreMatch(match, weights, maxWeight, key) {
88
+ const rootWeight = maxWeight > 0 ? (_nullishCoalesce(weights[match.rootPc], () => ( 0))) / maxWeight : 0;
89
+ const tones = _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, _chunkBSLTLGM3cjs.makeChord.call(void 0, match.rootPc, match.quality));
90
+ let score = rootWeight;
91
+ if (tones.every((pc) => _chunkBSLTLGM3cjs.isScaleTone.call(void 0, pc, key))) {
92
+ score += DIATONIC_BONUS;
93
+ }
94
+ if (match.exact) {
95
+ score += EXACT_BONUS;
96
+ }
97
+ score -= MISMATCH_PENALTY * (match.extraPcs.length + match.missingPcs.length);
98
+ return score;
99
+ }
100
+ function chordConfidence(chord, weights, totalWeight, exact) {
101
+ const tones = new Set(_chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, chord));
102
+ let chordWeight = 0;
103
+ for (let pc = 0; pc < 12; pc += 1) {
104
+ if (tones.has(pc)) {
105
+ chordWeight += _nullishCoalesce(weights[pc], () => ( 0));
106
+ }
107
+ }
108
+ const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;
109
+ const confidence = Math.min(1, Math.max(0, raw));
110
+ return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;
111
+ }
112
+ function analyzeWindow(notes, windowStart, windowEnd, ts, key) {
113
+ const weights = new Array(12).fill(0);
114
+ let lowestPitch = Number.POSITIVE_INFINITY;
115
+ for (const note of notes) {
116
+ const noteEnd = note.startBeat + note.durationBeat;
117
+ const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);
118
+ if (overlap <= EPS) {
119
+ continue;
120
+ }
121
+ const velocityFactor = note.velocity !== void 0 ? note.velocity / 127 : 1;
122
+ const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;
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);
125
+ weights[pc] = (_nullishCoalesce(weights[pc], () => ( 0))) + overlap * velocityFactor * accent;
126
+ if (note.pitch < lowestPitch) {
127
+ lowestPitch = note.pitch;
128
+ }
129
+ }
130
+ let totalWeight = 0;
131
+ let maxWeight = 0;
132
+ for (const w of weights) {
133
+ totalWeight += w;
134
+ maxWeight = Math.max(maxWeight, w);
135
+ }
136
+ if (totalWeight <= EPS) {
137
+ return null;
138
+ }
139
+ const ranked = weights.map((weight, pc) => ({ pc, weight })).filter(({ weight }) => weight > 0).sort((a, b) => b.weight - a.weight || a.pc - b.pc);
140
+ const selected = ranked.filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO).slice(0, MAX_DETECTION_PCS).map(({ pc }) => pc);
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" });
145
+ let bestMatch = null;
146
+ let bestScore = Number.NEGATIVE_INFINITY;
147
+ for (const match of matches) {
148
+ const score = scoreMatch(match, weights, maxWeight, key);
149
+ if (score > bestScore) {
150
+ bestScore = score;
151
+ bestMatch = match;
152
+ }
153
+ }
154
+ if (bestMatch) {
155
+ const chord = _chunkBSLTLGM3cjs.makeChord.call(void 0, bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);
156
+ return { chord, confidence: chordConfidence(chord, weights, totalWeight, bestMatch.exact) };
157
+ }
158
+ return null;
159
+ }
160
+ function sameChord(a, b) {
161
+ return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;
162
+ }
163
+ function chordTimelineFromNotes(notes, opts = {}) {
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");
168
+ const budget = opts.budget;
169
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, notes, "timeline notes", { allowNonPositiveDuration: true, budget });
170
+ const soundingIndex = _chunk2A2QMA74cjs.createNoteEventIndex.call(void 0,
171
+ notes.filter((note) => note.durationBeat > 0),
172
+ { name: "timeline notes", budget }
173
+ );
174
+ const sounding = soundingIndex.notes.map(({ note }) => note);
175
+ const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);
176
+ const totalBeats = _nullishCoalesce(opts.totalBeats, () => ( lastNoteEnd));
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)));
179
+ const segments = [];
180
+ const segmentConfidence = [];
181
+ const windowCount = Math.max(0, Math.ceil(totalBeats / harmonicRhythm - EPS));
182
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, windowCount, "timeline windows", budget);
183
+ const windowNotes = Array.from({ length: windowCount }, () => []);
184
+ let memberships = 0;
185
+ for (const indexed of soundingIndex.notes) {
186
+ const first = Math.max(0, Math.floor(indexed.note.startBeat / harmonicRhythm));
187
+ const lastExclusive = Math.min(windowCount, Math.ceil(indexed.endBeat / harmonicRhythm));
188
+ memberships += Math.max(0, lastExclusive - first);
189
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, memberships, "timeline note-to-window memberships", budget);
190
+ for (let window = first; window < lastExclusive; window += 1) {
191
+ _optionalChain([windowNotes, 'access', _7 => _7[window], 'optionalAccess', _8 => _8.push, 'call', _9 => _9(indexed.note)]);
192
+ }
193
+ }
194
+ for (let i = 0; i < windowCount; i += 1) {
195
+ const start = i * harmonicRhythm;
196
+ const end = Math.min(start + harmonicRhythm, totalBeats);
197
+ const inferred = analyzeWindow(_nullishCoalesce(windowNotes[i], () => ( [])), start, end, ts, key);
198
+ if (!inferred) {
199
+ continue;
200
+ }
201
+ const last = segments[segments.length - 1];
202
+ if (last && sameChord(last.chord, inferred.chord) && Math.abs(last.endBeat - start) < EPS) {
203
+ const lastLength = last.endBeat - last.startBeat;
204
+ const length = end - start;
205
+ const lastConfidence = _nullishCoalesce(segmentConfidence[segmentConfidence.length - 1], () => ( 0));
206
+ segmentConfidence[segmentConfidence.length - 1] = (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);
207
+ last.endBeat = end;
208
+ } else {
209
+ segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });
210
+ segmentConfidence.push(inferred.confidence);
211
+ }
212
+ }
213
+ return {
214
+ timeline: { at: segmentLookup(segments), segments },
215
+ key,
216
+ segmentConfidence
217
+ };
218
+ }
219
+ function detectCadences(timeline, key) {
220
+ const hits = [];
221
+ for (let i = 1; i < timeline.segments.length; i += 1) {
222
+ const prev = timeline.segments[i - 1];
223
+ const cur = timeline.segments[i];
224
+ if (!prev || !cur) {
225
+ continue;
226
+ }
227
+ if (Math.abs(cur.startBeat - prev.endBeat) > EPS) {
228
+ continue;
229
+ }
230
+ const type = _chunkKQN2ZYNDcjs.detectCadence.call(void 0, prev.chord, cur.chord, key);
231
+ if (type !== null) {
232
+ hits.push({ atBeat: cur.startBeat, type, from: prev.chord, to: cur.chord });
233
+ }
234
+ }
235
+ return hits;
236
+ }
237
+
238
+ // src/analyze/arrange/internal.ts
239
+ var EPS2 = 1e-9;
240
+ var MAX_LANE_LEAP = 24;
241
+ function covers(note, beat) {
242
+ return beat >= note.startBeat - EPS2 && beat < note.endBeat - EPS2;
243
+ }
244
+ function isPercussion(role) {
245
+ return role === "drums";
246
+ }
247
+ function poolNotes(tracks, only) {
248
+ const notes = [];
249
+ for (let index = 0; index < tracks.length; index += 1) {
250
+ const track = tracks[index];
251
+ if (track === void 0 || isPercussion(track.role)) {
252
+ continue;
253
+ }
254
+ if (only !== void 0 && !only.has(index)) {
255
+ continue;
256
+ }
257
+ for (const note of track.notes) {
258
+ if (note.durationBeat > 0) {
259
+ notes.push(note);
260
+ }
261
+ }
262
+ }
263
+ return notes;
264
+ }
265
+ function splitIntoSubVoices(ordered) {
266
+ const lanes = [];
267
+ const attachNearest = (note, excluded = /* @__PURE__ */ new Set()) => {
268
+ let best = -1;
269
+ let bestDistance = Number.POSITIVE_INFINITY;
270
+ let bestEnd = Number.NEGATIVE_INFINITY;
271
+ for (let lane = 0; lane < lanes.length; lane += 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]]);
276
+ if (last === void 0) {
277
+ continue;
278
+ }
279
+ const end = last.startBeat + last.durationBeat;
280
+ if (end > note.startBeat + EPS2) {
281
+ continue;
282
+ }
283
+ const distance = Math.abs(last.pitch - note.pitch);
284
+ if (distance > MAX_LANE_LEAP) {
285
+ continue;
286
+ }
287
+ if (distance < bestDistance || distance === bestDistance && end > bestEnd) {
288
+ best = lane;
289
+ bestDistance = distance;
290
+ bestEnd = end;
291
+ }
292
+ }
293
+ if (best >= 0) {
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
+ }
321
+ } else {
322
+ const note = block[0];
323
+ if (note !== void 0 && !attachNearest(note)) {
324
+ lanes.push([note]);
325
+ }
326
+ }
327
+ start = end;
328
+ }
329
+ return lanes;
330
+ }
331
+ function lastStartingAtOrBefore(sounding, beat) {
332
+ let low = 0;
333
+ let high = sounding.length;
334
+ while (low < high) {
335
+ const middle = Math.floor((low + high) / 2);
336
+ if ((_nullishCoalesce(_optionalChain([sounding, 'access', _24 => _24[middle], 'optionalAccess', _25 => _25.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + EPS2) {
337
+ low = middle + 1;
338
+ } else {
339
+ high = middle;
340
+ }
341
+ }
342
+ return low - 1;
343
+ }
344
+ function prepareTracks(tracks) {
345
+ let nextId = 0;
346
+ return tracks.map((track, index) => {
347
+ const ordered = track.notes.map((note, originalIndex) => ({ note, originalIndex })).filter(({ note }) => note.durationBeat > 0).sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch).map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));
348
+ const voices = splitIntoSubVoices(ordered).map((voice) => {
349
+ const sounding = voice.map((note, i) => {
350
+ const prev = i > 0 ? voice[i - 1] : void 0;
351
+ const prevEnd = prev === void 0 ? void 0 : prev.startBeat + prev.durationBeat;
352
+ const contiguous = prevEnd !== void 0 && Math.abs(prevEnd - note.startBeat) <= EPS2;
353
+ return {
354
+ pitch: note.pitch,
355
+ prevPitch: contiguous ? _optionalChain([prev, 'optionalAccess', _26 => _26.pitch]) : void 0,
356
+ startBeat: note.startBeat,
357
+ endBeat: note.startBeat + note.durationBeat
358
+ };
359
+ });
360
+ return {
361
+ voice,
362
+ sounding,
363
+ at: (beat) => {
364
+ const candidate = sounding[lastStartingAtOrBefore(sounding, beat)];
365
+ return candidate !== void 0 && covers(candidate, beat) ? candidate : void 0;
366
+ }
367
+ };
368
+ });
369
+ return {
370
+ name: _nullishCoalesce(track.name, () => ( `track ${index + 1}`)),
371
+ role: _nullishCoalesce(track.role, () => ( "other")),
372
+ trackIndex: index,
373
+ voices
374
+ };
375
+ });
376
+ }
377
+
378
+ // src/analyze/arrange/tension.ts
379
+ var FUNCTION_TENSION = {
380
+ tonic: 0,
381
+ subdominant: 0.5,
382
+ dominant: 1
383
+ };
384
+ var FUNCTION_WEIGHT = 0.5;
385
+ var DISSONANCE_WEIGHT = 0.35;
386
+ var SPAN_WEIGHT = 0.15;
387
+ var SPAN_SATURATION = 24;
388
+ function clamp01(value) {
389
+ return Math.min(1, Math.max(0, value));
390
+ }
391
+ function tensionCurve(tracks, opts = {}) {
392
+ const ts = _nullishCoalesce(opts.ts, () => ( _chunk2A2QMA74cjs.parseTimeSignature.call(void 0, "4/4")));
393
+ _chunkPI3UXRWKcjs.assertTimeSignature.call(void 0, ts);
394
+ const budget = opts.budget;
395
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
396
+ for (let index = 0; index < tracks.length; index += 1) {
397
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, _nullishCoalesce(_optionalChain([tracks, 'access', _27 => _27[index], 'optionalAccess', _28 => _28.notes]), () => ( [])), `tracks[${index}].notes`, {
398
+ allowNonPositiveDuration: true,
399
+ budget
400
+ });
401
+ }
402
+ const profile = _nullishCoalesce(opts.profile, () => ( "pop"));
403
+ const step = _nullishCoalesce(opts.step, () => ( 1));
404
+ _chunkPI3UXRWKcjs.assertRange.call(void 0, step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, "tension sampling step");
405
+ const harmonyTracks = opts.harmonyTracks === void 0 ? void 0 : new Set(opts.harmonyTracks.map(Math.trunc));
406
+ const pooled = poolNotes(tracks, harmonyTracks);
407
+ const totalBeats = poolNotes(tracks).reduce(
408
+ (end, n) => Math.max(end, n.startBeat + n.durationBeat),
409
+ 0
410
+ );
411
+ const { timeline, key } = opts.timeline === void 0 ? chordTimelineFromNotes(pooled, {
412
+ key: opts.key,
413
+ ts,
414
+ harmonicRhythm: opts.harmonicRhythm,
415
+ budget
416
+ }) : {
417
+ timeline: opts.timeline,
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)))
419
+ };
420
+ const prepared = prepareTracks(tracks);
421
+ const points = [];
422
+ const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS2));
423
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, sampleCount, "tension samples", budget);
424
+ const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);
425
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0,
426
+ sampleCount * Math.min(128, voiceCount) * voiceCount,
427
+ "tension sample-voice comparisons",
428
+ budget
429
+ );
430
+ for (let i = 0; i < sampleCount; i += 1) {
431
+ const beat = i * step;
432
+ points.push({ beat, tension: sampleTension(prepared, timeline, key, ts, profile, beat) });
433
+ }
434
+ return points;
435
+ }
436
+ function tensionCurveFrom(tracks, analysis, opts = {}) {
437
+ return tensionCurve(tracks, { ...opts, timeline: analysis.timeline, key: analysis.key });
438
+ }
439
+ function allSounding(prepared, beat) {
440
+ const out = [];
441
+ for (let t = 0; t < prepared.length; t += 1) {
442
+ const track = prepared[t];
443
+ if (!track || isPercussion(track.role)) {
444
+ continue;
445
+ }
446
+ for (const subVoice of track.voices) {
447
+ const note = subVoice.at(beat);
448
+ if (note !== void 0) {
449
+ out.push({ pitch: note.pitch, track: t });
450
+ }
451
+ }
452
+ }
453
+ return out;
454
+ }
455
+ function sampleTension(prepared, timeline, key, ts, profile, beat) {
456
+ const sounding = allSounding(prepared, beat);
457
+ if (sounding.length === 0) {
458
+ return 0;
459
+ }
460
+ const chord = timeline.at(beat);
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);
464
+ let nonChord = 0;
465
+ let dissonant = 0;
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;
473
+ }
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,
480
+ {
481
+ profile,
482
+ candidatePitch: pitch,
483
+ chord,
484
+ key,
485
+ otherVoices: others,
486
+ strongBeat
487
+ },
488
+ { suggestions: false }
489
+ );
490
+ if (result.safety === 2 /* Dissonant */) {
491
+ dissonant += count;
492
+ }
493
+ }
494
+ const dissonanceScore = Math.max(nonChord, dissonant) / sounding.length;
495
+ let low = Number.POSITIVE_INFINITY;
496
+ let high = Number.NEGATIVE_INFINITY;
497
+ for (const voice of sounding) {
498
+ low = Math.min(low, voice.pitch);
499
+ high = Math.max(high, voice.pitch);
500
+ }
501
+ const spanScore = Math.min(1, (high - low) / SPAN_SATURATION);
502
+ return clamp01(
503
+ FUNCTION_WEIGHT * functionScore + DISSONANCE_WEIGHT * dissonanceScore + SPAN_WEIGHT * spanScore
504
+ );
505
+ }
506
+
507
+ // src/analyze/voice/index.ts
508
+ function toVoiceNotes(events) {
509
+ return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));
510
+ }
511
+ var HUMANIZE_ADJACENCY = 0.05;
512
+ function intervalAboveBass(pitch, others, chord) {
513
+ let bass = Number.POSITIVE_INFINITY;
514
+ for (const ov of others) {
515
+ bass = Math.min(bass, ov.pitch);
516
+ }
517
+ if (Number.isFinite(bass) && bass < pitch) {
518
+ return ((pitch - bass) % 12 + 12) % 12;
519
+ }
520
+ const ref = _nullishCoalesce(chord.bassPc, () => ( chord.rootPc));
521
+ return ((_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pitch) - _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, ref)) % 12 + 12) % 12;
522
+ }
523
+ function tensionDegree(ic) {
524
+ if (ic === 1 || ic === 2) {
525
+ return 9;
526
+ }
527
+ if (ic === 5 || ic === 6) {
528
+ return 11;
529
+ }
530
+ return 13;
531
+ }
532
+ function isStep(a, b) {
533
+ const d = Math.abs(a - b);
534
+ return d === 1 || d === 2;
535
+ }
536
+ function suspensionType(ic, delta) {
537
+ if (delta > 0) {
538
+ return "sus2-3";
539
+ }
540
+ if (ic === 2) {
541
+ return "sus9-8";
542
+ }
543
+ if (ic === 10 || ic === 11) {
544
+ return "sus7-6";
545
+ }
546
+ if (ic === 5 || ic === 6) {
547
+ return "sus4-3";
548
+ }
549
+ if (ic === 8 || ic === 9) {
550
+ return "sus6-5";
551
+ }
552
+ return null;
553
+ }
554
+ function stepResolution(pitch, chord) {
555
+ for (let delta = 1; delta <= 2; delta += 1) {
556
+ if (_chunkBSLTLGM3cjs.isChordMember.call(void 0, pitch - delta, chord)) {
557
+ return pitch - delta;
558
+ }
559
+ }
560
+ return void 0;
561
+ }
562
+ function analyzeVoice(voice, chordAtBeat, key, otherVoicesAtBeat = () => []) {
563
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, voice, "voice notes", { allowNonPositiveDuration: true });
564
+ const result = [];
565
+ for (let i = 0; i < voice.length; i += 1) {
566
+ const note = voice[i];
567
+ if (!note) {
568
+ continue;
569
+ }
570
+ const prevNote = i > 0 ? voice[i - 1] : void 0;
571
+ const nextNote = i + 1 < voice.length ? voice[i + 1] : void 0;
572
+ const prevEnd = prevNote === void 0 ? Number.NEGATIVE_INFINITY : prevNote.startBeat + prevNote.durationBeat;
573
+ const noteEnd = note.startBeat + note.durationBeat;
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;
576
+ const chord = chordAtBeat(note.startBeat);
577
+ const labels = [];
578
+ const member = _chunkBSLTLGM3cjs.isChordMember.call(void 0, note.pitch, chord);
579
+ let handled = false;
580
+ if (chord && member) {
581
+ const role = _chunkBSLTLGM3cjs.chordToneRole.call(void 0, note.pitch, chord);
582
+ if (role) {
583
+ labels.push({ kind: "chordTone", role });
584
+ } else {
585
+ const ic = _chunkBSLTLGM3cjs.intervalAboveRoot.call(void 0, note.pitch, chord);
586
+ labels.push({ kind: "tension", degree: tensionDegree(ic) });
587
+ }
588
+ handled = true;
589
+ }
590
+ if (!handled && chord && prev && next) {
591
+ const prevChord = chordAtBeat(prev.startBeat);
592
+ const prepared = prev.pitch === note.pitch && _chunkBSLTLGM3cjs.isChordMember.call(void 0, prev.pitch, prevChord);
593
+ const resolves = isStep(next.pitch, note.pitch);
594
+ const others = otherVoicesAtBeat(note.startBeat);
595
+ const verticallyDissonant = others.some(
596
+ (ov) => _chunkBSLTLGM3cjs.createsVerticalDissonance.call(void 0, note.pitch, ov.pitch, true)
597
+ );
598
+ if (prepared && verticallyDissonant && resolves) {
599
+ const ic = intervalAboveBass(note.pitch, others, chord);
600
+ const type = suspensionType(ic, next.pitch - note.pitch);
601
+ if (type !== null) {
602
+ labels.push({ kind: "suspension", type, resolveTo: next.pitch });
603
+ handled = true;
604
+ }
605
+ }
606
+ }
607
+ if (!handled && chord && prev && next) {
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));
610
+ const up = note.pitch - prev.pitch > 0;
611
+ const contInto = next.pitch - note.pitch > 0;
612
+ const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;
613
+ if (prevMember && nextMember && !member && isStep(note.pitch, prev.pitch) && isStep(next.pitch, note.pitch) && sameDir) {
614
+ labels.push({ kind: "passing" });
615
+ handled = true;
616
+ }
617
+ }
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)) {
619
+ labels.push({ kind: "neighbor" });
620
+ handled = true;
621
+ }
622
+ if (!handled && chord && next && !member && note.pitch === next.pitch) {
623
+ const nextChord = chordAtBeat(next.startBeat);
624
+ if (_chunkBSLTLGM3cjs.isChordMember.call(void 0, next.pitch, nextChord)) {
625
+ labels.push({ kind: "anticipation" });
626
+ handled = true;
627
+ }
628
+ }
629
+ if (!handled && chord && prev && next && !member) {
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));
632
+ const stepFromPrev = isStep(note.pitch, prev.pitch);
633
+ const leapToNext = Math.abs(next.pitch - note.pitch) >= 3;
634
+ const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;
635
+ if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {
636
+ labels.push({ kind: "escape" });
637
+ handled = true;
638
+ }
639
+ }
640
+ if (!handled && chord && !member) {
641
+ const ic = _chunkBSLTLGM3cjs.intervalAboveRoot.call(void 0, note.pitch, chord);
642
+ const isTension = ic === 2 || ic === 5 || ic === 9;
643
+ const avoid = ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5) || ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10);
644
+ if (avoid) {
645
+ labels.push({ kind: "avoid" });
646
+ const resolveTo = stepResolution(note.pitch, chord);
647
+ if (resolveTo !== void 0) {
648
+ labels.push({ kind: "needsResolution", resolveTo });
649
+ }
650
+ } else if (isTension) {
651
+ labels.push({ kind: "tension", degree: tensionDegree(ic) });
652
+ } else {
653
+ const resolveTo = stepResolution(note.pitch, chord);
654
+ if (resolveTo !== void 0) {
655
+ labels.push({ kind: "needsResolution", resolveTo });
656
+ }
657
+ }
658
+ }
659
+ if (next && _chunkBSLTLGM3cjs.isLeadingToneResolution.call(void 0, note.pitch, next.pitch, key)) {
660
+ labels.push({ kind: "leadingTone", resolveTo: next.pitch });
661
+ }
662
+ const analyzed = {
663
+ noteId: _nullishCoalesce(note.id, () => ( i)),
664
+ pitch: note.pitch,
665
+ startBeat: note.startBeat,
666
+ durationBeat: note.durationBeat,
667
+ labels,
668
+ rationale: describe(labels)
669
+ };
670
+ if (note.originalIndex !== void 0) {
671
+ analyzed.originalIndex = note.originalIndex;
672
+ }
673
+ result.push(analyzed);
674
+ }
675
+ return result;
676
+ }
677
+ function describe(labels) {
678
+ const primary = labels[0];
679
+ if (!primary) {
680
+ return "Unclassified note";
681
+ }
682
+ switch (primary.kind) {
683
+ case "chordTone":
684
+ return `Chord ${primary.role}`;
685
+ case "tension":
686
+ return `${primary.degree}th tension`;
687
+ case "avoid":
688
+ return "Avoid note";
689
+ case "passing":
690
+ return "Passing tone between chord tones";
691
+ case "neighbor":
692
+ return "Neighbor tone";
693
+ case "suspension":
694
+ return `Suspension (${primary.type})`;
695
+ case "anticipation":
696
+ return "Anticipation of the next chord";
697
+ case "escape":
698
+ return "Escape tone";
699
+ case "needsResolution":
700
+ return "Unresolved dissonance";
701
+ case "leadingTone":
702
+ return "Leading tone resolving to the tonic";
703
+ }
704
+ }
705
+
706
+ // src/analyze/arrange/tracks.ts
707
+ function soundingVoicesAt(prepared, beat) {
708
+ const out = [];
709
+ for (let t = 0; t < prepared.length; t += 1) {
710
+ const track = prepared[t];
711
+ if (!track || isPercussion(track.role)) {
712
+ continue;
713
+ }
714
+ for (let v = 0; v < track.voices.length; v += 1) {
715
+ const note = _optionalChain([track, 'access', _31 => _31.voices, 'access', _32 => _32[v], 'optionalAccess', _33 => _33.at, 'call', _34 => _34(beat)]);
716
+ if (note === void 0) {
717
+ continue;
718
+ }
719
+ const snap = { pitch: note.pitch };
720
+ const previous = Math.abs(note.startBeat - beat) <= EPS2 ? note.prevPitch : note.pitch;
721
+ if (previous !== void 0) {
722
+ snap.prevPitch = previous;
723
+ }
724
+ out.push({ track: t, voice: v, snapshot: snap });
725
+ }
726
+ }
727
+ return out;
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
+ }
734
+ function firstSegmentAfter(timeline, beat) {
735
+ let low = 0;
736
+ let high = timeline.segments.length;
737
+ while (low < high) {
738
+ const middle = Math.floor((low + high) / 2);
739
+ if ((_nullishCoalesce(_optionalChain([timeline, 'access', _35 => _35.segments, 'access', _36 => _36[middle], 'optionalAccess', _37 => _37.startBeat]), () => ( Number.POSITIVE_INFINITY))) <= beat + EPS2) {
740
+ low = middle + 1;
741
+ } else {
742
+ high = middle;
743
+ }
744
+ }
745
+ return low;
746
+ }
747
+ function evaluationBeats(note, timeline) {
748
+ const noteEnd = note.startBeat + note.durationBeat;
749
+ const beats = [note.startBeat];
750
+ for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {
751
+ const start = _optionalChain([timeline, 'access', _38 => _38.segments, 'access', _39 => _39[i], 'optionalAccess', _40 => _40.startBeat]);
752
+ if (start === void 0 || start >= noteEnd - EPS2) {
753
+ break;
754
+ }
755
+ beats.push(start);
756
+ }
757
+ return beats;
758
+ }
759
+ function analyzeArrangement(tracks, opts = {}) {
760
+ const ts = _nullishCoalesce(opts.ts, () => ( _chunk2A2QMA74cjs.parseTimeSignature.call(void 0, "4/4")));
761
+ _chunkPI3UXRWKcjs.assertTimeSignature.call(void 0, ts);
762
+ const budget = opts.budget;
763
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, tracks.length, "arrangement tracks", budget);
764
+ let noteCount = 0;
765
+ for (let index = 0; index < tracks.length; index += 1) {
766
+ const notes = _optionalChain([tracks, 'access', _41 => _41[index], 'optionalAccess', _42 => _42.notes]);
767
+ if (!Array.isArray(notes)) {
768
+ throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
769
+ `tracks[${index}].notes must be an array; received ${typeof notes}`
770
+ );
771
+ }
772
+ _chunkPI3UXRWKcjs.assertNoteEvents.call(void 0, notes, `tracks[${index}].notes`, {
773
+ allowNonPositiveDuration: true,
774
+ budget
775
+ });
776
+ noteCount += notes.length;
777
+ }
778
+ _chunkPI3UXRWKcjs.assertGenerationBudget.call(void 0, noteCount, "arrangement notes", budget);
779
+ const profile = _nullishCoalesce(opts.profile, () => ( "pop"));
780
+ const minSeverity = _nullishCoalesce(opts.minSeverity, () => ( 1)) /* Warning */;
781
+ const harmonyTracks = opts.harmonyTracks === void 0 ? void 0 : new Set(opts.harmonyTracks.map(Math.trunc));
782
+ const pooled = poolNotes(tracks, harmonyTracks);
783
+ const inferred = opts.timeline === void 0 ? chordTimelineFromNotes(pooled, {
784
+ key: opts.key,
785
+ ts,
786
+ harmonicRhythm: opts.harmonicRhythm,
787
+ budget
788
+ }) : {
789
+ timeline: opts.timeline,
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)
795
+ };
796
+ const { timeline, key, segmentConfidence } = inferred;
797
+ const cadences = detectCadences(timeline, key);
798
+ const prepared = prepareTracks(tracks);
799
+ const soundingCache = /* @__PURE__ */ new Map();
800
+ const trackAnalyses = [];
801
+ const conflicts = [];
802
+ for (let t = 0; t < prepared.length; t += 1) {
803
+ const track = prepared[t];
804
+ if (!track) {
805
+ continue;
806
+ }
807
+ if (isPercussion(track.role)) {
808
+ trackAnalyses.push({ name: track.name, role: track.role, notes: [] });
809
+ continue;
810
+ }
811
+ const notes = [];
812
+ for (let v = 0; v < track.voices.length; v += 1) {
813
+ const subVoice = track.voices[v];
814
+ if (!subVoice) {
815
+ continue;
816
+ }
817
+ const analyzed = analyzeVoice(
818
+ subVoice.voice,
819
+ timeline.at,
820
+ key,
821
+ (beat) => otherVoicesSounding(prepared, t, v, beat, soundingCache)
822
+ );
823
+ notes.push(...analyzed.map((note) => ({ ...note, trackIndex: track.trackIndex })));
824
+ const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));
825
+ for (let noteIndex = 0; noteIndex < subVoice.voice.length; noteIndex += 1) {
826
+ const note = subVoice.voice[noteIndex];
827
+ const preparedNote = subVoice.sounding[noteIndex];
828
+ if (note === void 0 || preparedNote === void 0) {
829
+ continue;
830
+ }
831
+ for (const beat of evaluationBeats(note, timeline)) {
832
+ const atOnset = Math.abs(beat - note.startBeat) <= EPS2;
833
+ const safetyQuery = {
834
+ profile,
835
+ candidatePitch: note.pitch,
836
+ prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,
837
+ chord: timeline.at(beat),
838
+ key,
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 });
843
+ if (result.safety !== 0 /* Safe */ && result.safety >= minSeverity) {
844
+ result = _chunkWFDXOLUBcjs.evaluateSafety.call(void 0, safetyQuery);
845
+ const conflict = {
846
+ beat,
847
+ trackName: track.name,
848
+ trackIndex: track.trackIndex,
849
+ noteId: note.id,
850
+ pitch: note.pitch,
851
+ labels: _nullishCoalesce(labelsById.get(note.id), () => ( [])),
852
+ safety: result.safety,
853
+ reasons: result.reasons
854
+ };
855
+ if (note.originalIndex !== void 0) {
856
+ conflict.originalIndex = note.originalIndex;
857
+ }
858
+ if (result.rationale !== void 0) {
859
+ conflict.rationale = result.rationale;
860
+ }
861
+ if (result.resolveTo !== void 0) {
862
+ conflict.resolveTo = result.resolveTo;
863
+ }
864
+ if (result.suggestions !== void 0) {
865
+ conflict.suggestions = [...result.suggestions];
866
+ }
867
+ conflicts.push(conflict);
868
+ }
869
+ }
870
+ }
871
+ }
872
+ notes.sort((a, b) => a.noteId - b.noteId);
873
+ trackAnalyses.push({ name: track.name, role: track.role, notes });
874
+ }
875
+ conflicts.sort((a, b) => b.safety - a.safety || a.beat - b.beat);
876
+ return { key, timeline, segmentConfidence, cadences, tracks: trackAnalyses, conflicts };
877
+ }
878
+
879
+
880
+
881
+
882
+
883
+
884
+
885
+
886
+
887
+
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;
889
+ //# sourceMappingURL=chunk-4VZ5MZAM.cjs.map