@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
@@ -1,75 +1,20 @@
1
- import { T as TimeSignature } from '../index-Cl7mhOFM.cjs';
2
- import { K as KeyScale, N as NoteEvent } from '../types-MGP29r3t.cjs';
3
- import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-CxqBQtH1.cjs';
4
- import { b as ChordTimeline, C as CadenceHit } from '../index-CpOsP1y9.cjs';
5
- export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-CpOsP1y9.cjs';
6
- import { C as Chord } from '../index-BNlGP86A.cjs';
7
- export { C as ChordMatch, D as DetectChordOptions, K as KeyMatch, d as detectChord, a as detectChordBest, b as detectKey } from '../index-CnZCo29u.cjs';
8
- export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFunction, b as analyzeChord, c as borrowedSource, d as detectCadence, f as functionOf, i as isBorrowedChord, e as isDiatonic, g as isMinorKey, p as parallelKey, s as secondaryDominant } from '../function-DYt4iKsn.cjs';
1
+ import { T as TimeSignature } from '../index-DXd9JikN.cjs';
2
+ import { N as NoteEvent, K as KeyScale } from '../types-BN6_Q9Kl.cjs';
3
+ import { d as ChordToneRole, C as Chord } from '../index-oTQbYYIs.cjs';
4
+ export { a as ChordQuality, b as ChordSegment, c as ChordSpan, P as PitchSpelling } from '../index-oTQbYYIs.cjs';
5
+ import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-B2Ulxxpo.cjs';
6
+ import { a as ChordTimeline, C as CadenceHit } from '../index-BkYJ0xMq.cjs';
7
+ export { b as ChordTimelineOptions, c as ChordTimelineResult, d as chordTimelineFromChords, e as chordTimelineFromNotes, f as detectCadences } from '../index-BkYJ0xMq.cjs';
8
+ export { C as ChordMatch, D as DetectChordOptions, a as DetectKeyOptions, K as KeyMatch, b as KeyVariant, d as detectChord, c as detectChordBest, e as detectKey, f as detectKeyBest, g as detectKeyFromNotes } from '../index-alrRninU.cjs';
9
+ export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, b as ChordToRomanOptions, H as HarmonicFunction, c as analyzeChord, d as borrowedSource, e as chordToRoman, f as detectCadence, g as functionOf, i as isBorrowedChord, h as isDiatonic, j as isMinorKey, p as parallelKey, r as romanToChord, s as secondaryDominant } from '../function-CxixTB02.cjs';
9
10
 
10
11
  /**
11
- * Roman-numeral parsing and formatting.
12
+ * A suspension figure, named by the interval above the bass and the interval it
13
+ * resolves to.
12
14
  *
13
- * The {@link ROMAN_STYLE} table is the single source of truth for both
14
- * directions, so {@link chordToRoman} and {@link romanToChord} stay mutual
15
- * inverses by construction.
16
- */
17
-
18
- /**
19
- * Build the chord denoted by a Roman numeral in a key.
20
- *
21
- * Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`
22
- * suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions
23
- * (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash
24
- * (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree
25
- * whose root becomes a local major tonic for the applied chord. Inverted chords
26
- * carry a `bassPc`.
27
- *
28
- * The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==
29
- * `m7b5`), since no half-diminished triad exists. Extension and added-tone
30
- * suffixes are the canonical case-sensitive forms emitted by
31
- * {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->
32
- * `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->
33
- * `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->
34
- * `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.
35
- * A figure string that is neither a recognized inversion nor a supported
36
- * quality suffix throws rather than silently degrading to a triad.
37
- *
38
- * @param text The Roman numeral.
39
- * @param key The prevailing key.
40
- * @returns The chord.
41
- * @example
42
- * ```ts
43
- * import { romanToChord, majorKey } from '@libraz/libcantus';
44
- * romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }
45
- * ```
46
- * @category Functional Harmony
47
- */
48
- declare function romanToChord(text: string, key: KeyScale): Chord;
49
- /**
50
- * Render a chord as a Roman numeral relative to a key.
51
- *
52
- * Diatonic roots take their scale-degree numeral directly, so numerals are
53
- * correct in both major and minor keys (and any custom scale). The quality
54
- * selects the case and suffix; chromatic roots receive a flat/sharp spelling by
55
- * convention. When the chord carries a `bassPc` on a chord tone, a figured-bass
56
- * inversion (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true
57
- * seventh chords; added-tone and extended qualities have no lossless figure and
58
- * render in root position instead (the bass is dropped, pitch classes are
59
- * preserved).
60
- *
61
- * @param chord The chord to name.
62
- * @param key The prevailing key.
63
- * @returns The Roman numeral string.
64
- * @example
65
- * ```ts
66
- * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';
67
- * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)
68
- * ```
69
- * @category Functional Harmony
15
+ * @category Arrangement & Analysis
70
16
  */
71
- declare function chordToRoman(chord: Chord, key: KeyScale): string;
72
-
17
+ type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';
73
18
  /**
74
19
  * A theory annotation attached to a note.
75
20
  *
@@ -77,7 +22,7 @@ declare function chordToRoman(chord: Chord, key: KeyScale): string;
77
22
  */
78
23
  type TheoryLabel = {
79
24
  kind: 'chordTone';
80
- role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
25
+ role: ChordToneRole;
81
26
  } | {
82
27
  kind: 'tension';
83
28
  degree: 9 | 11 | 13;
@@ -89,7 +34,7 @@ type TheoryLabel = {
89
34
  kind: 'neighbor';
90
35
  } | {
91
36
  kind: 'suspension';
92
- type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';
37
+ type: SuspensionFigure;
93
38
  resolveTo: number;
94
39
  } | {
95
40
  kind: 'anticipation';
@@ -105,21 +50,60 @@ type TheoryLabel = {
105
50
  /**
106
51
  * A note with its theory labels and a short rationale.
107
52
  *
53
+ * The labels describe the note against the chord sounding at its **onset**;
54
+ * they are not re-derived for the later chords a long note sustains across.
55
+ * `analyzeArrangement` does re-check every crossed chord change, and reports
56
+ * what it finds as conflicts, so a note labelled a chord tone here can still
57
+ * appear in `conflicts` at a later beat.
58
+ *
59
+ * The identity fields make an annotation mappable back to the caller's input:
60
+ * `originalIndex` is the note's position in the array that was passed in, and
61
+ * `trackIndex` is set by {@link analyzeArrangement}.
62
+ *
108
63
  * @category Arrangement & Analysis
109
64
  */
110
65
  type AnalyzedNote = {
111
66
  noteId: number;
67
+ /** The note's index in the caller's own array, when the caller supplied one. */
68
+ originalIndex?: number;
69
+ /** The note's track, set by `analyzeArrangement`. */
70
+ trackIndex?: number;
71
+ pitch: number;
72
+ startBeat: number;
73
+ durationBeat: number;
112
74
  labels: TheoryLabel[];
113
75
  rationale?: string;
114
76
  };
115
77
  /**
116
- * A single note in a monophonic voice: a {@link NoteEvent} with a stable id.
78
+ * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,
79
+ * optionally, its index in the caller's original array.
117
80
  *
118
81
  * @category Arrangement & Analysis
119
82
  */
120
83
  type VoiceNote = NoteEvent & {
84
+ id?: number;
85
+ originalIndex?: number;
86
+ };
87
+ /**
88
+ * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and
89
+ * {@link analyzeArrangement} works in.
90
+ *
91
+ * @category Arrangement & Analysis
92
+ */
93
+ type IdentifiedVoiceNote = VoiceNote & {
121
94
  id: number;
122
95
  };
96
+ /**
97
+ * Give plain note events the ids {@link analyzeVoice} reports back.
98
+ *
99
+ * The id is only an identity handle, so the array position serves; this exists
100
+ * so the mapping is a named call rather than a `map` every caller writes.
101
+ *
102
+ * @param events The note events, in time order.
103
+ * @returns The same notes as voice notes, each carrying its index as its id.
104
+ * @category Arrangement & Analysis
105
+ */
106
+ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[];
123
107
  /**
124
108
  * Label every note of a voice with its theory roles.
125
109
  *
@@ -136,26 +120,28 @@ type VoiceNote = NoteEvent & {
136
120
  * escape) — only harmonic ones. Callers with truly polyphonic material should
137
121
  * split it into monophonic sub-voices first, as `analyzeArrangement` does.
138
122
  *
139
- * @param voice The monophonic voice, in time order.
123
+ * @param voice The monophonic voice, in time order. A note without an `id` is
124
+ * identified by its position, so plain note events can be passed straight in.
140
125
  * @param chordAtBeat Chord sounding at a given beat, or null.
141
126
  * @param key Key context for leading-tone detection.
142
- * @param otherVoicesAtBeat Other sounding voices at a given beat.
127
+ * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
128
+ * none, which is the whole story for a solo line.
143
129
  * @returns One annotation per input note.
144
130
  * @example
145
131
  * ```ts
146
132
  * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
147
133
  * const voice = [
148
- * { id: 0, pitch: 60, startBeat: 0, durationBeat: 1 },
149
- * { id: 1, pitch: 62, startBeat: 1, durationBeat: 1 },
150
- * { id: 2, pitch: 64, startBeat: 2, durationBeat: 1 },
134
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
135
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
136
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
151
137
  * ];
152
138
  * const cMajor = makeChord(0, 'maj');
153
- * const labels = analyzeVoice(voice, () => cMajor, majorKey(0), () => []);
139
+ * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));
154
140
  * labels; // one AnalyzedNote per input note, in the same order
155
141
  * ```
156
142
  * @category Arrangement & Analysis
157
143
  */
158
- declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
144
+ declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat?: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
159
145
 
160
146
  /**
161
147
  * Arrangement track-role analysis: a single harmony is inferred from every
@@ -167,9 +153,18 @@ declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) =>
167
153
  /**
168
154
  * The musical role a track plays in the arrangement.
169
155
  *
156
+ * Only `drums` changes the analysis: its pitches select instruments rather than
157
+ * naming harmony, so such a track is excluded from chord and key inference and
158
+ * from every voice-leading comparison, and is reported back with no
159
+ * annotations. `harmony` is meaningful through
160
+ * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords
161
+ * are inferred from. The rest are labels carried through to
162
+ * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so
163
+ * a track left unlabelled reports `'other'` rather than a guess.
164
+ *
170
165
  * @category Arrangement & Analysis
171
166
  */
172
- type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
167
+ type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';
173
168
  /**
174
169
  * One input track: its notes plus optional name and role.
175
170
  *
@@ -178,7 +173,7 @@ type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
178
173
  type ArrangementTrack = {
179
174
  name?: string;
180
175
  role?: TrackRole;
181
- notes: NoteEvent[];
176
+ notes: readonly NoteEvent[];
182
177
  };
183
178
  /**
184
179
  * A track after analysis: its resolved name, role, and per-note annotations.
@@ -199,10 +194,22 @@ type TrackAnalysis = {
199
194
  type Conflict = {
200
195
  beat: number;
201
196
  trackName: string;
197
+ /** The clashing track's index in the caller's input array. */
198
+ trackIndex: number;
199
+ /** The note's index in that track's own note array. */
200
+ originalIndex?: number;
201
+ /** The id the note carries in {@link TrackAnalysis.notes}. */
202
+ noteId: number;
202
203
  pitch: number;
204
+ /**
205
+ * The note's theory labels at its onset, so a caller can tell a genuine clash
206
+ * from a passing tone, a neighbour, or a prepared suspension — all of which
207
+ * are ordinary non-chord tones that still evaluate as unsafe.
208
+ */
209
+ labels: TheoryLabel[];
203
210
  /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
204
211
  safety: NoteSafety;
205
- /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
212
+ /** {@link SafetyResult.reasons} bitmask. */
206
213
  reasons: number;
207
214
  /** Preferred stepwise resolution, when the safety evaluator provides one. */
208
215
  resolveTo?: number;
@@ -232,6 +239,23 @@ type ArrangementAnalysis = {
232
239
  type ArrangementOptions = {
233
240
  /** Key context; inferred from the pooled notes when omitted. */
234
241
  key?: KeyScale;
242
+ /**
243
+ * A chord timeline to analyse against, instead of inferring one from the
244
+ * notes. Supply it to analyse a hand-written progression, or to reuse the
245
+ * timeline of an earlier call rather than re-running chord inference.
246
+ */
247
+ timeline?: ChordTimeline;
248
+ /**
249
+ * Indices of the tracks the harmony is inferred from. Defaults to every
250
+ * pitched track. Percussion tracks are excluded either way.
251
+ */
252
+ harmonyTracks?: number[];
253
+ /**
254
+ * Lowest severity reported in `conflicts`.
255
+ *
256
+ * @defaultValue {@link NoteSafety.Warning}
257
+ */
258
+ minSeverity?: NoteSafety;
235
259
  /**
236
260
  * Time signature; defaults to 4/4.
237
261
  *
@@ -250,16 +274,31 @@ type ArrangementOptions = {
250
274
  * @defaultValue `'pop'`
251
275
  */
252
276
  profile?: SafetyProfile;
277
+ /**
278
+ * Upper bound on the work this call may do — note counts, windows, and
279
+ * candidate counts are each checked against it before anything is allocated.
280
+ *
281
+ * Raise it to analyse a piece larger than the default allows; the default is
282
+ * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast
283
+ * rather than blocking the thread.
284
+ *
285
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
286
+ */
287
+ budget?: number;
253
288
  };
254
289
  /**
255
290
  * Analyse a whole arrangement against a single inferred harmony.
256
291
  *
257
- * The chord timeline and key are inferred from the pooled notes of every track
258
- * (see {@link chordTimelineFromNotes}); pooling all voices is robust even when
259
- * roles are absent or a track doubles the harmony, so it is preferred over
260
- * deriving the harmony from a subset. Zero- and negative-length notes never
261
- * sound, so they are dropped at ingest and appear in neither the annotations
262
- * nor the conflicts.
292
+ * The chord timeline and key are inferred from the pooled notes of every
293
+ * pitched track (see {@link chordTimelineFromNotes}); pooling all voices is
294
+ * robust even when roles are absent or a track doubles the harmony, so it is
295
+ * preferred over deriving the harmony from a subset. A track marked
296
+ * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and
297
+ * pooling them would corrupt the key, the timeline and every reading built on
298
+ * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to
299
+ * analyse against a progression you already have. Zero- and negative-length
300
+ * notes never sound, so they are dropped at ingest and appear in neither the
301
+ * annotations nor the conflicts.
263
302
  *
264
303
  * Voice analysis assumes one monophonic voice at a time, so each track is
265
304
  * first partitioned into monophonic sub-voices (a polyphonic block-chord track
@@ -272,6 +311,13 @@ type ArrangementOptions = {
272
311
  * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
273
312
  * clashing beat, sorted worst severity first and then by beat.
274
313
  *
314
+ * Every ordinary non-chord tone — a passing note, a neighbour, a prepared
315
+ * suspension — is by definition unsafe against the chord under it, so the
316
+ * conflict list is a report, not a fault list. Each conflict carries the note's
317
+ * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those
318
+ * apart and map a conflict back to the note it passed in; `minSeverity` narrows
319
+ * the list to outright dissonance.
320
+ *
275
321
  * @param tracks The tracks to analyse.
276
322
  * @param opts Analysis options; see {@link ArrangementOptions}.
277
323
  * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
@@ -311,12 +357,12 @@ type TensionPoint = {
311
357
  *
312
358
  * At each sample beat the tension combines three normalized terms:
313
359
  * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
314
- * tonic or no chord `0`), weighted {@link FUNCTION_WEIGHT};
360
+ * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;
315
361
  * - the dissonance of the sounding notes — the larger of the share of sounding
316
362
  * pitches that are not chord tones and the share that {@link evaluateSafety}
317
- * rates {@link NoteSafety.Dissonant} — weighted {@link DISSONANCE_WEIGHT};
363
+ * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;
318
364
  * - the registral span of the sounding notes, saturating at
319
- * {@link SPAN_SATURATION} semitones, weighted {@link SPAN_WEIGHT}.
365
+ * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.
320
366
  *
321
367
  * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
322
368
  * pooled together, so the result is deterministic and self-contained.
@@ -337,5 +383,29 @@ type TensionPoint = {
337
383
  declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
338
384
  step?: number;
339
385
  }): TensionPoint[];
386
+ /**
387
+ * Sample the tension of an arrangement already analysed by
388
+ * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.
389
+ *
390
+ * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference
391
+ * twice over the same notes. This takes the analysis it already produced, so
392
+ * the curve and the annotations describe the same harmony by construction.
393
+ *
394
+ * @param tracks The same tracks that were analysed.
395
+ * @param analysis The result of {@link analyzeArrangement} for those tracks.
396
+ * @param opts Analysis options plus an optional `step` (default one beat).
397
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
398
+ * @example
399
+ * ```ts
400
+ * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';
401
+ * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];
402
+ * const analysis = analyzeArrangement(tracks);
403
+ * tensionCurveFrom(tracks, analysis, { step: 1 });
404
+ * ```
405
+ * @category Arrangement & Analysis
406
+ */
407
+ declare function tensionCurveFrom(tracks: ArrangementTrack[], analysis: ArrangementAnalysis, opts?: ArrangementOptions & {
408
+ step?: number;
409
+ }): TensionPoint[];
340
410
 
341
- export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, ChordTimeline, type Conflict, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, romanToChord, tensionCurve };
411
+ export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, Chord as ChordData, ChordTimeline, ChordToneRole, type Conflict, type IdentifiedVoiceNote, KeyScale, NoteEvent, NoteSafety, SafetyProfile, type SuspensionFigure, type TensionPoint, type TheoryLabel, TimeSignature, type TrackAnalysis, type TrackRole, type VoiceNote, VoiceSnapshot, analyzeArrangement, analyzeVoice, tensionCurve, tensionCurveFrom, toVoiceNotes };