@libraz/libcantus 0.9.3 → 0.9.4

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 (198) hide show
  1. package/README.md +2 -2
  2. package/dist/analyze/index.cjs +39 -1947
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +197 -37
  5. package/dist/analyze/index.d.ts +197 -37
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
  36. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  37. package/dist/chunk-NP4NA2FB.cjs +106 -0
  38. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  39. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  40. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  41. package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
  42. package/dist/chunk-NYYWMV2C.js.map +1 -0
  43. package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
  44. package/dist/chunk-Q2XUP227.js.map +1 -0
  45. package/dist/chunk-Q6OP4U43.cjs +327 -0
  46. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  47. package/dist/chunk-RC6GBMCD.cjs +845 -0
  48. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  49. package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
  50. package/dist/chunk-RK35RIN7.js.map +1 -0
  51. package/dist/chunk-RUMU3UA6.cjs +53 -0
  52. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  53. package/dist/chunk-T64C7ZD7.cjs +1 -0
  54. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  55. package/dist/chunk-XEBG3G53.js +498 -0
  56. package/dist/chunk-XEBG3G53.js.map +1 -0
  57. package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
  58. package/dist/chunk-YLEMGEFH.js.map +1 -0
  59. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  60. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  61. package/dist/core/index.cjs +130 -647
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +301 -39
  64. package/dist/core/index.d.ts +301 -39
  65. package/dist/core/index.js +54 -16
  66. package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
  67. package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
  68. package/dist/generate/index.cjs +28 -4085
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +370 -62
  71. package/dist/generate/index.d.ts +370 -62
  72. package/dist/generate/index.js +23 -9
  73. package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
  74. package/dist/index-70p168VM.d.ts +192 -0
  75. package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
  76. package/dist/index-BtXT6UQ0.d.cts +192 -0
  77. package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  78. package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
  79. package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
  80. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  81. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  82. package/dist/index-OG65ocOr.d.cts +272 -0
  83. package/dist/index-OG65ocOr.d.ts +272 -0
  84. package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
  85. package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
  86. package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
  87. package/dist/index.cjs +419 -7383
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +14 -14
  90. package/dist/index.d.ts +14 -14
  91. package/dist/index.js +111 -27
  92. package/dist/model/index.cjs +8 -2562
  93. package/dist/model/index.cjs.map +1 -1
  94. package/dist/model/index.d.cts +433 -75
  95. package/dist/model/index.d.ts +433 -75
  96. package/dist/model/index.js +6 -7
  97. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  98. package/dist/styled-DJbkJQfj.d.cts +506 -0
  99. package/dist/theory/index.cjs +172 -1822
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +151 -233
  102. package/dist/theory/index.d.ts +151 -233
  103. package/dist/theory/index.js +30 -7
  104. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  105. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  106. package/package.json +24 -6
  107. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  108. package/dist/chunk-4MHA432G.js.map +0 -1
  109. package/dist/chunk-62QZKCJQ.js.map +0 -1
  110. package/dist/chunk-AKLYPWIN.js.map +0 -1
  111. package/dist/chunk-C5XYKUVG.js.map +0 -1
  112. package/dist/chunk-CHMXELDS.js.map +0 -1
  113. package/dist/chunk-DYWFNC4A.js +0 -152
  114. package/dist/chunk-DYWFNC4A.js.map +0 -1
  115. package/dist/chunk-EH6EM37U.js.map +0 -1
  116. package/dist/chunk-FBQK3ZQN.js +0 -131
  117. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  118. package/dist/chunk-GOAW4VYL.js.map +0 -1
  119. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  120. package/dist/chunk-IT2O4XFE.js.map +0 -1
  121. package/dist/chunk-MIWO425M.js.map +0 -1
  122. package/dist/chunk-SGU7SPLK.js.map +0 -1
  123. package/dist/index-CGMZNCx6.d.cts +0 -140
  124. package/dist/index-CGMZNCx6.d.ts +0 -140
  125. package/dist/index-CnZCo29u.d.cts +0 -108
  126. package/dist/index-JnF4v9bx.d.ts +0 -108
  127. package/dist/styled-DZ7GjShb.d.ts +0 -282
  128. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  129. package/src/analyze/arrange/index.ts +0 -21
  130. package/src/analyze/arrange/internal.ts +0 -127
  131. package/src/analyze/arrange/tension.ts +0 -199
  132. package/src/analyze/arrange/tracks.ts +0 -312
  133. package/src/analyze/detect/index.ts +0 -272
  134. package/src/analyze/functional/borrowed.ts +0 -84
  135. package/src/analyze/functional/cadence.ts +0 -66
  136. package/src/analyze/functional/function.ts +0 -184
  137. package/src/analyze/functional/index.ts +0 -23
  138. package/src/analyze/functional/internal.ts +0 -29
  139. package/src/analyze/functional/roman.ts +0 -402
  140. package/src/analyze/index.ts +0 -49
  141. package/src/analyze/timeline/index.ts +0 -451
  142. package/src/analyze/voice/index.ts +0 -339
  143. package/src/core/event-index/index.ts +0 -104
  144. package/src/core/index.ts +0 -61
  145. package/src/core/interval/index.ts +0 -81
  146. package/src/core/meter/index.ts +0 -302
  147. package/src/core/pitch/index.ts +0 -311
  148. package/src/core/random/index.ts +0 -76
  149. package/src/core/tuning/index.ts +0 -186
  150. package/src/core/types.ts +0 -29
  151. package/src/core/validation/index.ts +0 -119
  152. package/src/generate/bass/index.ts +0 -373
  153. package/src/generate/countermelody/index.ts +0 -470
  154. package/src/generate/drums/beat.ts +0 -266
  155. package/src/generate/drums/euclid.ts +0 -65
  156. package/src/generate/drums/fills.ts +0 -356
  157. package/src/generate/drums/ghost.ts +0 -119
  158. package/src/generate/drums/hihat.ts +0 -285
  159. package/src/generate/drums/hit.ts +0 -31
  160. package/src/generate/drums/index.ts +0 -361
  161. package/src/generate/drums/internal.ts +0 -257
  162. package/src/generate/drums/kick.ts +0 -160
  163. package/src/generate/drums/percussion.ts +0 -121
  164. package/src/generate/drums/rng.ts +0 -7
  165. package/src/generate/drums/swing.ts +0 -71
  166. package/src/generate/groove/index.ts +0 -351
  167. package/src/generate/harmonize/index.ts +0 -451
  168. package/src/generate/index.ts +0 -58
  169. package/src/generate/motif/index.ts +0 -422
  170. package/src/generate/progression/index.ts +0 -362
  171. package/src/generate/reharmony/index.ts +0 -323
  172. package/src/generate/rhythm/index.ts +0 -175
  173. package/src/index.ts +0 -13
  174. package/src/model/chord.ts +0 -442
  175. package/src/model/index.ts +0 -15
  176. package/src/model/interval.ts +0 -82
  177. package/src/model/key.ts +0 -266
  178. package/src/model/note.ts +0 -176
  179. package/src/model/progression.ts +0 -161
  180. package/src/model/shared.ts +0 -12
  181. package/src/theory/chord/index.ts +0 -375
  182. package/src/theory/chordscale/index.ts +0 -374
  183. package/src/theory/counterpoint/index.ts +0 -230
  184. package/src/theory/harmony/index.ts +0 -77
  185. package/src/theory/index.ts +0 -112
  186. package/src/theory/safety/index.ts +0 -488
  187. package/src/theory/scale/degrees.ts +0 -123
  188. package/src/theory/scale/index.ts +0 -28
  189. package/src/theory/scale/key.ts +0 -56
  190. package/src/theory/scale/masks.ts +0 -153
  191. package/src/theory/spelling/index.ts +0 -249
  192. package/src/theory/symbol/index.ts +0 -268
  193. package/src/theory/voicing/index.ts +0 -5
  194. package/src/theory/voicing/internal.ts +0 -168
  195. package/src/theory/voicing/leading.ts +0 -112
  196. package/src/theory/voicing/satb.ts +0 -216
  197. package/src/theory/voicing/styled.ts +0 -199
  198. package/tsconfig.json +0 -23
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-RC6GBMCD.cjs","../src/analyze/timeline/index.ts","../src/analyze/arrange/internal.ts","../src/analyze/arrange/tension.ts","../src/analyze/voice/index.ts","../src/analyze/arrange/tracks.ts"],"names":["EPS"],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-RC6GBMCD.cjs","sourcesContent":[null,"import { createNoteEventIndex } from '../../core/event-index/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBar, metricWeight, parseTimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordSpan } from '../../theory/chord/index.js';\nimport { chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport { isScaleTone, majorKey } from '../../theory/scale/index.js';\nimport type { ChordMatch } from '../detect/index.js';\nimport { detectChord, detectChordBest, detectKeyFromNotes } from '../detect/index.js';\nimport type { Cadence } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\n\nexport type { ChordSegment } from '../../theory/chord/index.js';\n\nimport type { ChordSegment } from '../../theory/chord/index.js';\n\n/**\n * A beat-indexed sequence of chord segments.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimeline = {\n /** The chord sounding at a beat, or null when no segment covers it. */\n at: (beat: number) => Chord | null;\n segments: ChordSegment[];\n};\n\n/**\n * Build a chord timeline from placed chords.\n *\n * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last\n * chord runs to `totalBeats`. Segments with no positive length (from duplicate\n * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns\n * the covering segment's chord, or null when the beat lies outside every segment.\n *\n * @param chords Placed chords in time order.\n * @param totalBeats End of the timeline in beats.\n * @returns A queryable chord timeline.\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromChords(\n chords: readonly ChordSpan[],\n totalBeats: number,\n): ChordTimeline {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n assertGenerationBudget(chords.length, 'timeline chords');\n for (let index = 0; index < chords.length; index += 1) {\n assertRange(\n chords[index]?.startBeat ?? Number.NaN,\n 0,\n Number.MAX_SAFE_INTEGER,\n `chords[${index}].startBeat`,\n );\n }\n const sorted = [...chords]\n .filter((chord) => chord.startBeat < totalBeats)\n .sort((a, b) => a.startBeat - b.startBeat);\n const segments: ChordSegment[] = sorted\n .map((gc, i) => {\n const next = sorted[i + 1];\n const nextBeat = next ? next.startBeat : totalBeats;\n const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));\n return {\n startBeat: gc.startBeat,\n endBeat,\n chord: makeChord(gc.rootPc, gc.quality, gc.bassPc),\n };\n })\n .filter((seg) => seg.endBeat > seg.startBeat);\n\n return { at: segmentLookup(segments), segments };\n}\n\n/**\n * Build the `at(beat)` lookup over a segment list.\n *\n * Segments are disjoint and in beat order, so the covering one is found by\n * binary search. A linear scan here is the inner loop of arrangement analysis,\n * which queries it once per note per chord change.\n */\nfunction segmentLookup(segments: ChordSegment[]): (beat: number) => Chord | null {\n return (beat) => {\n assertFiniteNumber(beat, 'timeline query beat');\n let low = 0;\n let high = segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n const candidate = segments[low - 1];\n return candidate !== undefined && beat >= candidate.startBeat && beat < candidate.endBeat\n ? candidate.chord\n : null;\n };\n}\n\nconst EPS = 1e-9;\n\n/** Fraction of the strongest pitch-class weight below which a pc is noise. */\nconst NOISE_THRESHOLD_RATIO = 0.2;\n\n/** Maximum number of pitch classes fed to chord detection per window. */\nconst MAX_DETECTION_PCS = 6;\n\n/** Number of top-weighted pitch classes tried in the detection fallback. */\nconst FALLBACK_PCS = 3;\n\n/** Confidence multiplier applied when the chosen chord match is inexact. */\nconst INEXACT_CONFIDENCE_FACTOR = 0.85;\n\n/** Score bonus for a match whose tones are all in the key. */\nconst DIATONIC_BONUS = 0.5;\n\n/** Score bonus for an exact match (no extra and no missing tones). */\nconst EXACT_BONUS = 0.5;\n\n/** Score penalty per extra or missing tone in a match. */\nconst MISMATCH_PENALTY = 0.3;\n\n/**\n * Options controlling {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineOptions = {\n /** Key context; inferred from the notes with {@link detectKeyFromNotes} when omitted. */\n key?: KeyScale;\n /**\n * Time signature used for metric accents; defaults to 4/4.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * Window length in beats per chord slot; defaults to one bar of `ts`.\n *\n * @defaultValue one bar of `ts`\n */\n harmonicRhythm?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * The result of {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineResult = {\n /** The inferred timeline, with adjacent identical chords merged. */\n timeline: ChordTimeline;\n /** The key used for the analysis (given or inferred). */\n key: KeyScale;\n /** One confidence value in [0, 1] per segment, in segment order. */\n segmentConfidence: number[];\n};\n\n/** A window's inferred chord and its confidence, or null for an empty window. */\ntype WindowChord = {\n chord: Chord;\n confidence: number;\n};\n\n/**\n * Score a chord match against a window's pitch-class weights: a heavily\n * weighted root, in-key tones, and exactness all raise the score; extra or\n * missing tones lower it.\n */\nfunction scoreMatch(\n match: ChordMatch,\n weights: number[],\n maxWeight: number,\n key: KeyScale,\n): number {\n const rootWeight = maxWeight > 0 ? (weights[match.rootPc] ?? 0) / maxWeight : 0;\n const tones = chordPitchClasses(makeChord(match.rootPc, match.quality));\n let score = rootWeight;\n if (tones.every((pc) => isScaleTone(pc, key))) {\n score += DIATONIC_BONUS;\n }\n if (match.exact) {\n score += EXACT_BONUS;\n }\n score -= MISMATCH_PENALTY * (match.extraPcs.length + match.missingPcs.length);\n return score;\n}\n\n/** Confidence of a chord for a window: chord-tone weight over total weight. */\nfunction chordConfidence(\n chord: Chord,\n weights: number[],\n totalWeight: number,\n exact: boolean,\n): number {\n const tones = new Set(chordPitchClasses(chord));\n let chordWeight = 0;\n for (let pc = 0; pc < 12; pc += 1) {\n if (tones.has(pc)) {\n chordWeight += weights[pc] ?? 0;\n }\n }\n const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;\n}\n\n/**\n * Infer the chord sounding in one window from the notes overlapping it.\n *\n * Builds a pitch-class weight histogram (overlap duration x velocity x\n * metric-accent bonus for onsets inside the window), keeps the significantly\n * weighted pitch classes, and picks the best {@link detectChord} match by\n * root weight, key membership, and exactness.\n */\nfunction analyzeWindow(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n ts: TimeSignature,\n key: KeyScale,\n): WindowChord | null {\n const weights = new Array<number>(12).fill(0);\n let lowestPitch = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);\n if (overlap <= EPS) {\n continue;\n }\n const velocityFactor = note.velocity !== undefined ? note.velocity / 127 : 1;\n const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;\n const accent = onsetInWindow ? 1 + metricWeight(note.startBeat, ts) / 3 : 1;\n const pc = pitchClass(note.pitch);\n weights[pc] = (weights[pc] ?? 0) + overlap * velocityFactor * accent;\n if (note.pitch < lowestPitch) {\n lowestPitch = note.pitch;\n }\n }\n let totalWeight = 0;\n let maxWeight = 0;\n for (const w of weights) {\n totalWeight += w;\n maxWeight = Math.max(maxWeight, w);\n }\n if (totalWeight <= EPS) {\n return null;\n }\n\n // Keep pitch classes with meaningful weight, strongest first.\n const ranked = weights\n .map((weight, pc) => ({ pc, weight }))\n .filter(({ weight }) => weight > 0)\n .sort((a, b) => b.weight - a.weight || a.pc - b.pc);\n const selected = ranked\n .filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO)\n .slice(0, MAX_DETECTION_PCS)\n .map(({ pc }) => pc);\n\n // Feed detection with the window's true bass as the lowest pitch so inversion\n // detection works; when the bass pc was filtered out as noise, anchor on the\n // heaviest pc instead so no spurious inversion is reported.\n const bassPc = pitchClass(lowestPitch);\n const anchorPc = selected.includes(bassPc) ? bassPc : (selected[0] ?? bassPc);\n const detectionPitches = selected.map((pc) => (pc === anchorPc ? pc : pc + 12));\n\n const matches = detectChord(detectionPitches);\n let bestMatch: ChordMatch | null = null;\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const match of matches) {\n const score = scoreMatch(match, weights, maxWeight, key);\n if (score > bestScore) {\n bestScore = score;\n bestMatch = match;\n }\n }\n if (bestMatch) {\n const chord = makeChord(bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);\n return { chord, confidence: chordConfidence(chord, weights, totalWeight, bestMatch.exact) };\n }\n\n // Fallback: retry on only the top-weighted pitch classes.\n const fallback = detectChordBest(selected.slice(0, FALLBACK_PCS));\n if (fallback) {\n return { chord: fallback, confidence: chordConfidence(fallback, weights, totalWeight, false) };\n }\n return null;\n}\n\n/** Whether two chords are the same root, quality, and bass. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;\n}\n\n/**\n * Infer a chord timeline from raw multi-track notes.\n *\n * The span `[0, totalBeats)` is sliced into `harmonicRhythm`-beat windows; each\n * window's chord is inferred from a pitch-class weight histogram of the notes\n * overlapping it (weight = overlap duration x velocity x metric-accent bonus\n * for onsets in the window). Adjacent windows carrying the identical chord are\n * merged into one segment; windows with no notes produce no segment, so\n * `at(beat)` returns null there. Each segment carries a confidence in [0, 1]:\n * the fraction of the window weight explained by chord tones, reduced when the\n * match is inexact, and duration-weighted across merged windows.\n *\n * Notes with a zero or negative duration never sound, so they are dropped at\n * ingest: they contribute to neither the key inference, the span, nor any\n * window's histogram.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link ChordTimelineOptions}.\n * @returns The inferred timeline, the key used, and per-segment confidences.\n * @throws If `harmonicRhythm` is not positive.\n * @example\n * ```ts\n * import { chordTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C\n * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E\n * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G\n * ];\n * const { timeline, key } = chordTimelineFromNotes(notes);\n * timeline.at(0); // the chord inferred over beat 0, or null\n * ```\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromNotes(\n notes: NoteEvent[],\n opts: ChordTimelineOptions = {},\n): ChordTimelineResult {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBar(ts);\n assertRange(harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'harmonic rhythm');\n const budget = opts.budget;\n assertNoteEvents(notes, 'timeline notes', { allowNonPositiveDuration: true, budget });\n // Zero/negative-length notes never sound; drop them before any inference.\n const soundingIndex = createNoteEventIndex(\n notes.filter((note) => note.durationBeat > 0),\n { name: 'timeline notes', budget },\n );\n const sounding = soundingIndex.notes.map(({ note }) => note);\n const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastNoteEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n // Weighted by duration and velocity, exactly as the per-window chord\n // histogram below is: a busy ornamental figure must not outvote the sustained\n // harmony that establishes the key.\n const key = opts.key ?? detectKeyFromNotes(sounding)[0]?.key ?? majorKey(0);\n\n const segments: ChordSegment[] = [];\n const segmentConfidence: number[] = [];\n const windowCount = Math.max(0, Math.ceil(totalBeats / harmonicRhythm - EPS));\n assertGenerationBudget(windowCount, 'timeline windows', budget);\n const windowNotes: NoteEvent[][] = Array.from({ length: windowCount }, () => []);\n let memberships = 0;\n for (const indexed of soundingIndex.notes) {\n const first = Math.max(0, Math.floor(indexed.note.startBeat / harmonicRhythm));\n const lastExclusive = Math.min(windowCount, Math.ceil(indexed.endBeat / harmonicRhythm));\n memberships += Math.max(0, lastExclusive - first);\n assertGenerationBudget(memberships, 'timeline note-to-window memberships', budget);\n for (let window = first; window < lastExclusive; window += 1) {\n windowNotes[window]?.push(indexed.note);\n }\n }\n for (let i = 0; i < windowCount; i += 1) {\n const start = i * harmonicRhythm;\n const end = Math.min(start + harmonicRhythm, totalBeats);\n const inferred = analyzeWindow(windowNotes[i] ?? [], start, end, ts, key);\n if (!inferred) {\n continue;\n }\n const last = segments[segments.length - 1];\n if (last && sameChord(last.chord, inferred.chord) && Math.abs(last.endBeat - start) < EPS) {\n // Merge into the previous segment, blending confidence by duration.\n const lastLength = last.endBeat - last.startBeat;\n const length = end - start;\n const lastConfidence = segmentConfidence[segmentConfidence.length - 1] ?? 0;\n segmentConfidence[segmentConfidence.length - 1] =\n (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);\n last.endBeat = end;\n } else {\n segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });\n segmentConfidence.push(inferred.confidence);\n }\n }\n\n return {\n timeline: { at: segmentLookup(segments), segments },\n key,\n segmentConfidence,\n };\n}\n\n/**\n * A cadence found between two consecutive timeline segments.\n *\n * @category Arrangement & Analysis\n */\nexport type CadenceHit = {\n /** The beat where the cadence arrives (the second chord's onset). */\n atBeat: number;\n type: Exclude<Cadence, null>;\n from: Chord;\n to: Chord;\n};\n\n/**\n * Find cadences between consecutive segments of a chord timeline.\n *\n * Each temporally adjacent segment pair is classified with\n * {@link detectCadence}; pairs forming no cadence are skipped. Segments\n * separated by a gap (a rest in the timeline) are not a chord-to-chord\n * progression, so they are never paired.\n *\n * @param timeline The chord timeline to scan.\n * @param key The prevailing key.\n * @returns The cadences found, in time order.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant\n * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic\n * ];\n * const { timeline, key } = chordTimelineFromNotes(notes);\n * const cadences = detectCadences(timeline, key);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectCadences(timeline: ChordTimeline, key: KeyScale): CadenceHit[] {\n const hits: CadenceHit[] = [];\n for (let i = 1; i < timeline.segments.length; i += 1) {\n const prev = timeline.segments[i - 1];\n const cur = timeline.segments[i];\n if (!prev || !cur) {\n continue;\n }\n if (Math.abs(cur.startBeat - prev.endBeat) > EPS) {\n continue; // A rest separates the chords; no cadential motion across it.\n }\n const type = detectCadence(prev.chord, cur.chord, key);\n if (type !== null) {\n hits.push({ atBeat: cur.startBeat, type, from: prev.chord, to: cur.chord });\n }\n }\n return hits;\n}\n","/**\n * Shared helpers for the arrangement-analysis modules: pooling every track's\n * sounding notes and partitioning a track into monophonic sub-voices, plus the\n * small geometric primitives both the track-role and tension passes rely on.\n */\n\nimport type { NoteEvent } from '../../core/types.js';\nimport type { IdentifiedVoiceNote } from '../voice/index.js';\nimport type { ArrangementTrack, TrackRole } from './tracks.js';\n\n/** Float tolerance for beat boundary comparisons. */\nexport const EPS = 1e-9;\n\n/**\n * Largest interval, in semitones, a sub-voice will span between consecutive\n * notes. Beyond two octaves the connection is not a melodic step or leap but an\n * artefact of lane packing, so a new sub-voice is opened instead.\n */\nconst MAX_LANE_LEAP = 24;\n\n/** A note with the pitch that immediately preceded it in the same sub-voice. */\nexport type PreparedNote = {\n pitch: number;\n prevPitch?: number;\n startBeat: number;\n endBeat: number;\n};\n\n/** One monophonic sub-voice of a track: ordered notes plus sounding spans. */\nexport type PreparedVoice = {\n /** The sub-voice's notes in time order, each with a stable id. */\n voice: IdentifiedVoiceNote[];\n /** The same notes as sounding spans, each carrying its predecessor's pitch. */\n sounding: PreparedNote[];\n /**\n * The span sounding at a beat, or undefined. A sub-voice is monophonic by\n * construction, so at most one span covers any beat and the lookup is a\n * single binary search rather than a scan.\n */\n at: (beat: number) => PreparedNote | undefined;\n};\n\n/** A track prepared for analysis: resolved metadata plus monophonic sub-voices. */\nexport type PreparedTrack = {\n name: string;\n role: TrackRole;\n /** The track's index in the caller's input array. */\n trackIndex: number;\n /**\n * The track's notes partitioned into monophonic sub-voices. Voice analysis\n * assumes one note at a time, so a polyphonic track (e.g. a 'harmony' track\n * playing block chords) is split here; a monophonic track yields exactly one\n * sub-voice.\n */\n voices: PreparedVoice[];\n};\n\n/** Whether a sounding span `[start, end)` covers a beat. */\nexport function covers(note: PreparedNote, beat: number): boolean {\n return beat >= note.startBeat - EPS && beat < note.endBeat - EPS;\n}\n\n/** Whether a track's role means it carries no harmonic content. */\nexport function isPercussion(role: TrackRole | undefined): boolean {\n return role === 'drums';\n}\n\n/**\n * Every sounding note of every pitched track pooled into one flat list. Zero-\n * and negative-length notes never sound, so they are dropped here (matching the\n * chord-inference ingest in the timeline module), and percussion tracks are\n * skipped entirely: their pitches are instrument selections, not harmony.\n */\nexport function poolNotes(tracks: ArrangementTrack[], only?: ReadonlySet<number>): NoteEvent[] {\n const notes: NoteEvent[] = [];\n for (let index = 0; index < tracks.length; index += 1) {\n const track = tracks[index];\n if (track === undefined || isPercussion(track.role)) {\n continue;\n }\n if (only !== undefined && !only.has(index)) {\n continue;\n }\n for (const note of track.notes) {\n if (note.durationBeat > 0) {\n notes.push(note);\n }\n }\n }\n return notes;\n}\n\n/**\n * Split a track's ordered notes into monophonic sub-voices.\n *\n * Each note joins the free sub-voice — one whose last note has ended — whose\n * last pitch is nearest its own, so a track whose voices cross keeps each voice\n * on its own lane instead of handing a low note to whichever lane happened to\n * be created first. Ties go to the lane that ended latest, which is the one the\n * note actually follows. A connection wider than {@link MAX_LANE_LEAP} is not a\n * melodic one, so it opens a new sub-voice instead.\n *\n * The predecessor pitch this produces feeds leap and parallel detection, so a\n * mis-assigned lane invents voice-leading faults that are not in the music.\n */\nfunction splitIntoSubVoices(ordered: IdentifiedVoiceNote[]): IdentifiedVoiceNote[][] {\n const lanes: IdentifiedVoiceNote[][] = [];\n for (const note of ordered) {\n let best = -1;\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestEnd = Number.NEGATIVE_INFINITY;\n for (let lane = 0; lane < lanes.length; lane += 1) {\n const last = lanes[lane]?.[(lanes[lane]?.length ?? 0) - 1];\n if (last === undefined) {\n continue;\n }\n const end = last.startBeat + last.durationBeat;\n if (end > note.startBeat + EPS) {\n continue; // still sounding, so the lane is not free\n }\n const distance = Math.abs(last.pitch - note.pitch);\n if (distance > MAX_LANE_LEAP) {\n continue;\n }\n if (distance < bestDistance || (distance === bestDistance && end > bestEnd)) {\n best = lane;\n bestDistance = distance;\n bestEnd = end;\n }\n }\n if (best >= 0) {\n lanes[best]?.push(note);\n } else {\n lanes.push([note]);\n }\n }\n return lanes;\n}\n\n/** Index of the last span starting at or before a beat, or -1. */\nfunction lastStartingAtOrBefore(sounding: PreparedNote[], beat: number): number {\n let low = 0;\n let high = sounding.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((sounding[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low - 1;\n}\n\n/**\n * Resolve each track's metadata and derive its monophonic sub-voices.\n *\n * Notes are sorted by onset, then pitch, so ordering is deterministic, and ids\n * are assigned in that order; each note keeps the index it had in the caller's\n * array so annotations can be mapped back. Zero- and negative-length notes never\n * sound and are dropped at ingest. Each track is then partitioned into\n * monophonic sub-voices (see {@link splitIntoSubVoices}); the predecessor pitch\n * used for suspension and parallel detection is the previous note of the same\n * sub-voice, never a simultaneous chord member.\n */\nexport function prepareTracks(tracks: ArrangementTrack[]): PreparedTrack[] {\n let nextId = 0;\n return tracks.map((track, index) => {\n const ordered: IdentifiedVoiceNote[] = track.notes\n .map((note, originalIndex) => ({ note, originalIndex }))\n .filter(({ note }) => note.durationBeat > 0)\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch)\n .map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));\n const voices: PreparedVoice[] = splitIntoSubVoices(ordered).map((voice) => {\n const sounding: PreparedNote[] = voice.map((note, i) => {\n const prev = i > 0 ? voice[i - 1] : undefined;\n const prevEnd = prev === undefined ? undefined : prev.startBeat + prev.durationBeat;\n const contiguous = prevEnd !== undefined && Math.abs(prevEnd - note.startBeat) <= EPS;\n return {\n pitch: note.pitch,\n prevPitch: contiguous ? prev?.pitch : undefined,\n startBeat: note.startBeat,\n endBeat: note.startBeat + note.durationBeat,\n };\n });\n return {\n voice,\n sounding,\n at: (beat: number) => {\n const candidate = sounding[lastStartingAtOrBefore(sounding, beat)];\n return candidate !== undefined && covers(candidate, beat) ? candidate : undefined;\n },\n };\n });\n return {\n name: track.name ?? `track ${index + 1}`,\n role: track.role ?? 'other',\n trackIndex: index,\n voices,\n };\n });\n}\n","/**\n * Arrangement tension analysis: a coarse harmonic-tension curve sampled at\n * regular beats across a whole multi-track piece, combining the sounding\n * chord's harmonic function, the dissonance of the sounding notes, and their\n * registral span into a single normalized reading.\n */\n\nimport { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport { evaluateSafety, NoteSafety, type SafetyProfile } from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { detectKeyFromNotes } from '../detect/index.js';\nimport { functionOf } from '../functional/index.js';\nimport { type ChordTimeline, chordTimelineFromNotes } from '../timeline/index.js';\nimport { EPS, isPercussion, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\nimport type { ArrangementAnalysis, ArrangementOptions, ArrangementTrack } from './tracks.js';\n\n/**\n * A tension reading sampled at a beat.\n *\n * @category Arrangement & Analysis\n */\nexport type TensionPoint = {\n beat: number;\n /** Combined tension in [0, 1]. */\n tension: number;\n};\n\n/** Tension contributed by the sounding chord's harmonic function. */\nconst FUNCTION_TENSION: Record<'tonic' | 'subdominant' | 'dominant', number> = {\n tonic: 0,\n subdominant: 0.5,\n dominant: 1,\n};\n\n/** Weight of the harmonic-function term in the combined tension score. */\nconst FUNCTION_WEIGHT = 0.5;\n/** Weight of the non-chord-tone / dissonance term. */\nconst DISSONANCE_WEIGHT = 0.35;\n/** Weight of the registral-span term. */\nconst SPAN_WEIGHT = 0.15;\n/** Pitch span, in semitones, that saturates the span term. */\nconst SPAN_SATURATION = 24;\n\n/** Clamp a value into [0, 1]. */\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Sample the harmonic tension of an arrangement at regular beats.\n *\n * At each sample beat the tension combines three normalized terms:\n * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,\n * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;\n * - the dissonance of the sounding notes — the larger of the share of sounding\n * pitches that are not chord tones and the share that {@link evaluateSafety}\n * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;\n * - the registral span of the sounding notes, saturating at\n * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.\n *\n * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks\n * pooled together, so the result is deterministic and self-contained.\n *\n * @param tracks The tracks to sample.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @throws If `step` is not positive.\n * @example\n * ```ts\n * import { tensionCurve } from '@libraz/libcantus';\n * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const curve = tensionCurve([{ notes }], { step: 1 });\n * curve; // one { beat, tension } sample per beat, in beat order\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurve(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const budget = opts.budget;\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n for (let index = 0; index < tracks.length; index += 1) {\n assertNoteEvents(tracks[index]?.notes ?? [], `tracks[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget,\n });\n }\n const profile: SafetyProfile = opts.profile ?? 'pop';\n const step = opts.step ?? 1;\n assertRange(step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'tension sampling step');\n\n const harmonyTracks =\n opts.harmonyTracks === undefined ? undefined : new Set(opts.harmonyTracks.map(Math.trunc));\n const pooled = poolNotes(tracks, harmonyTracks);\n const totalBeats = poolNotes(tracks).reduce(\n (end, n) => Math.max(end, n.startBeat + n.durationBeat),\n 0,\n );\n const { timeline, key } =\n opts.timeline === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n budget,\n })\n : {\n timeline: opts.timeline,\n key: opts.key ?? detectKeyFromNotes(pooled)[0]?.key ?? majorKey(0),\n };\n\n const prepared = prepareTracks(tracks);\n const points: TensionPoint[] = [];\n const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS));\n assertGenerationBudget(sampleCount, 'tension samples', budget);\n // The per-sample cost is one lookup per sub-voice, so it is their product —\n // not either dimension alone — that has to stay inside the budget.\n const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);\n assertGenerationBudget(sampleCount * voiceCount, 'tension sample-voice lookups', budget);\n for (let i = 0; i < sampleCount; i += 1) {\n const beat = i * step;\n points.push({ beat, tension: sampleTension(prepared, timeline, key, ts, profile, beat) });\n }\n return points;\n}\n\n/**\n * Sample the tension of an arrangement already analysed by\n * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.\n *\n * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference\n * twice over the same notes. This takes the analysis it already produced, so\n * the curve and the annotations describe the same harmony by construction.\n *\n * @param tracks The same tracks that were analysed.\n * @param analysis The result of {@link analyzeArrangement} for those tracks.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @example\n * ```ts\n * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';\n * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];\n * const analysis = analyzeArrangement(tracks);\n * tensionCurveFrom(tracks, analysis, { step: 1 });\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurveFrom(\n tracks: ArrangementTrack[],\n analysis: ArrangementAnalysis,\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n return tensionCurve(tracks, { ...opts, timeline: analysis.timeline, key: analysis.key });\n}\n\n/**\n * All sounding pitched notes across every track at a beat, tagged with their\n * track. Percussion carries no harmony, so it is skipped; each sub-voice is\n * monophonic, so its contribution is one binary search rather than a scan.\n */\nfunction allSounding(prepared: PreparedTrack[], beat: number): { pitch: number; track: number }[] {\n const out: { pitch: number; track: number }[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (const subVoice of track.voices) {\n const note = subVoice.at(beat);\n if (note !== undefined) {\n out.push({ pitch: note.pitch, track: t });\n }\n }\n }\n return out;\n}\n\n/** Combine the harmonic, dissonance, and span terms at one sample beat. */\nfunction sampleTension(\n prepared: PreparedTrack[],\n timeline: ChordTimeline,\n key: KeyScale,\n ts: TimeSignature,\n profile: SafetyProfile,\n beat: number,\n): number {\n const sounding = allSounding(prepared, beat);\n if (sounding.length === 0) {\n return 0;\n }\n const chord = timeline.at(beat);\n const functionScore = chord ? FUNCTION_TENSION[functionOf(chord, key)] : 0;\n\n const chordPcs = chord ? new Set(chordPitchClasses(chord)) : null;\n const strongBeat = isStrongBeat(beat, ts);\n let nonChord = 0;\n let dissonant = 0;\n for (let i = 0; i < sounding.length; i += 1) {\n const voice = sounding[i];\n if (!voice) {\n continue;\n }\n // Non-chord-tone share only applies when a chord is sounding; at a timeline\n // gap there is no reference harmony, so vertical dissonance alone drives the\n // dissonance term (the chord function term is already 0 there).\n if (chordPcs && !chordPcs.has(pitchClass(voice.pitch))) {\n nonChord += 1;\n }\n const others = sounding.filter((_, j) => j !== i).map((s) => ({ pitch: s.pitch }));\n // Only the verdict is read, so the replacement-pitch search is skipped.\n const result = evaluateSafety(\n {\n profile,\n candidatePitch: voice.pitch,\n chord,\n key,\n otherVoices: others,\n strongBeat,\n },\n { suggestions: false },\n );\n if (result.safety === NoteSafety.Dissonant) {\n dissonant += 1;\n }\n }\n const dissonanceScore = Math.max(nonChord, dissonant) / sounding.length;\n\n let low = Number.POSITIVE_INFINITY;\n let high = Number.NEGATIVE_INFINITY;\n for (const voice of sounding) {\n low = Math.min(low, voice.pitch);\n high = Math.max(high, voice.pitch);\n }\n const spanScore = Math.min(1, (high - low) / SPAN_SATURATION);\n\n return clamp01(\n FUNCTION_WEIGHT * functionScore + DISSONANCE_WEIGHT * dissonanceScore + SPAN_WEIGHT * spanScore,\n );\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport { assertNoteEvents } from '../../core/validation/index.js';\nimport type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole, intervalAboveRoot, isChordMember } from '../../theory/chord/index.js';\nimport {\n createsVerticalDissonance,\n isLeadingToneResolution,\n} from '../../theory/counterpoint/index.js';\nimport type { VoiceSnapshot } from '../../theory/safety/index.js';\n\n/**\n * A suspension figure, named by the interval above the bass and the interval it\n * resolves to.\n *\n * @category Arrangement & Analysis\n */\nexport type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';\n\n/**\n * A theory annotation attached to a note.\n *\n * @category Arrangement & Analysis\n */\nexport type TheoryLabel =\n | { kind: 'chordTone'; role: ChordToneRole }\n | { kind: 'tension'; degree: 9 | 11 | 13 }\n | { kind: 'avoid' }\n | { kind: 'passing' }\n | { kind: 'neighbor' }\n | { kind: 'suspension'; type: SuspensionFigure; resolveTo: number }\n | { kind: 'anticipation' }\n | { kind: 'escape' }\n | { kind: 'needsResolution'; resolveTo: number }\n | { kind: 'leadingTone'; resolveTo: number };\n\n/**\n * A note with its theory labels and a short rationale.\n *\n * The labels describe the note against the chord sounding at its **onset**;\n * they are not re-derived for the later chords a long note sustains across.\n * `analyzeArrangement` does re-check every crossed chord change, and reports\n * what it finds as conflicts, so a note labelled a chord tone here can still\n * appear in `conflicts` at a later beat.\n *\n * The identity fields make an annotation mappable back to the caller's input:\n * `originalIndex` is the note's position in the array that was passed in, and\n * `trackIndex` is set by {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type AnalyzedNote = {\n noteId: number;\n /** The note's index in the caller's own array, when the caller supplied one. */\n originalIndex?: number;\n /** The note's track, set by `analyzeArrangement`. */\n trackIndex?: number;\n pitch: number;\n startBeat: number;\n durationBeat: number;\n labels: TheoryLabel[];\n rationale?: string;\n};\n\n/**\n * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,\n * optionally, its index in the caller's original array.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceNote = NoteEvent & { id?: number; originalIndex?: number };\n\n/**\n * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and\n * {@link analyzeArrangement} works in.\n *\n * @category Arrangement & Analysis\n */\nexport type IdentifiedVoiceNote = VoiceNote & { id: number };\n\n/**\n * Give plain note events the ids {@link analyzeVoice} reports back.\n *\n * The id is only an identity handle, so the array position serves; this exists\n * so the mapping is a named call rather than a `map` every caller writes.\n *\n * @param events The note events, in time order.\n * @returns The same notes as voice notes, each carrying its index as its id.\n * @category Arrangement & Analysis\n */\nexport function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[] {\n return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));\n}\n\n/** Float tolerance for beat comparisons. */\nconst EPS = 1e-9;\n\n/**\n * Interval class of a pitch above the actual sounding bass.\n *\n * Suspension figures (4-3, 7-6, 9-8) are named for the interval above the bass,\n * not the chord root, so this prefers the lowest pitch among the other sounding\n * voices when one lies below the note. Without a lower sounding voice it falls\n * back to the chord's bass pitch class (or root in root position).\n */\nfunction intervalAboveBass(pitch: number, others: VoiceSnapshot[], chord: Chord): number {\n let bass = Number.POSITIVE_INFINITY;\n for (const ov of others) {\n bass = Math.min(bass, ov.pitch);\n }\n if (Number.isFinite(bass) && bass < pitch) {\n return (((pitch - bass) % 12) + 12) % 12;\n }\n const ref = chord.bassPc ?? chord.rootPc;\n return (((pitchClass(pitch) - pitchClass(ref)) % 12) + 12) % 12;\n}\n\n/**\n * Map an interval class above the root to its extended-tension scale degree:\n * a flat/natural ninth (1 or 2) to 9, an eleventh (5 or 6) to 11, and a\n * thirteenth (8 or 9) to 13.\n */\nfunction tensionDegree(ic: number): 9 | 11 | 13 {\n if (ic === 1 || ic === 2) {\n return 9;\n }\n if (ic === 5 || ic === 6) {\n return 11;\n }\n return 13;\n}\n\nfunction isStep(a: number, b: number): boolean {\n const d = Math.abs(a - b);\n return d === 1 || d === 2;\n}\n\n/**\n * Classify a suspension figure from the interval class above the sounding bass\n * (`ic`) and the resolution direction (`delta`, positive when resolving upward).\n */\nfunction suspensionType(ic: number, delta: number): SuspensionFigure | null {\n if (delta > 0) {\n return 'sus2-3';\n }\n if (ic === 2) {\n return 'sus9-8';\n }\n if (ic === 10 || ic === 11) {\n return 'sus7-6';\n }\n if (ic === 5 || ic === 6) {\n return 'sus4-3';\n }\n if (ic === 8 || ic === 9) {\n return 'sus6-5';\n }\n // Any other interval above the bass is a downward-resolving dissonance with\n // no standard figure; a catch-all `sus4-3` would name it after an interval it\n // does not form.\n return null;\n}\n\nfunction stepResolution(pitch: number, chord: Chord): number | undefined {\n for (let delta = 1; delta <= 2; delta += 1) {\n if (isChordMember(pitch - delta, chord)) {\n return pitch - delta;\n }\n }\n return undefined;\n}\n\n/**\n * Label every note of a voice with its theory roles.\n *\n * Each note is classified against the chord sounding at its beat. Chord tones\n * get a role label; non-chord tones are matched, in order, as suspensions\n * (prepared by an identical consonant pitch and resolving by step), passing\n * tones, neighbors, anticipations, and escape tones, then fall back to tension,\n * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted\n * additionally.\n *\n * The voice is expected to be monophonic (one note at a time). Notes sharing an\n * onset are treated as simultaneous cluster members, not melodic neighbors, so\n * they receive no melodic labels (suspension, passing, neighbor, anticipation,\n * escape) — only harmonic ones. Callers with truly polyphonic material should\n * split it into monophonic sub-voices first, as `analyzeArrangement` does.\n *\n * @param voice The monophonic voice, in time order. A note without an `id` is\n * identified by its position, so plain note events can be passed straight in.\n * @param chordAtBeat Chord sounding at a given beat, or null.\n * @param key Key context for leading-tone detection.\n * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to\n * none, which is the whole story for a solo line.\n * @returns One annotation per input note.\n * @example\n * ```ts\n * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';\n * const voice = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * const cMajor = makeChord(0, 'maj');\n * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));\n * labels; // one AnalyzedNote per input note, in the same order\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeVoice(\n voice: readonly VoiceNote[],\n chordAtBeat: (beat: number) => Chord | null,\n key: KeyScale,\n otherVoicesAtBeat: (beat: number) => VoiceSnapshot[] = () => [],\n): AnalyzedNote[] {\n assertNoteEvents(voice, 'voice notes', { allowNonPositiveDuration: true });\n const result: AnalyzedNote[] = [];\n\n for (let i = 0; i < voice.length; i += 1) {\n const note = voice[i];\n if (!note) {\n continue;\n }\n const prevNote = i > 0 ? voice[i - 1] : undefined;\n const nextNote = i + 1 < voice.length ? voice[i + 1] : undefined;\n // A neighboring note sharing this note's onset is a simultaneous cluster\n // member, not a melodic predecessor/successor; treating it as one would\n // fabricate passing/suspension figures, so it is dropped here.\n const prevEnd =\n prevNote === undefined\n ? Number.NEGATIVE_INFINITY\n : prevNote.startBeat + prevNote.durationBeat;\n const noteEnd = note.startBeat + note.durationBeat;\n const prev =\n prevNote !== undefined && Math.abs(prevEnd - note.startBeat) <= EPS ? prevNote : undefined;\n const next =\n nextNote !== undefined && Math.abs(noteEnd - nextNote.startBeat) <= EPS\n ? nextNote\n : undefined;\n const chord = chordAtBeat(note.startBeat);\n const labels: TheoryLabel[] = [];\n const member = isChordMember(note.pitch, chord);\n let handled = false;\n\n if (chord && member) {\n const role = chordToneRole(note.pitch, chord);\n if (role) {\n labels.push({ kind: 'chordTone', role });\n } else {\n const ic = intervalAboveRoot(note.pitch, chord);\n labels.push({ kind: 'tension', degree: tensionDegree(ic) });\n }\n handled = true;\n }\n\n if (!handled && chord && prev && next) {\n const prevChord = chordAtBeat(prev.startBeat);\n const prepared = prev.pitch === note.pitch && isChordMember(prev.pitch, prevChord);\n const resolves = isStep(next.pitch, note.pitch);\n const others = otherVoicesAtBeat(note.startBeat);\n const verticallyDissonant = others.some((ov) =>\n createsVerticalDissonance(note.pitch, ov.pitch, true),\n );\n if (prepared && verticallyDissonant && resolves) {\n const ic = intervalAboveBass(note.pitch, others, chord);\n const type = suspensionType(ic, next.pitch - note.pitch);\n if (type !== null) {\n labels.push({ kind: 'suspension', type, resolveTo: next.pitch });\n handled = true;\n }\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const up = note.pitch - prev.pitch > 0;\n const contInto = next.pitch - note.pitch > 0;\n const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;\n if (\n prevMember &&\n nextMember &&\n !member &&\n isStep(note.pitch, prev.pitch) &&\n isStep(next.pitch, note.pitch) &&\n sameDir\n ) {\n labels.push({ kind: 'passing' });\n handled = true;\n }\n }\n\n if (\n !handled &&\n chord &&\n prev &&\n next &&\n !member &&\n prev.pitch === next.pitch &&\n isChordMember(prev.pitch, chordAtBeat(prev.startBeat)) &&\n isStep(note.pitch, prev.pitch)\n ) {\n labels.push({ kind: 'neighbor' });\n handled = true;\n }\n\n if (!handled && chord && next && !member && note.pitch === next.pitch) {\n const nextChord = chordAtBeat(next.startBeat);\n if (isChordMember(next.pitch, nextChord)) {\n labels.push({ kind: 'anticipation' });\n handled = true;\n }\n }\n\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const stepFromPrev = isStep(note.pitch, prev.pitch);\n const leapToNext = Math.abs(next.pitch - note.pitch) >= 3;\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {\n labels.push({ kind: 'escape' });\n handled = true;\n }\n }\n\n if (!handled && chord && !member) {\n const ic = intervalAboveRoot(note.pitch, chord);\n const isTension = ic === 2 || ic === 5 || ic === 9;\n const avoid =\n (ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5)) ||\n (ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10));\n if (avoid) {\n labels.push({ kind: 'avoid' });\n const resolveTo = stepResolution(note.pitch, chord);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n } else if (isTension) {\n labels.push({ kind: 'tension', degree: tensionDegree(ic) });\n } else {\n const resolveTo = stepResolution(note.pitch, chord);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n }\n }\n\n if (next && isLeadingToneResolution(note.pitch, next.pitch, key)) {\n labels.push({ kind: 'leadingTone', resolveTo: next.pitch });\n }\n\n const analyzed: AnalyzedNote = {\n noteId: note.id ?? i,\n pitch: note.pitch,\n startBeat: note.startBeat,\n durationBeat: note.durationBeat,\n labels,\n rationale: describe(labels),\n };\n if (note.originalIndex !== undefined) {\n analyzed.originalIndex = note.originalIndex;\n }\n result.push(analyzed);\n }\n\n return result;\n}\n\n/** Build a short rationale from a note's primary label. */\nfunction describe(labels: TheoryLabel[]): string {\n const primary = labels[0];\n if (!primary) {\n return 'Unclassified note';\n }\n switch (primary.kind) {\n case 'chordTone':\n return `Chord ${primary.role}`;\n case 'tension':\n return `${primary.degree}th tension`;\n case 'avoid':\n return 'Avoid note';\n case 'passing':\n return 'Passing tone between chord tones';\n case 'neighbor':\n return 'Neighbor tone';\n case 'suspension':\n return `Suspension (${primary.type})`;\n case 'anticipation':\n return 'Anticipation of the next chord';\n case 'escape':\n return 'Escape tone';\n case 'needsResolution':\n return 'Unresolved dissonance';\n case 'leadingTone':\n return 'Leading tone resolving to the tonic';\n }\n}\n","/**\n * Arrangement track-role analysis: a single harmony is inferred from every\n * track's notes pooled together, each track is then analysed against that shared\n * harmony, and notes that clash with the sounding chord are collected as\n * conflicts.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport {\n evaluateSafety,\n NoteSafety,\n type SafetyProfile,\n type VoiceSnapshot,\n} from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { detectKeyFromNotes } from '../detect/index.js';\nimport {\n type CadenceHit,\n type ChordTimeline,\n chordTimelineFromNotes,\n detectCadences,\n} from '../timeline/index.js';\nimport {\n type AnalyzedNote,\n analyzeVoice,\n type TheoryLabel,\n type VoiceNote,\n} from '../voice/index.js';\nimport { EPS, isPercussion, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\n\n/**\n * The musical role a track plays in the arrangement.\n *\n * Only `drums` changes the analysis: its pitches select instruments rather than\n * naming harmony, so such a track is excluded from chord and key inference and\n * from every voice-leading comparison, and is reported back with no\n * annotations. `harmony` is meaningful through\n * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords\n * are inferred from. The rest are labels carried through to\n * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so\n * a track left unlabelled reports `'other'` rather than a guess.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';\n\n/**\n * One input track: its notes plus optional name and role.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementTrack = {\n name?: string;\n role?: TrackRole;\n notes: readonly NoteEvent[];\n};\n\n/**\n * A track after analysis: its resolved name, role, and per-note annotations.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackAnalysis = {\n name: string;\n role: TrackRole;\n /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */\n notes: AnalyzedNote[];\n};\n\n/**\n * A note that clashes with the harmony sounding beneath it.\n *\n * @category Arrangement & Analysis\n */\nexport type Conflict = {\n beat: number;\n trackName: string;\n /** The clashing track's index in the caller's input array. */\n trackIndex: number;\n /** The note's index in that track's own note array. */\n originalIndex?: number;\n /** The id the note carries in {@link TrackAnalysis.notes}. */\n noteId: number;\n pitch: number;\n /**\n * The note's theory labels at its onset, so a caller can tell a genuine clash\n * from a passing tone, a neighbour, or a prepared suspension — all of which\n * are ordinary non-chord tones that still evaluate as unsafe.\n */\n labels: TheoryLabel[];\n /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */\n safety: NoteSafety;\n /** {@link SafetyResult.reasons} bitmask. */\n reasons: number;\n /** Preferred stepwise resolution, when the safety evaluator provides one. */\n resolveTo?: number;\n /** Nearby fully safe replacement pitches, nearest first. */\n suggestions?: number[];\n rationale?: string;\n};\n\n/**\n * The full result of {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementAnalysis = {\n key: KeyScale;\n timeline: ChordTimeline;\n segmentConfidence: number[];\n cadences: CadenceHit[];\n tracks: TrackAnalysis[];\n /** Notes clashing with the sounding harmony, worst severity first. */\n conflicts: Conflict[];\n};\n\n/**\n * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementOptions = {\n /** Key context; inferred from the pooled notes when omitted. */\n key?: KeyScale;\n /**\n * A chord timeline to analyse against, instead of inferring one from the\n * notes. Supply it to analyse a hand-written progression, or to reuse the\n * timeline of an earlier call rather than re-running chord inference.\n */\n timeline?: ChordTimeline;\n /**\n * Indices of the tracks the harmony is inferred from. Defaults to every\n * pitched track. Percussion tracks are excluded either way.\n */\n harmonyTracks?: number[];\n /**\n * Lowest severity reported in `conflicts`.\n *\n * @defaultValue {@link NoteSafety.Warning}\n */\n minSeverity?: NoteSafety;\n /**\n * Time signature; defaults to 4/4.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * Chord-slot length in beats; defaults to one bar of `ts`.\n *\n * @defaultValue one bar of `ts`\n */\n harmonicRhythm?: number;\n /**\n * Safety profile used for conflict detection; defaults to `pop`.\n *\n * @defaultValue `'pop'`\n */\n profile?: SafetyProfile;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * Sounding pitches of every sub-voice at a beat, excluding one sub-voice.\n *\n * Only the sub-voice under analysis is excluded — sibling sub-voices of the\n * same track are included, so dissonant clusters inside a single polyphonic\n * track are still detected.\n */\nfunction otherVoicesSounding(\n prepared: PreparedTrack[],\n excludeTrack: number,\n excludeVoice: number,\n beat: number,\n): VoiceSnapshot[] {\n const out: VoiceSnapshot[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (let v = 0; v < track.voices.length; v += 1) {\n if (t === excludeTrack && v === excludeVoice) {\n continue;\n }\n // Each sub-voice is monophonic, so this is one binary search per voice\n // rather than a scan of every note in the arrangement.\n const note = track.voices[v]?.at(beat);\n if (note === undefined) {\n continue;\n }\n const snap: VoiceSnapshot = { pitch: note.pitch };\n // Motion reasons compare one real transition shared by both voices.\n // A voice attacking exactly here contributes its adjacent predecessor;\n // a sustained voice contributes the same pitch (oblique motion).\n const previous = Math.abs(note.startBeat - beat) <= EPS ? note.prevPitch : note.pitch;\n if (previous !== undefined) {\n snap.prevPitch = previous;\n }\n out.push(snap);\n }\n }\n return out;\n}\n\n/** Index of the first segment starting strictly after a beat. */\nfunction firstSegmentAfter(timeline: ChordTimeline, beat: number): number {\n let low = 0;\n let high = timeline.segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((timeline.segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * The beats at which a note must be checked against the harmony: its onset,\n * plus the onset of every later chord segment the note sustains across. Each\n * crossed segment is visited exactly once (at its own start), so a boundary\n * beat is never evaluated twice for the same note.\n *\n * Segments are in beat order, so the crossed ones are a contiguous run found by\n * binary search rather than by scanning the whole timeline for every note.\n */\nfunction evaluationBeats(note: VoiceNote, timeline: ChordTimeline): number[] {\n const noteEnd = note.startBeat + note.durationBeat;\n const beats = [note.startBeat];\n for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {\n const start = timeline.segments[i]?.startBeat;\n if (start === undefined || start >= noteEnd - EPS) {\n break;\n }\n beats.push(start);\n }\n return beats;\n}\n\n/**\n * Analyse a whole arrangement against a single inferred harmony.\n *\n * The chord timeline and key are inferred from the pooled notes of every\n * pitched track (see {@link chordTimelineFromNotes}); pooling all voices is\n * robust even when roles are absent or a track doubles the harmony, so it is\n * preferred over deriving the harmony from a subset. A track marked\n * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and\n * pooling them would corrupt the key, the timeline and every reading built on\n * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to\n * analyse against a progression you already have. Zero- and negative-length\n * notes never sound, so they are dropped at ingest and appear in neither the\n * annotations nor the conflicts.\n *\n * Voice analysis assumes one monophonic voice at a time, so each track is\n * first partitioned into monophonic sub-voices (a polyphonic block-chord track\n * fans out into one sub-voice per simultaneous note; a monophonic track is\n * unaffected). Each sub-voice is labelled note-by-note against the harmony\n * with {@link analyzeVoice}, seeing every other sub-voice — including siblings\n * within the same track — as its accompaniment. Every note is then re-checked\n * with {@link evaluateSafety} at its onset and again at each chord change it\n * sustains across, so a held note that clashes with a later chord is caught:\n * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per\n * clashing beat, sorted worst severity first and then by beat.\n *\n * Every ordinary non-chord tone — a passing note, a neighbour, a prepared\n * suspension — is by definition unsafe against the chord under it, so the\n * conflict list is a report, not a fault list. Each conflict carries the note's\n * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those\n * apart and map a conflict back to the note it passed in; `minSeverity` narrows\n * the list to outright dissonance.\n *\n * @param tracks The tracks to analyse.\n * @param opts Analysis options; see {@link ArrangementOptions}.\n * @returns The inferred harmony, per-track annotations, cadences, and conflicts.\n * @example\n * ```ts\n * import { analyzeArrangement } from '@libraz/libcantus';\n * const melody = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 },\n * { pitch: 67, startBeat: 2, durationBeat: 2 },\n * ];\n * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);\n * conflicts; // notes clashing with the inferred harmony, worst severity first\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeArrangement(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions = {},\n): ArrangementAnalysis {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const budget = opts.budget;\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n let noteCount = 0;\n for (let index = 0; index < tracks.length; index += 1) {\n const notes = tracks[index]?.notes;\n // A missing array is rejected by name here rather than becoming a raw\n // TypeError further down, where the message no longer says which track.\n if (!Array.isArray(notes)) {\n throw new InvalidInputError(\n `tracks[${index}].notes must be an array; received ${typeof notes}`,\n );\n }\n assertNoteEvents(notes, `tracks[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget,\n });\n noteCount += notes.length;\n }\n // The pooled total is what the downstream timeline analysis actually sizes\n // its work by, so it is checked here, under this layer's own name.\n assertGenerationBudget(noteCount, 'arrangement notes', budget);\n const profile: SafetyProfile = opts.profile ?? 'pop';\n const minSeverity = opts.minSeverity ?? NoteSafety.Warning;\n const harmonyTracks =\n opts.harmonyTracks === undefined ? undefined : new Set(opts.harmonyTracks.map(Math.trunc));\n const pooled = poolNotes(tracks, harmonyTracks);\n\n const inferred =\n opts.timeline === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n })\n : {\n timeline: opts.timeline,\n key: opts.key ?? detectKeyFromNotes(pooled)[0]?.key ?? majorKey(0),\n segmentConfidence: opts.timeline.segments.map(() => 1),\n };\n const { timeline, key, segmentConfidence } = inferred;\n const cadences = detectCadences(timeline, key);\n const prepared = prepareTracks(tracks);\n\n const trackAnalyses: TrackAnalysis[] = [];\n const conflicts: Conflict[] = [];\n\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track) {\n continue;\n }\n if (isPercussion(track.role)) {\n // A drum hit has no harmonic reading, so labelling it against the chord\n // would produce noise rather than analysis.\n trackAnalyses.push({ name: track.name, role: track.role, notes: [] });\n continue;\n }\n const notes: AnalyzedNote[] = [];\n for (let v = 0; v < track.voices.length; v += 1) {\n const subVoice = track.voices[v];\n if (!subVoice) {\n continue;\n }\n const analyzed = analyzeVoice(subVoice.voice, timeline.at, key, (beat) =>\n otherVoicesSounding(prepared, t, v, beat),\n );\n notes.push(...analyzed.map((note) => ({ ...note, trackIndex: track.trackIndex })));\n const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));\n\n for (let noteIndex = 0; noteIndex < subVoice.voice.length; noteIndex += 1) {\n const note = subVoice.voice[noteIndex];\n const preparedNote = subVoice.sounding[noteIndex];\n if (note === undefined || preparedNote === undefined) {\n continue;\n }\n for (const beat of evaluationBeats(note, timeline)) {\n const atOnset = Math.abs(beat - note.startBeat) <= EPS;\n const result = evaluateSafety({\n profile,\n candidatePitch: note.pitch,\n prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,\n chord: timeline.at(beat),\n key,\n otherVoices: otherVoicesSounding(prepared, t, v, beat),\n strongBeat: isStrongBeat(beat, ts),\n });\n if (result.safety !== NoteSafety.Safe && result.safety >= minSeverity) {\n const conflict: Conflict = {\n beat,\n trackName: track.name,\n trackIndex: track.trackIndex,\n noteId: note.id,\n pitch: note.pitch,\n labels: labelsById.get(note.id) ?? [],\n safety: result.safety,\n reasons: result.reasons,\n };\n if (note.originalIndex !== undefined) {\n conflict.originalIndex = note.originalIndex;\n }\n if (result.rationale !== undefined) {\n conflict.rationale = result.rationale;\n }\n if (result.resolveTo !== undefined) {\n conflict.resolveTo = result.resolveTo;\n }\n if (result.suggestions !== undefined) {\n conflict.suggestions = [...result.suggestions];\n }\n conflicts.push(conflict);\n }\n }\n }\n }\n // Sub-voice results interleave; restore the track's onset-then-pitch order.\n notes.sort((a, b) => a.noteId - b.noteId);\n trackAnalyses.push({ name: track.name, role: track.role, notes });\n }\n\n conflicts.sort((a, b) => b.safety - a.safety || a.beat - b.beat);\n\n return { key, timeline, segmentConfidence, cadences, tracks: trackAnalyses, conflicts };\n}\n"]}