@libraz/libcantus 0.9.1 → 0.9.3

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 (160) hide show
  1. package/README.md +19 -3
  2. package/dist/analyze/index.cjs +1970 -0
  3. package/dist/analyze/index.cjs.map +1 -0
  4. package/dist/analyze/index.d.cts +341 -0
  5. package/dist/analyze/index.d.ts +341 -0
  6. package/dist/analyze/index.js +51 -0
  7. package/dist/analyze/index.js.map +1 -0
  8. package/dist/chunk-2ZWMSJH6.js +30 -0
  9. package/dist/chunk-2ZWMSJH6.js.map +1 -0
  10. package/dist/chunk-4MHA432G.js +2854 -0
  11. package/dist/chunk-4MHA432G.js.map +1 -0
  12. package/dist/chunk-62QZKCJQ.js +49 -0
  13. package/dist/chunk-62QZKCJQ.js.map +1 -0
  14. package/dist/chunk-AKLYPWIN.js +83 -0
  15. package/dist/chunk-AKLYPWIN.js.map +1 -0
  16. package/dist/chunk-C5XYKUVG.js +904 -0
  17. package/dist/chunk-C5XYKUVG.js.map +1 -0
  18. package/dist/chunk-CHMXELDS.js +549 -0
  19. package/dist/chunk-CHMXELDS.js.map +1 -0
  20. package/dist/chunk-DYWFNC4A.js +152 -0
  21. package/dist/chunk-DYWFNC4A.js.map +1 -0
  22. package/dist/chunk-EH6EM37U.js +315 -0
  23. package/dist/chunk-EH6EM37U.js.map +1 -0
  24. package/dist/chunk-FBQK3ZQN.js +131 -0
  25. package/dist/chunk-FBQK3ZQN.js.map +1 -0
  26. package/dist/chunk-GOAW4VYL.js +719 -0
  27. package/dist/chunk-GOAW4VYL.js.map +1 -0
  28. package/dist/chunk-I5R7ZBJS.js +202 -0
  29. package/dist/chunk-I5R7ZBJS.js.map +1 -0
  30. package/dist/chunk-IT2O4XFE.js +154 -0
  31. package/dist/chunk-IT2O4XFE.js.map +1 -0
  32. package/dist/chunk-MIWO425M.js +412 -0
  33. package/dist/chunk-MIWO425M.js.map +1 -0
  34. package/dist/chunk-N6IHQUOX.js +1 -0
  35. package/dist/chunk-N6IHQUOX.js.map +1 -0
  36. package/dist/chunk-SGU7SPLK.js +924 -0
  37. package/dist/chunk-SGU7SPLK.js.map +1 -0
  38. package/dist/core/index.cjs +648 -0
  39. package/dist/core/index.cjs.map +1 -0
  40. package/dist/core/index.d.cts +262 -0
  41. package/dist/core/index.d.ts +262 -0
  42. package/dist/core/index.js +93 -0
  43. package/dist/core/index.js.map +1 -0
  44. package/dist/function-DVH4Eusr.d.ts +187 -0
  45. package/dist/function-DYt4iKsn.d.cts +187 -0
  46. package/dist/generate/index.cjs +4123 -0
  47. package/dist/generate/index.cjs.map +1 -0
  48. package/dist/generate/index.d.cts +986 -0
  49. package/dist/generate/index.d.ts +986 -0
  50. package/dist/generate/index.js +52 -0
  51. package/dist/generate/index.js.map +1 -0
  52. package/dist/index-BNlGP86A.d.cts +157 -0
  53. package/dist/index-Bg5dYMmZ.d.ts +56 -0
  54. package/dist/index-Bt4tOUD9.d.ts +157 -0
  55. package/dist/index-CGMZNCx6.d.cts +140 -0
  56. package/dist/index-CGMZNCx6.d.ts +140 -0
  57. package/dist/index-Cl7mhOFM.d.cts +164 -0
  58. package/dist/index-Cl7mhOFM.d.ts +164 -0
  59. package/dist/index-CnZCo29u.d.cts +108 -0
  60. package/dist/index-CpOsP1y9.d.cts +151 -0
  61. package/dist/index-CxqBQtH1.d.cts +128 -0
  62. package/dist/index-Dikst44v.d.cts +56 -0
  63. package/dist/index-DxvARIP2.d.ts +151 -0
  64. package/dist/index-Jc0Ryi79.d.ts +128 -0
  65. package/dist/index-JnF4v9bx.d.ts +108 -0
  66. package/dist/index.cjs +3710 -3143
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.cts +15 -3982
  69. package/dist/index.d.ts +15 -3982
  70. package/dist/index.js +191 -6504
  71. package/dist/index.js.map +1 -1
  72. package/dist/model/index.cjs +2574 -0
  73. package/dist/model/index.cjs.map +1 -0
  74. package/dist/model/index.d.cts +631 -0
  75. package/dist/model/index.d.ts +631 -0
  76. package/dist/model/index.js +21 -0
  77. package/dist/model/index.js.map +1 -0
  78. package/dist/styled-DZ7GjShb.d.ts +282 -0
  79. package/dist/styled-ZTbdaVFM.d.cts +282 -0
  80. package/dist/theory/index.cjs +1823 -0
  81. package/dist/theory/index.cjs.map +1 -0
  82. package/dist/theory/index.d.cts +535 -0
  83. package/dist/theory/index.d.ts +535 -0
  84. package/dist/theory/index.js +150 -0
  85. package/dist/theory/index.js.map +1 -0
  86. package/dist/types-MGP29r3t.d.cts +30 -0
  87. package/dist/types-MGP29r3t.d.ts +30 -0
  88. package/package.json +61 -5
  89. package/src/analyze/arrange/index.ts +21 -0
  90. package/src/analyze/arrange/internal.ts +127 -0
  91. package/src/analyze/arrange/tension.ts +199 -0
  92. package/src/analyze/arrange/tracks.ts +312 -0
  93. package/src/{detect → analyze/detect}/index.ts +50 -13
  94. package/src/analyze/functional/borrowed.ts +84 -0
  95. package/src/analyze/functional/cadence.ts +66 -0
  96. package/src/analyze/functional/function.ts +184 -0
  97. package/src/analyze/functional/index.ts +23 -0
  98. package/src/analyze/functional/internal.ts +29 -0
  99. package/src/{functional/index.ts → analyze/functional/roman.ts} +23 -332
  100. package/src/analyze/index.ts +49 -0
  101. package/src/{timeline → analyze/timeline}/index.ts +50 -18
  102. package/src/{analysis → analyze/voice}/index.ts +19 -7
  103. package/src/core/event-index/index.ts +104 -0
  104. package/src/core/index.ts +61 -0
  105. package/src/{meter → core/meter}/index.ts +24 -4
  106. package/src/{random → core/random}/index.ts +26 -3
  107. package/src/{tuning → core/tuning}/index.ts +36 -6
  108. package/src/core/validation/index.ts +119 -0
  109. package/src/{bass → generate/bass}/index.ts +28 -5
  110. package/src/{countermelody → generate/countermelody}/index.ts +176 -85
  111. package/src/{drums → generate/drums}/euclid.ts +10 -3
  112. package/src/{drums → generate/drums}/index.ts +58 -26
  113. package/src/{drums → generate/drums}/percussion.ts +1 -1
  114. package/src/{drums → generate/drums}/rng.ts +2 -2
  115. package/src/{groove → generate/groove}/index.ts +28 -3
  116. package/src/{harmonize → generate/harmonize}/index.ts +68 -57
  117. package/src/generate/index.ts +58 -0
  118. package/src/{motif → generate/motif}/index.ts +52 -28
  119. package/src/{progression → generate/progression}/index.ts +19 -41
  120. package/src/{reharmony → generate/reharmony}/index.ts +12 -19
  121. package/src/{rhythm → generate/rhythm}/index.ts +13 -3
  122. package/src/index.ts +13 -234
  123. package/src/model/chord.ts +442 -0
  124. package/src/model/index.ts +5 -1096
  125. package/src/model/interval.ts +82 -0
  126. package/src/model/key.ts +266 -0
  127. package/src/model/note.ts +176 -0
  128. package/src/model/progression.ts +161 -0
  129. package/src/model/shared.ts +12 -0
  130. package/src/{chord → theory/chord}/index.ts +23 -3
  131. package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
  132. package/src/{harmony → theory/harmony}/index.ts +6 -21
  133. package/src/theory/index.ts +112 -0
  134. package/src/{safety → theory/safety}/index.ts +72 -3
  135. package/src/theory/scale/degrees.ts +123 -0
  136. package/src/theory/scale/index.ts +28 -0
  137. package/src/theory/scale/key.ts +56 -0
  138. package/src/theory/scale/masks.ts +153 -0
  139. package/src/{spelling → theory/spelling}/index.ts +73 -6
  140. package/src/{symbol → theory/symbol}/index.ts +8 -1
  141. package/src/theory/voicing/index.ts +5 -0
  142. package/src/theory/voicing/internal.ts +168 -0
  143. package/src/theory/voicing/leading.ts +112 -0
  144. package/src/theory/voicing/satb.ts +216 -0
  145. package/src/theory/voicing/styled.ts +199 -0
  146. package/src/arrange/index.ts +0 -571
  147. package/src/scale/index.ts +0 -331
  148. package/src/voicing/index.ts +0 -623
  149. /package/src/{interval → core/interval}/index.ts +0 -0
  150. /package/src/{pitch → core/pitch}/index.ts +0 -0
  151. /package/src/{types.ts → core/types.ts} +0 -0
  152. /package/src/{drums → generate/drums}/beat.ts +0 -0
  153. /package/src/{drums → generate/drums}/fills.ts +0 -0
  154. /package/src/{drums → generate/drums}/ghost.ts +0 -0
  155. /package/src/{drums → generate/drums}/hihat.ts +0 -0
  156. /package/src/{drums → generate/drums}/hit.ts +0 -0
  157. /package/src/{drums → generate/drums}/internal.ts +0 -0
  158. /package/src/{drums → generate/drums}/kick.ts +0 -0
  159. /package/src/{drums → generate/drums}/swing.ts +0 -0
  160. /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
@@ -0,0 +1,341 @@
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';
9
+
10
+ /**
11
+ * Roman-numeral parsing and formatting.
12
+ *
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
70
+ */
71
+ declare function chordToRoman(chord: Chord, key: KeyScale): string;
72
+
73
+ /**
74
+ * A theory annotation attached to a note.
75
+ *
76
+ * @category Arrangement & Analysis
77
+ */
78
+ type TheoryLabel = {
79
+ kind: 'chordTone';
80
+ role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
81
+ } | {
82
+ kind: 'tension';
83
+ degree: 9 | 11 | 13;
84
+ } | {
85
+ kind: 'avoid';
86
+ } | {
87
+ kind: 'passing';
88
+ } | {
89
+ kind: 'neighbor';
90
+ } | {
91
+ kind: 'suspension';
92
+ type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';
93
+ resolveTo: number;
94
+ } | {
95
+ kind: 'anticipation';
96
+ } | {
97
+ kind: 'escape';
98
+ } | {
99
+ kind: 'needsResolution';
100
+ resolveTo: number;
101
+ } | {
102
+ kind: 'leadingTone';
103
+ resolveTo: number;
104
+ };
105
+ /**
106
+ * A note with its theory labels and a short rationale.
107
+ *
108
+ * @category Arrangement & Analysis
109
+ */
110
+ type AnalyzedNote = {
111
+ noteId: number;
112
+ labels: TheoryLabel[];
113
+ rationale?: string;
114
+ };
115
+ /**
116
+ * A single note in a monophonic voice: a {@link NoteEvent} with a stable id.
117
+ *
118
+ * @category Arrangement & Analysis
119
+ */
120
+ type VoiceNote = NoteEvent & {
121
+ id: number;
122
+ };
123
+ /**
124
+ * Label every note of a voice with its theory roles.
125
+ *
126
+ * Each note is classified against the chord sounding at its beat. Chord tones
127
+ * get a role label; non-chord tones are matched, in order, as suspensions
128
+ * (prepared by an identical consonant pitch and resolving by step), passing
129
+ * tones, neighbors, anticipations, and escape tones, then fall back to tension,
130
+ * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted
131
+ * additionally.
132
+ *
133
+ * The voice is expected to be monophonic (one note at a time). Notes sharing an
134
+ * onset are treated as simultaneous cluster members, not melodic neighbors, so
135
+ * they receive no melodic labels (suspension, passing, neighbor, anticipation,
136
+ * escape) — only harmonic ones. Callers with truly polyphonic material should
137
+ * split it into monophonic sub-voices first, as `analyzeArrangement` does.
138
+ *
139
+ * @param voice The monophonic voice, in time order.
140
+ * @param chordAtBeat Chord sounding at a given beat, or null.
141
+ * @param key Key context for leading-tone detection.
142
+ * @param otherVoicesAtBeat Other sounding voices at a given beat.
143
+ * @returns One annotation per input note.
144
+ * @example
145
+ * ```ts
146
+ * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
147
+ * 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 },
151
+ * ];
152
+ * const cMajor = makeChord(0, 'maj');
153
+ * const labels = analyzeVoice(voice, () => cMajor, majorKey(0), () => []);
154
+ * labels; // one AnalyzedNote per input note, in the same order
155
+ * ```
156
+ * @category Arrangement & Analysis
157
+ */
158
+ declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
159
+
160
+ /**
161
+ * Arrangement track-role analysis: a single harmony is inferred from every
162
+ * track's notes pooled together, each track is then analysed against that shared
163
+ * harmony, and notes that clash with the sounding chord are collected as
164
+ * conflicts.
165
+ */
166
+
167
+ /**
168
+ * The musical role a track plays in the arrangement.
169
+ *
170
+ * @category Arrangement & Analysis
171
+ */
172
+ type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
173
+ /**
174
+ * One input track: its notes plus optional name and role.
175
+ *
176
+ * @category Arrangement & Analysis
177
+ */
178
+ type ArrangementTrack = {
179
+ name?: string;
180
+ role?: TrackRole;
181
+ notes: NoteEvent[];
182
+ };
183
+ /**
184
+ * A track after analysis: its resolved name, role, and per-note annotations.
185
+ *
186
+ * @category Arrangement & Analysis
187
+ */
188
+ type TrackAnalysis = {
189
+ name: string;
190
+ role: TrackRole;
191
+ /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
192
+ notes: AnalyzedNote[];
193
+ };
194
+ /**
195
+ * A note that clashes with the harmony sounding beneath it.
196
+ *
197
+ * @category Arrangement & Analysis
198
+ */
199
+ type Conflict = {
200
+ beat: number;
201
+ trackName: string;
202
+ pitch: number;
203
+ /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
204
+ safety: NoteSafety;
205
+ /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
206
+ reasons: number;
207
+ /** Preferred stepwise resolution, when the safety evaluator provides one. */
208
+ resolveTo?: number;
209
+ /** Nearby fully safe replacement pitches, nearest first. */
210
+ suggestions?: number[];
211
+ rationale?: string;
212
+ };
213
+ /**
214
+ * The full result of {@link analyzeArrangement}.
215
+ *
216
+ * @category Arrangement & Analysis
217
+ */
218
+ type ArrangementAnalysis = {
219
+ key: KeyScale;
220
+ timeline: ChordTimeline;
221
+ segmentConfidence: number[];
222
+ cadences: CadenceHit[];
223
+ tracks: TrackAnalysis[];
224
+ /** Notes clashing with the sounding harmony, worst severity first. */
225
+ conflicts: Conflict[];
226
+ };
227
+ /**
228
+ * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.
229
+ *
230
+ * @category Arrangement & Analysis
231
+ */
232
+ type ArrangementOptions = {
233
+ /** Key context; inferred from the pooled notes when omitted. */
234
+ key?: KeyScale;
235
+ /**
236
+ * Time signature; defaults to 4/4.
237
+ *
238
+ * @defaultValue `4/4`
239
+ */
240
+ ts?: TimeSignature;
241
+ /**
242
+ * Chord-slot length in beats; defaults to one bar of `ts`.
243
+ *
244
+ * @defaultValue one bar of `ts`
245
+ */
246
+ harmonicRhythm?: number;
247
+ /**
248
+ * Safety profile used for conflict detection; defaults to `pop`.
249
+ *
250
+ * @defaultValue `'pop'`
251
+ */
252
+ profile?: SafetyProfile;
253
+ };
254
+ /**
255
+ * Analyse a whole arrangement against a single inferred harmony.
256
+ *
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.
263
+ *
264
+ * Voice analysis assumes one monophonic voice at a time, so each track is
265
+ * first partitioned into monophonic sub-voices (a polyphonic block-chord track
266
+ * fans out into one sub-voice per simultaneous note; a monophonic track is
267
+ * unaffected). Each sub-voice is labelled note-by-note against the harmony
268
+ * with {@link analyzeVoice}, seeing every other sub-voice — including siblings
269
+ * within the same track — as its accompaniment. Every note is then re-checked
270
+ * with {@link evaluateSafety} at its onset and again at each chord change it
271
+ * sustains across, so a held note that clashes with a later chord is caught:
272
+ * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
273
+ * clashing beat, sorted worst severity first and then by beat.
274
+ *
275
+ * @param tracks The tracks to analyse.
276
+ * @param opts Analysis options; see {@link ArrangementOptions}.
277
+ * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
278
+ * @example
279
+ * ```ts
280
+ * import { analyzeArrangement } from '@libraz/libcantus';
281
+ * const melody = [
282
+ * { pitch: 60, startBeat: 0, durationBeat: 2 },
283
+ * { pitch: 67, startBeat: 2, durationBeat: 2 },
284
+ * ];
285
+ * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
286
+ * conflicts; // notes clashing with the inferred harmony, worst severity first
287
+ * ```
288
+ * @category Arrangement & Analysis
289
+ */
290
+ declare function analyzeArrangement(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementAnalysis;
291
+
292
+ /**
293
+ * Arrangement tension analysis: a coarse harmonic-tension curve sampled at
294
+ * regular beats across a whole multi-track piece, combining the sounding
295
+ * chord's harmonic function, the dissonance of the sounding notes, and their
296
+ * registral span into a single normalized reading.
297
+ */
298
+
299
+ /**
300
+ * A tension reading sampled at a beat.
301
+ *
302
+ * @category Arrangement & Analysis
303
+ */
304
+ type TensionPoint = {
305
+ beat: number;
306
+ /** Combined tension in [0, 1]. */
307
+ tension: number;
308
+ };
309
+ /**
310
+ * Sample the harmonic tension of an arrangement at regular beats.
311
+ *
312
+ * At each sample beat the tension combines three normalized terms:
313
+ * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
314
+ * tonic or no chord `0`), weighted {@link FUNCTION_WEIGHT};
315
+ * - the dissonance of the sounding notes — the larger of the share of sounding
316
+ * pitches that are not chord tones and the share that {@link evaluateSafety}
317
+ * rates {@link NoteSafety.Dissonant} — weighted {@link DISSONANCE_WEIGHT};
318
+ * - the registral span of the sounding notes, saturating at
319
+ * {@link SPAN_SATURATION} semitones, weighted {@link SPAN_WEIGHT}.
320
+ *
321
+ * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
322
+ * pooled together, so the result is deterministic and self-contained.
323
+ *
324
+ * @param tracks The tracks to sample.
325
+ * @param opts Analysis options plus an optional `step` (default one beat).
326
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
327
+ * @throws If `step` is not positive.
328
+ * @example
329
+ * ```ts
330
+ * import { tensionCurve } from '@libraz/libcantus';
331
+ * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
332
+ * const curve = tensionCurve([{ notes }], { step: 1 });
333
+ * curve; // one { beat, tension } sample per beat, in beat order
334
+ * ```
335
+ * @category Arrangement & Analysis
336
+ */
337
+ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
338
+ step?: number;
339
+ }): TensionPoint[];
340
+
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 };
@@ -0,0 +1,341 @@
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-Jc0Ryi79.js';
4
+ import { b as ChordTimeline, C as CadenceHit } from '../index-DxvARIP2.js';
5
+ export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-DxvARIP2.js';
6
+ import { C as Chord } from '../index-Bt4tOUD9.js';
7
+ export { C as ChordMatch, D as DetectChordOptions, K as KeyMatch, d as detectChord, a as detectChordBest, b as detectKey } from '../index-JnF4v9bx.js';
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-DVH4Eusr.js';
9
+
10
+ /**
11
+ * Roman-numeral parsing and formatting.
12
+ *
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
70
+ */
71
+ declare function chordToRoman(chord: Chord, key: KeyScale): string;
72
+
73
+ /**
74
+ * A theory annotation attached to a note.
75
+ *
76
+ * @category Arrangement & Analysis
77
+ */
78
+ type TheoryLabel = {
79
+ kind: 'chordTone';
80
+ role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
81
+ } | {
82
+ kind: 'tension';
83
+ degree: 9 | 11 | 13;
84
+ } | {
85
+ kind: 'avoid';
86
+ } | {
87
+ kind: 'passing';
88
+ } | {
89
+ kind: 'neighbor';
90
+ } | {
91
+ kind: 'suspension';
92
+ type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';
93
+ resolveTo: number;
94
+ } | {
95
+ kind: 'anticipation';
96
+ } | {
97
+ kind: 'escape';
98
+ } | {
99
+ kind: 'needsResolution';
100
+ resolveTo: number;
101
+ } | {
102
+ kind: 'leadingTone';
103
+ resolveTo: number;
104
+ };
105
+ /**
106
+ * A note with its theory labels and a short rationale.
107
+ *
108
+ * @category Arrangement & Analysis
109
+ */
110
+ type AnalyzedNote = {
111
+ noteId: number;
112
+ labels: TheoryLabel[];
113
+ rationale?: string;
114
+ };
115
+ /**
116
+ * A single note in a monophonic voice: a {@link NoteEvent} with a stable id.
117
+ *
118
+ * @category Arrangement & Analysis
119
+ */
120
+ type VoiceNote = NoteEvent & {
121
+ id: number;
122
+ };
123
+ /**
124
+ * Label every note of a voice with its theory roles.
125
+ *
126
+ * Each note is classified against the chord sounding at its beat. Chord tones
127
+ * get a role label; non-chord tones are matched, in order, as suspensions
128
+ * (prepared by an identical consonant pitch and resolving by step), passing
129
+ * tones, neighbors, anticipations, and escape tones, then fall back to tension,
130
+ * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted
131
+ * additionally.
132
+ *
133
+ * The voice is expected to be monophonic (one note at a time). Notes sharing an
134
+ * onset are treated as simultaneous cluster members, not melodic neighbors, so
135
+ * they receive no melodic labels (suspension, passing, neighbor, anticipation,
136
+ * escape) — only harmonic ones. Callers with truly polyphonic material should
137
+ * split it into monophonic sub-voices first, as `analyzeArrangement` does.
138
+ *
139
+ * @param voice The monophonic voice, in time order.
140
+ * @param chordAtBeat Chord sounding at a given beat, or null.
141
+ * @param key Key context for leading-tone detection.
142
+ * @param otherVoicesAtBeat Other sounding voices at a given beat.
143
+ * @returns One annotation per input note.
144
+ * @example
145
+ * ```ts
146
+ * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
147
+ * 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 },
151
+ * ];
152
+ * const cMajor = makeChord(0, 'maj');
153
+ * const labels = analyzeVoice(voice, () => cMajor, majorKey(0), () => []);
154
+ * labels; // one AnalyzedNote per input note, in the same order
155
+ * ```
156
+ * @category Arrangement & Analysis
157
+ */
158
+ declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
159
+
160
+ /**
161
+ * Arrangement track-role analysis: a single harmony is inferred from every
162
+ * track's notes pooled together, each track is then analysed against that shared
163
+ * harmony, and notes that clash with the sounding chord are collected as
164
+ * conflicts.
165
+ */
166
+
167
+ /**
168
+ * The musical role a track plays in the arrangement.
169
+ *
170
+ * @category Arrangement & Analysis
171
+ */
172
+ type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
173
+ /**
174
+ * One input track: its notes plus optional name and role.
175
+ *
176
+ * @category Arrangement & Analysis
177
+ */
178
+ type ArrangementTrack = {
179
+ name?: string;
180
+ role?: TrackRole;
181
+ notes: NoteEvent[];
182
+ };
183
+ /**
184
+ * A track after analysis: its resolved name, role, and per-note annotations.
185
+ *
186
+ * @category Arrangement & Analysis
187
+ */
188
+ type TrackAnalysis = {
189
+ name: string;
190
+ role: TrackRole;
191
+ /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
192
+ notes: AnalyzedNote[];
193
+ };
194
+ /**
195
+ * A note that clashes with the harmony sounding beneath it.
196
+ *
197
+ * @category Arrangement & Analysis
198
+ */
199
+ type Conflict = {
200
+ beat: number;
201
+ trackName: string;
202
+ pitch: number;
203
+ /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
204
+ safety: NoteSafety;
205
+ /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
206
+ reasons: number;
207
+ /** Preferred stepwise resolution, when the safety evaluator provides one. */
208
+ resolveTo?: number;
209
+ /** Nearby fully safe replacement pitches, nearest first. */
210
+ suggestions?: number[];
211
+ rationale?: string;
212
+ };
213
+ /**
214
+ * The full result of {@link analyzeArrangement}.
215
+ *
216
+ * @category Arrangement & Analysis
217
+ */
218
+ type ArrangementAnalysis = {
219
+ key: KeyScale;
220
+ timeline: ChordTimeline;
221
+ segmentConfidence: number[];
222
+ cadences: CadenceHit[];
223
+ tracks: TrackAnalysis[];
224
+ /** Notes clashing with the sounding harmony, worst severity first. */
225
+ conflicts: Conflict[];
226
+ };
227
+ /**
228
+ * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.
229
+ *
230
+ * @category Arrangement & Analysis
231
+ */
232
+ type ArrangementOptions = {
233
+ /** Key context; inferred from the pooled notes when omitted. */
234
+ key?: KeyScale;
235
+ /**
236
+ * Time signature; defaults to 4/4.
237
+ *
238
+ * @defaultValue `4/4`
239
+ */
240
+ ts?: TimeSignature;
241
+ /**
242
+ * Chord-slot length in beats; defaults to one bar of `ts`.
243
+ *
244
+ * @defaultValue one bar of `ts`
245
+ */
246
+ harmonicRhythm?: number;
247
+ /**
248
+ * Safety profile used for conflict detection; defaults to `pop`.
249
+ *
250
+ * @defaultValue `'pop'`
251
+ */
252
+ profile?: SafetyProfile;
253
+ };
254
+ /**
255
+ * Analyse a whole arrangement against a single inferred harmony.
256
+ *
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.
263
+ *
264
+ * Voice analysis assumes one monophonic voice at a time, so each track is
265
+ * first partitioned into monophonic sub-voices (a polyphonic block-chord track
266
+ * fans out into one sub-voice per simultaneous note; a monophonic track is
267
+ * unaffected). Each sub-voice is labelled note-by-note against the harmony
268
+ * with {@link analyzeVoice}, seeing every other sub-voice — including siblings
269
+ * within the same track — as its accompaniment. Every note is then re-checked
270
+ * with {@link evaluateSafety} at its onset and again at each chord change it
271
+ * sustains across, so a held note that clashes with a later chord is caught:
272
+ * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
273
+ * clashing beat, sorted worst severity first and then by beat.
274
+ *
275
+ * @param tracks The tracks to analyse.
276
+ * @param opts Analysis options; see {@link ArrangementOptions}.
277
+ * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
278
+ * @example
279
+ * ```ts
280
+ * import { analyzeArrangement } from '@libraz/libcantus';
281
+ * const melody = [
282
+ * { pitch: 60, startBeat: 0, durationBeat: 2 },
283
+ * { pitch: 67, startBeat: 2, durationBeat: 2 },
284
+ * ];
285
+ * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
286
+ * conflicts; // notes clashing with the inferred harmony, worst severity first
287
+ * ```
288
+ * @category Arrangement & Analysis
289
+ */
290
+ declare function analyzeArrangement(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementAnalysis;
291
+
292
+ /**
293
+ * Arrangement tension analysis: a coarse harmonic-tension curve sampled at
294
+ * regular beats across a whole multi-track piece, combining the sounding
295
+ * chord's harmonic function, the dissonance of the sounding notes, and their
296
+ * registral span into a single normalized reading.
297
+ */
298
+
299
+ /**
300
+ * A tension reading sampled at a beat.
301
+ *
302
+ * @category Arrangement & Analysis
303
+ */
304
+ type TensionPoint = {
305
+ beat: number;
306
+ /** Combined tension in [0, 1]. */
307
+ tension: number;
308
+ };
309
+ /**
310
+ * Sample the harmonic tension of an arrangement at regular beats.
311
+ *
312
+ * At each sample beat the tension combines three normalized terms:
313
+ * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
314
+ * tonic or no chord `0`), weighted {@link FUNCTION_WEIGHT};
315
+ * - the dissonance of the sounding notes — the larger of the share of sounding
316
+ * pitches that are not chord tones and the share that {@link evaluateSafety}
317
+ * rates {@link NoteSafety.Dissonant} — weighted {@link DISSONANCE_WEIGHT};
318
+ * - the registral span of the sounding notes, saturating at
319
+ * {@link SPAN_SATURATION} semitones, weighted {@link SPAN_WEIGHT}.
320
+ *
321
+ * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
322
+ * pooled together, so the result is deterministic and self-contained.
323
+ *
324
+ * @param tracks The tracks to sample.
325
+ * @param opts Analysis options plus an optional `step` (default one beat).
326
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
327
+ * @throws If `step` is not positive.
328
+ * @example
329
+ * ```ts
330
+ * import { tensionCurve } from '@libraz/libcantus';
331
+ * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
332
+ * const curve = tensionCurve([{ notes }], { step: 1 });
333
+ * curve; // one { beat, tension } sample per beat, in beat order
334
+ * ```
335
+ * @category Arrangement & Analysis
336
+ */
337
+ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
338
+ step?: number;
339
+ }): TensionPoint[];
340
+
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 };