@libraz/libcantus 0.9.2 → 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 (200) hide show
  1. package/README.md +6 -5
  2. package/dist/analyze/index.cjs +41 -1676
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +194 -122
  5. package/dist/analyze/index.d.ts +194 -122
  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-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
  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-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
  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-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/chunk-HKGYCQ34.js +53 -0
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/chunk-I24MV65U.js +106 -0
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
  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-N6IHQUOX.js +1 -0
  36. package/dist/chunk-N6IHQUOX.js.map +1 -0
  37. package/dist/chunk-NKIBQ6PT.js +30 -0
  38. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  39. package/dist/chunk-NP4NA2FB.cjs +106 -0
  40. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  41. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  42. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  43. package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
  44. package/dist/chunk-NYYWMV2C.js.map +1 -0
  45. package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
  46. package/dist/chunk-Q2XUP227.js.map +1 -0
  47. package/dist/chunk-Q6OP4U43.cjs +327 -0
  48. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  49. package/dist/chunk-RC6GBMCD.cjs +845 -0
  50. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  51. package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
  52. package/dist/chunk-RK35RIN7.js.map +1 -0
  53. package/dist/chunk-RUMU3UA6.cjs +53 -0
  54. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  55. package/dist/chunk-T64C7ZD7.cjs +1 -0
  56. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  57. package/dist/chunk-XEBG3G53.js +498 -0
  58. package/dist/chunk-XEBG3G53.js.map +1 -0
  59. package/dist/chunk-YLEMGEFH.js +246 -0
  60. package/dist/chunk-YLEMGEFH.js.map +1 -0
  61. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  62. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  63. package/dist/core/index.cjs +130 -431
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +331 -17
  66. package/dist/core/index.d.ts +331 -17
  67. package/dist/core/index.js +71 -13
  68. package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
  69. package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
  70. package/dist/generate/index.cjs +30 -3751
  71. package/dist/generate/index.cjs.map +1 -1
  72. package/dist/generate/index.d.cts +373 -59
  73. package/dist/generate/index.d.ts +373 -59
  74. package/dist/generate/index.js +23 -9
  75. package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
  76. package/dist/index-70p168VM.d.ts +192 -0
  77. package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
  78. package/dist/index-BtXT6UQ0.d.cts +192 -0
  79. package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  80. package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
  81. package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
  82. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  83. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  84. package/dist/index-OG65ocOr.d.cts +272 -0
  85. package/dist/index-OG65ocOr.d.ts +272 -0
  86. package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
  87. package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
  88. package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
  89. package/dist/index.cjs +419 -6848
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.d.cts +14 -13
  92. package/dist/index.d.ts +14 -13
  93. package/dist/index.js +132 -27
  94. package/dist/model/index.cjs +8 -2423
  95. package/dist/model/index.cjs.map +1 -1
  96. package/dist/model/index.d.cts +433 -74
  97. package/dist/model/index.d.ts +433 -74
  98. package/dist/model/index.js +6 -7
  99. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  100. package/dist/styled-DJbkJQfj.d.cts +506 -0
  101. package/dist/theory/index.cjs +172 -1676
  102. package/dist/theory/index.cjs.map +1 -1
  103. package/dist/theory/index.d.cts +151 -233
  104. package/dist/theory/index.d.ts +151 -233
  105. package/dist/theory/index.js +31 -7
  106. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  107. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  108. package/package.json +73 -24
  109. package/dist/chunk-5HTNMQW4.js.map +0 -1
  110. package/dist/chunk-7Y7WF64D.js.map +0 -1
  111. package/dist/chunk-A4532ZFR.js.map +0 -1
  112. package/dist/chunk-BHUT2S5G.js +0 -22
  113. package/dist/chunk-BHUT2S5G.js.map +0 -1
  114. package/dist/chunk-CCBLVAPK.js +0 -42
  115. package/dist/chunk-CCBLVAPK.js.map +0 -1
  116. package/dist/chunk-COKTX2G5.js.map +0 -1
  117. package/dist/chunk-DYWFNC4A.js +0 -152
  118. package/dist/chunk-DYWFNC4A.js.map +0 -1
  119. package/dist/chunk-GQKN5KDT.js.map +0 -1
  120. package/dist/chunk-LN7ZZMD2.js.map +0 -1
  121. package/dist/chunk-MSWFZF7J.js +0 -118
  122. package/dist/chunk-MSWFZF7J.js.map +0 -1
  123. package/dist/chunk-R3S2LUBH.js.map +0 -1
  124. package/dist/chunk-SDM3VJJ5.js +0 -41
  125. package/dist/chunk-SDM3VJJ5.js.map +0 -1
  126. package/dist/chunk-XTC3X6MS.js.map +0 -1
  127. package/dist/chunk-ZXW5YIWK.js +0 -55
  128. package/dist/chunk-ZXW5YIWK.js.map +0 -1
  129. package/dist/index-CGMZNCx6.d.cts +0 -140
  130. package/dist/index-CGMZNCx6.d.ts +0 -140
  131. package/dist/styled-BjXtOFNk.d.cts +0 -282
  132. package/dist/styled-D7BAx3S3.d.ts +0 -282
  133. package/src/analyze/arrange/index.ts +0 -21
  134. package/src/analyze/arrange/internal.ts +0 -125
  135. package/src/analyze/arrange/tension.ts +0 -187
  136. package/src/analyze/arrange/tracks.ts +0 -279
  137. package/src/analyze/detect/index.ts +0 -235
  138. package/src/analyze/functional/borrowed.ts +0 -84
  139. package/src/analyze/functional/cadence.ts +0 -66
  140. package/src/analyze/functional/function.ts +0 -184
  141. package/src/analyze/functional/index.ts +0 -23
  142. package/src/analyze/functional/internal.ts +0 -29
  143. package/src/analyze/functional/roman.ts +0 -393
  144. package/src/analyze/index.ts +0 -49
  145. package/src/analyze/timeline/index.ts +0 -415
  146. package/src/analyze/voice/index.ts +0 -330
  147. package/src/core/index.ts +0 -47
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -282
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -53
  152. package/src/core/tuning/index.ts +0 -156
  153. package/src/core/types.ts +0 -29
  154. package/src/generate/bass/index.ts +0 -350
  155. package/src/generate/countermelody/index.ts +0 -379
  156. package/src/generate/drums/beat.ts +0 -266
  157. package/src/generate/drums/euclid.ts +0 -58
  158. package/src/generate/drums/fills.ts +0 -356
  159. package/src/generate/drums/ghost.ts +0 -119
  160. package/src/generate/drums/hihat.ts +0 -285
  161. package/src/generate/drums/hit.ts +0 -31
  162. package/src/generate/drums/index.ts +0 -329
  163. package/src/generate/drums/internal.ts +0 -257
  164. package/src/generate/drums/kick.ts +0 -160
  165. package/src/generate/drums/percussion.ts +0 -121
  166. package/src/generate/drums/rng.ts +0 -7
  167. package/src/generate/drums/swing.ts +0 -71
  168. package/src/generate/groove/index.ts +0 -326
  169. package/src/generate/harmonize/index.ts +0 -440
  170. package/src/generate/index.ts +0 -59
  171. package/src/generate/motif/index.ts +0 -398
  172. package/src/generate/progression/index.ts +0 -370
  173. package/src/generate/reharmony/index.ts +0 -330
  174. package/src/generate/rhythm/index.ts +0 -165
  175. package/src/index.ts +0 -247
  176. package/src/model/chord.ts +0 -423
  177. package/src/model/index.ts +0 -15
  178. package/src/model/interval.ts +0 -82
  179. package/src/model/key.ts +0 -266
  180. package/src/model/note.ts +0 -176
  181. package/src/model/progression.ts +0 -161
  182. package/src/model/shared.ts +0 -12
  183. package/src/theory/chord/index.ts +0 -373
  184. package/src/theory/chordscale/index.ts +0 -374
  185. package/src/theory/counterpoint/index.ts +0 -230
  186. package/src/theory/harmony/index.ts +0 -92
  187. package/src/theory/index.ts +0 -107
  188. package/src/theory/safety/index.ts +0 -419
  189. package/src/theory/scale/degrees.ts +0 -123
  190. package/src/theory/scale/index.ts +0 -28
  191. package/src/theory/scale/key.ts +0 -56
  192. package/src/theory/scale/masks.ts +0 -153
  193. package/src/theory/spelling/index.ts +0 -182
  194. package/src/theory/symbol/index.ts +0 -268
  195. package/src/theory/voicing/index.ts +0 -5
  196. package/src/theory/voicing/internal.ts +0 -168
  197. package/src/theory/voicing/leading.ts +0 -112
  198. package/src/theory/voicing/satb.ts +0 -195
  199. package/src/theory/voicing/styled.ts +0 -176
  200. package/tsconfig.json +0 -23
@@ -1,10 +1,12 @@
1
- import { T as TimeSignature } from '../index-Cl7mhOFM.js';
2
- import { K as KeyScale, N as NoteEvent } from '../types-MGP29r3t.js';
3
- import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-BreoJRw0.js';
4
- import { b as ChordTimeline, C as CadenceHit } from '../index-BMPxwjwV.js';
5
- export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-BMPxwjwV.js';
6
- import { C as Chord, a as ChordQuality } from '../index-CM5W4rcm.js';
7
- 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-2bJW1Zp_.js';
1
+ import { T as TimeSignature } from '../index-DXd9JikN.js';
2
+ import { K as KeyScale, N as NoteEvent } from '../types-BN6_Q9Kl.js';
3
+ import { C as Chord, d as ChordToneRole } from '../index-C83mZI1P.js';
4
+ export { a as ChordQuality, b as ChordSegment, c as ChordSpan, P as PitchSpelling } from '../index-C83mZI1P.js';
5
+ import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-6VR8-kit.js';
6
+ import { a as ChordTimeline, C as CadenceHit } from '../index-C5UBbQ4G.js';
7
+ export { b as ChordTimelineOptions, c as ChordTimelineResult, d as chordTimelineFromChords, e as chordTimelineFromNotes, f as detectCadences } from '../index-C5UBbQ4G.js';
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-70p168VM.js';
9
+ 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-BtiZb9i9.js';
8
10
 
9
11
  /**
10
12
  * Roman-numeral parsing and formatting.
@@ -45,30 +47,64 @@ export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFun
45
47
  * @category Functional Harmony
46
48
  */
47
49
  declare function romanToChord(text: string, key: KeyScale): Chord;
50
+ /**
51
+ * Options for {@link chordToRoman}.
52
+ *
53
+ * @category Functional Harmony
54
+ */
55
+ type ChordToRomanOptions = {
56
+ /**
57
+ * Render a tonicizing chord as an applied numeral (`V7/V`, `viio7/ii`)
58
+ * instead of naming its root against the home key (`II7`, `#ivo7`).
59
+ *
60
+ * Off by default: naming the root is always a correct spelling, whereas
61
+ * whether a chromatic dominant is genuinely applied is a reading only the
62
+ * caller can make. Turning it on makes {@link chordToRoman} the exact inverse
63
+ * of {@link romanToChord} for the chords {@link secondaryDominant} builds.
64
+ */
65
+ applied?: boolean;
66
+ };
48
67
  /**
49
68
  * Render a chord as a Roman numeral relative to a key.
50
69
  *
51
70
  * Diatonic roots take their scale-degree numeral directly, so numerals are
52
71
  * correct in both major and minor keys (and any custom scale). The quality
53
72
  * selects the case and suffix; chromatic roots receive a flat/sharp spelling by
54
- * convention. When the chord carries a `bassPc` on a chord tone, a figured-bass
55
- * inversion (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true
56
- * seventh chords; added-tone and extended qualities have no lossless figure and
57
- * render in root position instead (the bass is dropped, pitch classes are
58
- * preserved).
73
+ * convention. A key that is not heptatonic has no numeral of its own for every
74
+ * degree, so its roots are named against its parallel major.
75
+ *
76
+ * When the chord carries a `bassPc` on a chord tone, a figured-bass inversion
77
+ * (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true seventh
78
+ * chords; added-tone and extended qualities have no lossless figure and render
79
+ * in root position instead (the bass is dropped, pitch classes are preserved).
80
+ *
81
+ * A `bassPc` that is *not* a chord tone — a pedal or passing bass such as
82
+ * `C/D` — is likewise dropped, and the numeral names the upper chord alone.
83
+ * Roman-numeral notation spends its slash on applied chords (`V7/V`), so there
84
+ * is no unambiguous way to write such a bass; a caller that must keep it should
85
+ * read `chord.bassPc` alongside the numeral.
59
86
  *
60
87
  * @param chord The chord to name.
61
88
  * @param key The prevailing key.
89
+ * @param opts `applied` renders tonicizing chords as `V7/V`-style numerals.
62
90
  * @returns The Roman numeral string.
63
91
  * @example
64
92
  * ```ts
65
93
  * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';
66
94
  * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)
95
+ * chordToRoman(makeChord(2, 'dom7'), majorKey(0), { applied: true }); // => 'V7/V'
67
96
  * ```
68
97
  * @category Functional Harmony
69
98
  */
70
- declare function chordToRoman(chord: Chord, key: KeyScale): string;
99
+ declare function chordToRoman(chord: Chord, key: KeyScale, opts?: ChordToRomanOptions): string;
71
100
 
101
+ /**
102
+ * A suspension figure, named by the interval above the bass and the interval it
103
+ * resolves to.
104
+ *
105
+ * @category Arrangement & Analysis
106
+ */
107
+ type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';
72
108
  /**
73
109
  * A theory annotation attached to a note.
74
110
  *
@@ -76,7 +112,7 @@ declare function chordToRoman(chord: Chord, key: KeyScale): string;
76
112
  */
77
113
  type TheoryLabel = {
78
114
  kind: 'chordTone';
79
- role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
115
+ role: ChordToneRole;
80
116
  } | {
81
117
  kind: 'tension';
82
118
  degree: 9 | 11 | 13;
@@ -88,7 +124,7 @@ type TheoryLabel = {
88
124
  kind: 'neighbor';
89
125
  } | {
90
126
  kind: 'suspension';
91
- type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';
127
+ type: SuspensionFigure;
92
128
  resolveTo: number;
93
129
  } | {
94
130
  kind: 'anticipation';
@@ -104,21 +140,60 @@ type TheoryLabel = {
104
140
  /**
105
141
  * A note with its theory labels and a short rationale.
106
142
  *
143
+ * The labels describe the note against the chord sounding at its **onset**;
144
+ * they are not re-derived for the later chords a long note sustains across.
145
+ * `analyzeArrangement` does re-check every crossed chord change, and reports
146
+ * what it finds as conflicts, so a note labelled a chord tone here can still
147
+ * appear in `conflicts` at a later beat.
148
+ *
149
+ * The identity fields make an annotation mappable back to the caller's input:
150
+ * `originalIndex` is the note's position in the array that was passed in, and
151
+ * `trackIndex` is set by {@link analyzeArrangement}.
152
+ *
107
153
  * @category Arrangement & Analysis
108
154
  */
109
155
  type AnalyzedNote = {
110
156
  noteId: number;
157
+ /** The note's index in the caller's own array, when the caller supplied one. */
158
+ originalIndex?: number;
159
+ /** The note's track, set by `analyzeArrangement`. */
160
+ trackIndex?: number;
161
+ pitch: number;
162
+ startBeat: number;
163
+ durationBeat: number;
111
164
  labels: TheoryLabel[];
112
165
  rationale?: string;
113
166
  };
114
167
  /**
115
- * A single note in a monophonic voice: a {@link NoteEvent} with a stable id.
168
+ * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,
169
+ * optionally, its index in the caller's original array.
116
170
  *
117
171
  * @category Arrangement & Analysis
118
172
  */
119
173
  type VoiceNote = NoteEvent & {
174
+ id?: number;
175
+ originalIndex?: number;
176
+ };
177
+ /**
178
+ * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and
179
+ * {@link analyzeArrangement} works in.
180
+ *
181
+ * @category Arrangement & Analysis
182
+ */
183
+ type IdentifiedVoiceNote = VoiceNote & {
120
184
  id: number;
121
185
  };
186
+ /**
187
+ * Give plain note events the ids {@link analyzeVoice} reports back.
188
+ *
189
+ * The id is only an identity handle, so the array position serves; this exists
190
+ * so the mapping is a named call rather than a `map` every caller writes.
191
+ *
192
+ * @param events The note events, in time order.
193
+ * @returns The same notes as voice notes, each carrying its index as its id.
194
+ * @category Arrangement & Analysis
195
+ */
196
+ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[];
122
197
  /**
123
198
  * Label every note of a voice with its theory roles.
124
199
  *
@@ -135,26 +210,28 @@ type VoiceNote = NoteEvent & {
135
210
  * escape) — only harmonic ones. Callers with truly polyphonic material should
136
211
  * split it into monophonic sub-voices first, as `analyzeArrangement` does.
137
212
  *
138
- * @param voice The monophonic voice, in time order.
213
+ * @param voice The monophonic voice, in time order. A note without an `id` is
214
+ * identified by its position, so plain note events can be passed straight in.
139
215
  * @param chordAtBeat Chord sounding at a given beat, or null.
140
216
  * @param key Key context for leading-tone detection.
141
- * @param otherVoicesAtBeat Other sounding voices at a given beat.
217
+ * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
218
+ * none, which is the whole story for a solo line.
142
219
  * @returns One annotation per input note.
143
220
  * @example
144
221
  * ```ts
145
222
  * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
146
223
  * const voice = [
147
- * { id: 0, pitch: 60, startBeat: 0, durationBeat: 1 },
148
- * { id: 1, pitch: 62, startBeat: 1, durationBeat: 1 },
149
- * { id: 2, pitch: 64, startBeat: 2, durationBeat: 1 },
224
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
225
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
226
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
150
227
  * ];
151
228
  * const cMajor = makeChord(0, 'maj');
152
- * const labels = analyzeVoice(voice, () => cMajor, majorKey(0), () => []);
229
+ * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));
153
230
  * labels; // one AnalyzedNote per input note, in the same order
154
231
  * ```
155
232
  * @category Arrangement & Analysis
156
233
  */
157
- declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
234
+ declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat?: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
158
235
 
159
236
  /**
160
237
  * Arrangement track-role analysis: a single harmony is inferred from every
@@ -166,9 +243,18 @@ declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) =>
166
243
  /**
167
244
  * The musical role a track plays in the arrangement.
168
245
  *
246
+ * Only `drums` changes the analysis: its pitches select instruments rather than
247
+ * naming harmony, so such a track is excluded from chord and key inference and
248
+ * from every voice-leading comparison, and is reported back with no
249
+ * annotations. `harmony` is meaningful through
250
+ * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords
251
+ * are inferred from. The rest are labels carried through to
252
+ * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so
253
+ * a track left unlabelled reports `'other'` rather than a guess.
254
+ *
169
255
  * @category Arrangement & Analysis
170
256
  */
171
- type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
257
+ type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';
172
258
  /**
173
259
  * One input track: its notes plus optional name and role.
174
260
  *
@@ -177,7 +263,7 @@ type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
177
263
  type ArrangementTrack = {
178
264
  name?: string;
179
265
  role?: TrackRole;
180
- notes: NoteEvent[];
266
+ notes: readonly NoteEvent[];
181
267
  };
182
268
  /**
183
269
  * A track after analysis: its resolved name, role, and per-note annotations.
@@ -198,11 +284,27 @@ type TrackAnalysis = {
198
284
  type Conflict = {
199
285
  beat: number;
200
286
  trackName: string;
287
+ /** The clashing track's index in the caller's input array. */
288
+ trackIndex: number;
289
+ /** The note's index in that track's own note array. */
290
+ originalIndex?: number;
291
+ /** The id the note carries in {@link TrackAnalysis.notes}. */
292
+ noteId: number;
201
293
  pitch: number;
294
+ /**
295
+ * The note's theory labels at its onset, so a caller can tell a genuine clash
296
+ * from a passing tone, a neighbour, or a prepared suspension — all of which
297
+ * are ordinary non-chord tones that still evaluate as unsafe.
298
+ */
299
+ labels: TheoryLabel[];
202
300
  /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
203
301
  safety: NoteSafety;
204
- /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
302
+ /** {@link SafetyResult.reasons} bitmask. */
205
303
  reasons: number;
304
+ /** Preferred stepwise resolution, when the safety evaluator provides one. */
305
+ resolveTo?: number;
306
+ /** Nearby fully safe replacement pitches, nearest first. */
307
+ suggestions?: number[];
206
308
  rationale?: string;
207
309
  };
208
310
  /**
@@ -227,6 +329,23 @@ type ArrangementAnalysis = {
227
329
  type ArrangementOptions = {
228
330
  /** Key context; inferred from the pooled notes when omitted. */
229
331
  key?: KeyScale;
332
+ /**
333
+ * A chord timeline to analyse against, instead of inferring one from the
334
+ * notes. Supply it to analyse a hand-written progression, or to reuse the
335
+ * timeline of an earlier call rather than re-running chord inference.
336
+ */
337
+ timeline?: ChordTimeline;
338
+ /**
339
+ * Indices of the tracks the harmony is inferred from. Defaults to every
340
+ * pitched track. Percussion tracks are excluded either way.
341
+ */
342
+ harmonyTracks?: number[];
343
+ /**
344
+ * Lowest severity reported in `conflicts`.
345
+ *
346
+ * @defaultValue {@link NoteSafety.Warning}
347
+ */
348
+ minSeverity?: NoteSafety;
230
349
  /**
231
350
  * Time signature; defaults to 4/4.
232
351
  *
@@ -245,16 +364,31 @@ type ArrangementOptions = {
245
364
  * @defaultValue `'pop'`
246
365
  */
247
366
  profile?: SafetyProfile;
367
+ /**
368
+ * Upper bound on the work this call may do — note counts, windows, and
369
+ * candidate counts are each checked against it before anything is allocated.
370
+ *
371
+ * Raise it to analyse a piece larger than the default allows; the default is
372
+ * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast
373
+ * rather than blocking the thread.
374
+ *
375
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
376
+ */
377
+ budget?: number;
248
378
  };
249
379
  /**
250
380
  * Analyse a whole arrangement against a single inferred harmony.
251
381
  *
252
- * The chord timeline and key are inferred from the pooled notes of every track
253
- * (see {@link chordTimelineFromNotes}); pooling all voices is robust even when
254
- * roles are absent or a track doubles the harmony, so it is preferred over
255
- * deriving the harmony from a subset. Zero- and negative-length notes never
256
- * sound, so they are dropped at ingest and appear in neither the annotations
257
- * nor the conflicts.
382
+ * The chord timeline and key are inferred from the pooled notes of every
383
+ * pitched track (see {@link chordTimelineFromNotes}); pooling all voices is
384
+ * robust even when roles are absent or a track doubles the harmony, so it is
385
+ * preferred over deriving the harmony from a subset. A track marked
386
+ * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and
387
+ * pooling them would corrupt the key, the timeline and every reading built on
388
+ * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to
389
+ * analyse against a progression you already have. Zero- and negative-length
390
+ * notes never sound, so they are dropped at ingest and appear in neither the
391
+ * annotations nor the conflicts.
258
392
  *
259
393
  * Voice analysis assumes one monophonic voice at a time, so each track is
260
394
  * first partitioned into monophonic sub-voices (a polyphonic block-chord track
@@ -267,6 +401,13 @@ type ArrangementOptions = {
267
401
  * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
268
402
  * clashing beat, sorted worst severity first and then by beat.
269
403
  *
404
+ * Every ordinary non-chord tone — a passing note, a neighbour, a prepared
405
+ * suspension — is by definition unsafe against the chord under it, so the
406
+ * conflict list is a report, not a fault list. Each conflict carries the note's
407
+ * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those
408
+ * apart and map a conflict back to the note it passed in; `minSeverity` narrows
409
+ * the list to outright dissonance.
410
+ *
270
411
  * @param tracks The tracks to analyse.
271
412
  * @param opts Analysis options; see {@link ArrangementOptions}.
272
413
  * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
@@ -306,12 +447,12 @@ type TensionPoint = {
306
447
  *
307
448
  * At each sample beat the tension combines three normalized terms:
308
449
  * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
309
- * tonic or no chord `0`), weighted {@link FUNCTION_WEIGHT};
450
+ * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;
310
451
  * - the dissonance of the sounding notes — the larger of the share of sounding
311
452
  * pitches that are not chord tones and the share that {@link evaluateSafety}
312
- * rates {@link NoteSafety.Dissonant} — weighted {@link DISSONANCE_WEIGHT};
453
+ * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;
313
454
  * - the registral span of the sounding notes, saturating at
314
- * {@link SPAN_SATURATION} semitones, weighted {@link SPAN_WEIGHT}.
455
+ * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.
315
456
  *
316
457
  * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
317
458
  * pooled together, so the result is deterministic and self-contained.
@@ -332,98 +473,29 @@ type TensionPoint = {
332
473
  declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
333
474
  step?: number;
334
475
  }): TensionPoint[];
335
-
336
- /**
337
- * Recognition: infer a chord name from a set of pitches, or a key from a set of
338
- * pitch classes. This is the inverse direction of the chord/scale builders,
339
- * which only go name -> notes.
340
- */
341
-
342
- /**
343
- * A candidate chord interpretation of a pitch set.
344
- *
345
- * @category Recognition
346
- */
347
- type ChordMatch = {
348
- rootPc: number;
349
- quality: ChordQuality;
350
- /** Chord tones absent from the input (an incomplete voicing). */
351
- missingPcs: number[];
352
- /** Input pitch classes not belonging to the chord. */
353
- extraPcs: number[];
354
- /** True when the input pitch-class set equals the chord exactly. */
355
- exact: boolean;
356
- /** Inversion implied by the lowest note: 0 root position, 1 first, ... */
357
- inversion: number;
358
- /** Bass pitch class when inverted (the lowest note is not the root). */
359
- bassPc?: number;
360
- };
361
- /**
362
- * A candidate key interpretation of a pitch-class set.
363
- *
364
- * @category Recognition
365
- */
366
- type KeyMatch = {
367
- key: KeyScale;
368
- mode: 'major' | 'minor';
369
- /** Fraction of input pitch classes that are in the scale, in [0, 1]. */
370
- fit: number;
371
- };
372
476
  /**
373
- * Identify chords matching a set of pitches.
477
+ * Sample the tension of an arrangement already analysed by
478
+ * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.
374
479
  *
375
- * Every input pitch class is tried as a root against every known chord quality.
376
- * A match is reported when all of the chord's tones are present in the input, or
377
- * when the only absent tone is the perfect fifth (a common omission in shell
378
- * voicings); matches are ranked best-first by fewest extra notes, then fewest
379
- * missing notes, then most specific (largest) chord. An exact match (no extras,
380
- * no missing) is flagged and ranked first.
480
+ * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference
481
+ * twice over the same notes. This takes the analysis it already produced, so
482
+ * the curve and the annotations describe the same harmony by construction.
381
483
  *
382
- * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).
383
- * @returns Ranked chord interpretations (may be empty).
384
- * @example
385
- * ```ts
386
- * import { detectChord } from '@libraz/libcantus';
387
- * const matches = detectChord([60, 64, 67]); // C E G
388
- * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }
389
- * ```
390
- * @category Recognition
391
- */
392
- declare function detectChord(pitches: number[]): ChordMatch[];
393
- /**
394
- * The single best chord interpretation of a pitch set, or null if none.
395
- *
396
- * @param pitches MIDI pitches or bare pitch classes.
397
- * @returns The top-ranked chord, or null when nothing matches.
398
- * @example
399
- * ```ts
400
- * import { detectChordBest } from '@libraz/libcantus';
401
- * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }
402
- * ```
403
- * @category Recognition
404
- */
405
- declare function detectChordBest(pitches: number[]): Chord | null;
406
- /**
407
- * Rank major and minor keys by how well they contain a set of pitch classes.
408
- *
409
- * The tonic is weighted so that, among equally-fitting keys, the one whose root
410
- * appears in the input is preferred. Minor candidates are scored against the
411
- * natural, harmonic, and melodic minor variants and keep the best of the three,
412
- * so a minor cadence containing the leading tone still resolves to its own
413
- * tonic; the returned key is always the natural-minor scale. Returns all 24
414
- * keys ranked best-first, or an empty array for an empty input (mirroring
415
- * {@link detectChord}).
416
- *
417
- * @param pitches MIDI pitches or bare pitch classes.
418
- * @returns Ranked key interpretations (empty for an empty input).
484
+ * @param tracks The same tracks that were analysed.
485
+ * @param analysis The result of {@link analyzeArrangement} for those tracks.
486
+ * @param opts Analysis options plus an optional `step` (default one beat).
487
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
419
488
  * @example
420
489
  * ```ts
421
- * import { detectKey } from '@libraz/libcantus';
422
- * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale
423
- * keys[0].mode; // 'major', with keys[0].key.rootPc === 0
490
+ * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';
491
+ * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];
492
+ * const analysis = analyzeArrangement(tracks);
493
+ * tensionCurveFrom(tracks, analysis, { step: 1 });
424
494
  * ```
425
- * @category Recognition
495
+ * @category Arrangement & Analysis
426
496
  */
427
- declare function detectKey(pitches: number[]): KeyMatch[];
497
+ declare function tensionCurveFrom(tracks: ArrangementTrack[], analysis: ArrangementAnalysis, opts?: ArrangementOptions & {
498
+ step?: number;
499
+ }): TensionPoint[];
428
500
 
429
- export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, type ChordMatch, ChordTimeline, type Conflict, type KeyMatch, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, detectChord, detectChordBest, detectKey, romanToChord, tensionCurve };
501
+ export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, Chord as ChordData, ChordTimeline, type ChordToRomanOptions, 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, chordToRoman, romanToChord, tensionCurve, tensionCurveFrom, toVoiceNotes };
@@ -4,9 +4,11 @@ import {
4
4
  chordTimelineFromChords,
5
5
  chordTimelineFromNotes,
6
6
  detectCadences,
7
- tensionCurve
8
- } from "../chunk-5HTNMQW4.js";
9
- import "../chunk-MSWFZF7J.js";
7
+ tensionCurve,
8
+ tensionCurveFrom,
9
+ toVoiceNotes
10
+ } from "../chunk-Q2XUP227.js";
11
+ import "../chunk-YLEMGEFH.js";
10
12
  import {
11
13
  analyzeChord,
12
14
  borrowedSource,
@@ -15,6 +17,8 @@ import {
15
17
  detectChord,
16
18
  detectChordBest,
17
19
  detectKey,
20
+ detectKeyBest,
21
+ detectKeyFromNotes,
18
22
  functionOf,
19
23
  isBorrowedChord,
20
24
  isDiatonic,
@@ -22,11 +26,14 @@ import {
22
26
  parallelKey,
23
27
  romanToChord,
24
28
  secondaryDominant
25
- } from "../chunk-LN7ZZMD2.js";
26
- import "../chunk-A4532ZFR.js";
27
- import "../chunk-7Y7WF64D.js";
28
- import "../chunk-CCBLVAPK.js";
29
+ } from "../chunk-KMHOICIY.js";
30
+ import {
31
+ NoteSafety
32
+ } from "../chunk-NYYWMV2C.js";
33
+ import "../chunk-D2FAJBYL.js";
34
+ import "../chunk-XEBG3G53.js";
29
35
  export {
36
+ NoteSafety,
30
37
  analyzeArrangement,
31
38
  analyzeChord,
32
39
  analyzeVoice,
@@ -39,6 +46,8 @@ export {
39
46
  detectChord,
40
47
  detectChordBest,
41
48
  detectKey,
49
+ detectKeyBest,
50
+ detectKeyFromNotes,
42
51
  functionOf,
43
52
  isBorrowedChord,
44
53
  isDiatonic,
@@ -46,6 +55,8 @@ export {
46
55
  parallelKey,
47
56
  romanToChord,
48
57
  secondaryDominant,
49
- tensionCurve
58
+ tensionCurve,
59
+ tensionCurveFrom,
60
+ toVoiceNotes
50
61
  };
51
62
  //# sourceMappingURL=index.js.map