@libraz/libcantus 0.9.0 → 0.9.2

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 (155) hide show
  1. package/README.md +25 -2
  2. package/dist/analyze/index.cjs +1697 -0
  3. package/dist/analyze/index.cjs.map +1 -0
  4. package/dist/analyze/index.d.cts +429 -0
  5. package/dist/analyze/index.d.ts +429 -0
  6. package/dist/analyze/index.js +51 -0
  7. package/dist/analyze/index.js.map +1 -0
  8. package/dist/chunk-5HTNMQW4.js +653 -0
  9. package/dist/chunk-5HTNMQW4.js.map +1 -0
  10. package/dist/chunk-7Y7WF64D.js +410 -0
  11. package/dist/chunk-7Y7WF64D.js.map +1 -0
  12. package/dist/chunk-A4532ZFR.js +259 -0
  13. package/dist/chunk-A4532ZFR.js.map +1 -0
  14. package/dist/chunk-BHUT2S5G.js +22 -0
  15. package/dist/chunk-BHUT2S5G.js.map +1 -0
  16. package/dist/chunk-CCBLVAPK.js +42 -0
  17. package/dist/chunk-CCBLVAPK.js.map +1 -0
  18. package/dist/chunk-COKTX2G5.js +907 -0
  19. package/dist/chunk-COKTX2G5.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-GQKN5KDT.js +159 -0
  23. package/dist/chunk-GQKN5KDT.js.map +1 -0
  24. package/dist/chunk-LN7ZZMD2.js +530 -0
  25. package/dist/chunk-LN7ZZMD2.js.map +1 -0
  26. package/dist/chunk-MSWFZF7J.js +118 -0
  27. package/dist/chunk-MSWFZF7J.js.map +1 -0
  28. package/dist/chunk-R3S2LUBH.js +2722 -0
  29. package/dist/chunk-R3S2LUBH.js.map +1 -0
  30. package/dist/chunk-SDM3VJJ5.js +41 -0
  31. package/dist/chunk-SDM3VJJ5.js.map +1 -0
  32. package/dist/chunk-XTC3X6MS.js +816 -0
  33. package/dist/chunk-XTC3X6MS.js.map +1 -0
  34. package/dist/chunk-ZXW5YIWK.js +55 -0
  35. package/dist/chunk-ZXW5YIWK.js.map +1 -0
  36. package/dist/core/index.cjs +432 -0
  37. package/dist/core/index.cjs.map +1 -0
  38. package/dist/core/index.d.cts +210 -0
  39. package/dist/core/index.d.ts +210 -0
  40. package/dist/core/index.js +73 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/function-2bJW1Zp_.d.ts +187 -0
  43. package/dist/function-r2sh-jOO.d.cts +187 -0
  44. package/dist/generate/index.cjs +3787 -0
  45. package/dist/generate/index.cjs.map +1 -0
  46. package/dist/generate/index.d.cts +980 -0
  47. package/dist/generate/index.d.ts +980 -0
  48. package/dist/generate/index.js +52 -0
  49. package/dist/generate/index.js.map +1 -0
  50. package/dist/index-BMPxwjwV.d.ts +151 -0
  51. package/dist/index-BOBXBNwP.d.cts +157 -0
  52. package/dist/index-BdeQ1r_p.d.cts +56 -0
  53. package/dist/index-BreoJRw0.d.ts +127 -0
  54. package/dist/index-CGMZNCx6.d.cts +140 -0
  55. package/dist/index-CGMZNCx6.d.ts +140 -0
  56. package/dist/index-CM5W4rcm.d.ts +157 -0
  57. package/dist/index-CP3wAfEH.d.ts +56 -0
  58. package/dist/index-Cl7mhOFM.d.cts +164 -0
  59. package/dist/index-Cl7mhOFM.d.ts +164 -0
  60. package/dist/index-DZ1M21do.d.cts +127 -0
  61. package/dist/index-D_ZTUNKp.d.cts +151 -0
  62. package/dist/index.cjs +5277 -4893
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +14 -2621
  65. package/dist/index.d.ts +14 -2621
  66. package/dist/index.js +170 -6152
  67. package/dist/index.js.map +1 -1
  68. package/dist/model/index.cjs +2435 -0
  69. package/dist/model/index.cjs.map +1 -0
  70. package/dist/model/index.d.cts +630 -0
  71. package/dist/model/index.d.ts +630 -0
  72. package/dist/model/index.js +21 -0
  73. package/dist/model/index.js.map +1 -0
  74. package/dist/styled-BjXtOFNk.d.cts +282 -0
  75. package/dist/styled-D7BAx3S3.d.ts +282 -0
  76. package/dist/theory/index.cjs +1677 -0
  77. package/dist/theory/index.cjs.map +1 -0
  78. package/dist/theory/index.d.cts +535 -0
  79. package/dist/theory/index.d.ts +535 -0
  80. package/dist/theory/index.js +149 -0
  81. package/dist/theory/index.js.map +1 -0
  82. package/dist/types-MGP29r3t.d.cts +30 -0
  83. package/dist/types-MGP29r3t.d.ts +30 -0
  84. package/package.json +31 -1
  85. package/src/analyze/arrange/index.ts +21 -0
  86. package/src/analyze/arrange/internal.ts +125 -0
  87. package/src/analyze/arrange/tension.ts +187 -0
  88. package/src/analyze/arrange/tracks.ts +279 -0
  89. package/src/{detect → analyze/detect}/index.ts +82 -23
  90. package/src/analyze/functional/borrowed.ts +84 -0
  91. package/src/analyze/functional/cadence.ts +66 -0
  92. package/src/analyze/functional/function.ts +184 -0
  93. package/src/analyze/functional/index.ts +23 -0
  94. package/src/analyze/functional/internal.ts +29 -0
  95. package/src/analyze/functional/roman.ts +393 -0
  96. package/src/analyze/index.ts +49 -0
  97. package/src/{timeline → analyze/timeline}/index.ts +87 -24
  98. package/src/{analysis → analyze/voice}/index.ts +81 -12
  99. package/src/core/index.ts +47 -0
  100. package/src/{interval → core/interval}/index.ts +15 -1
  101. package/src/{meter → core/meter}/index.ts +108 -5
  102. package/src/{pitch → core/pitch}/index.ts +58 -3
  103. package/src/{random → core/random}/index.ts +13 -1
  104. package/src/{tuning → core/tuning}/index.ts +28 -1
  105. package/src/{bass → generate/bass}/index.ts +66 -17
  106. package/src/{countermelody → generate/countermelody}/index.ts +54 -24
  107. package/src/{drums → generate/drums}/fills.ts +31 -20
  108. package/src/{drums → generate/drums}/index.ts +127 -9
  109. package/src/{drums → generate/drums}/internal.ts +7 -1
  110. package/src/{drums → generate/drums}/rng.ts +2 -2
  111. package/src/{drums → generate/drums}/swing.ts +7 -2
  112. package/src/{groove → generate/groove}/index.ts +118 -22
  113. package/src/{harmonize → generate/harmonize}/index.ts +96 -23
  114. package/src/generate/index.ts +59 -0
  115. package/src/{motif → generate/motif}/index.ts +94 -17
  116. package/src/{progression → generate/progression}/index.ts +61 -27
  117. package/src/{reharmony → generate/reharmony}/index.ts +46 -15
  118. package/src/{rhythm → generate/rhythm}/index.ts +47 -13
  119. package/src/index.ts +130 -116
  120. package/src/model/chord.ts +423 -0
  121. package/src/model/index.ts +5 -947
  122. package/src/model/interval.ts +82 -0
  123. package/src/model/key.ts +266 -0
  124. package/src/model/note.ts +176 -0
  125. package/src/model/progression.ts +161 -0
  126. package/src/model/shared.ts +12 -0
  127. package/src/{chord → theory/chord}/index.ts +85 -4
  128. package/src/{chordscale → theory/chordscale}/index.ts +67 -9
  129. package/src/{counterpoint → theory/counterpoint}/index.ts +23 -2
  130. package/src/{harmony → theory/harmony}/index.ts +19 -2
  131. package/src/theory/index.ts +107 -0
  132. package/src/{safety → theory/safety}/index.ts +59 -12
  133. package/src/theory/scale/degrees.ts +123 -0
  134. package/src/theory/scale/index.ts +28 -0
  135. package/src/theory/scale/key.ts +56 -0
  136. package/src/theory/scale/masks.ts +153 -0
  137. package/src/{spelling → theory/spelling}/index.ts +21 -3
  138. package/src/{symbol → theory/symbol}/index.ts +91 -36
  139. package/src/theory/voicing/index.ts +5 -0
  140. package/src/theory/voicing/internal.ts +168 -0
  141. package/src/theory/voicing/leading.ts +112 -0
  142. package/src/theory/voicing/satb.ts +195 -0
  143. package/src/theory/voicing/styled.ts +176 -0
  144. package/src/arrange/index.ts +0 -409
  145. package/src/functional/index.ts +0 -602
  146. package/src/scale/index.ts +0 -215
  147. package/src/voicing/index.ts +0 -523
  148. /package/src/{types.ts → core/types.ts} +0 -0
  149. /package/src/{drums → generate/drums}/beat.ts +0 -0
  150. /package/src/{drums → generate/drums}/euclid.ts +0 -0
  151. /package/src/{drums → generate/drums}/ghost.ts +0 -0
  152. /package/src/{drums → generate/drums}/hihat.ts +0 -0
  153. /package/src/{drums → generate/drums}/hit.ts +0 -0
  154. /package/src/{drums → generate/drums}/kick.ts +0 -0
  155. /package/src/{drums → generate/drums}/percussion.ts +0 -0
@@ -0,0 +1,429 @@
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-DZ1M21do.cjs';
4
+ import { b as ChordTimeline, C as CadenceHit } from '../index-D_ZTUNKp.cjs';
5
+ export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-D_ZTUNKp.cjs';
6
+ import { C as Chord, a as ChordQuality } from '../index-BOBXBNwP.cjs';
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-r2sh-jOO.cjs';
8
+
9
+ /**
10
+ * Roman-numeral parsing and formatting.
11
+ *
12
+ * The {@link ROMAN_STYLE} table is the single source of truth for both
13
+ * directions, so {@link chordToRoman} and {@link romanToChord} stay mutual
14
+ * inverses by construction.
15
+ */
16
+
17
+ /**
18
+ * Build the chord denoted by a Roman numeral in a key.
19
+ *
20
+ * Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`
21
+ * suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions
22
+ * (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash
23
+ * (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree
24
+ * whose root becomes a local major tonic for the applied chord. Inverted chords
25
+ * carry a `bassPc`.
26
+ *
27
+ * The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==
28
+ * `m7b5`), since no half-diminished triad exists. Extension and added-tone
29
+ * suffixes are the canonical case-sensitive forms emitted by
30
+ * {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->
31
+ * `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->
32
+ * `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->
33
+ * `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.
34
+ * A figure string that is neither a recognized inversion nor a supported
35
+ * quality suffix throws rather than silently degrading to a triad.
36
+ *
37
+ * @param text The Roman numeral.
38
+ * @param key The prevailing key.
39
+ * @returns The chord.
40
+ * @example
41
+ * ```ts
42
+ * import { romanToChord, majorKey } from '@libraz/libcantus';
43
+ * romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }
44
+ * ```
45
+ * @category Functional Harmony
46
+ */
47
+ declare function romanToChord(text: string, key: KeyScale): Chord;
48
+ /**
49
+ * Render a chord as a Roman numeral relative to a key.
50
+ *
51
+ * Diatonic roots take their scale-degree numeral directly, so numerals are
52
+ * correct in both major and minor keys (and any custom scale). The quality
53
+ * 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).
59
+ *
60
+ * @param chord The chord to name.
61
+ * @param key The prevailing key.
62
+ * @returns The Roman numeral string.
63
+ * @example
64
+ * ```ts
65
+ * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';
66
+ * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)
67
+ * ```
68
+ * @category Functional Harmony
69
+ */
70
+ declare function chordToRoman(chord: Chord, key: KeyScale): string;
71
+
72
+ /**
73
+ * A theory annotation attached to a note.
74
+ *
75
+ * @category Arrangement & Analysis
76
+ */
77
+ type TheoryLabel = {
78
+ kind: 'chordTone';
79
+ role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
80
+ } | {
81
+ kind: 'tension';
82
+ degree: 9 | 11 | 13;
83
+ } | {
84
+ kind: 'avoid';
85
+ } | {
86
+ kind: 'passing';
87
+ } | {
88
+ kind: 'neighbor';
89
+ } | {
90
+ kind: 'suspension';
91
+ type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';
92
+ resolveTo: number;
93
+ } | {
94
+ kind: 'anticipation';
95
+ } | {
96
+ kind: 'escape';
97
+ } | {
98
+ kind: 'needsResolution';
99
+ resolveTo: number;
100
+ } | {
101
+ kind: 'leadingTone';
102
+ resolveTo: number;
103
+ };
104
+ /**
105
+ * A note with its theory labels and a short rationale.
106
+ *
107
+ * @category Arrangement & Analysis
108
+ */
109
+ type AnalyzedNote = {
110
+ noteId: number;
111
+ labels: TheoryLabel[];
112
+ rationale?: string;
113
+ };
114
+ /**
115
+ * A single note in a monophonic voice: a {@link NoteEvent} with a stable id.
116
+ *
117
+ * @category Arrangement & Analysis
118
+ */
119
+ type VoiceNote = NoteEvent & {
120
+ id: number;
121
+ };
122
+ /**
123
+ * Label every note of a voice with its theory roles.
124
+ *
125
+ * Each note is classified against the chord sounding at its beat. Chord tones
126
+ * get a role label; non-chord tones are matched, in order, as suspensions
127
+ * (prepared by an identical consonant pitch and resolving by step), passing
128
+ * tones, neighbors, anticipations, and escape tones, then fall back to tension,
129
+ * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted
130
+ * additionally.
131
+ *
132
+ * The voice is expected to be monophonic (one note at a time). Notes sharing an
133
+ * onset are treated as simultaneous cluster members, not melodic neighbors, so
134
+ * they receive no melodic labels (suspension, passing, neighbor, anticipation,
135
+ * escape) — only harmonic ones. Callers with truly polyphonic material should
136
+ * split it into monophonic sub-voices first, as `analyzeArrangement` does.
137
+ *
138
+ * @param voice The monophonic voice, in time order.
139
+ * @param chordAtBeat Chord sounding at a given beat, or null.
140
+ * @param key Key context for leading-tone detection.
141
+ * @param otherVoicesAtBeat Other sounding voices at a given beat.
142
+ * @returns One annotation per input note.
143
+ * @example
144
+ * ```ts
145
+ * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
146
+ * 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 },
150
+ * ];
151
+ * const cMajor = makeChord(0, 'maj');
152
+ * const labels = analyzeVoice(voice, () => cMajor, majorKey(0), () => []);
153
+ * labels; // one AnalyzedNote per input note, in the same order
154
+ * ```
155
+ * @category Arrangement & Analysis
156
+ */
157
+ declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
158
+
159
+ /**
160
+ * Arrangement track-role analysis: a single harmony is inferred from every
161
+ * track's notes pooled together, each track is then analysed against that shared
162
+ * harmony, and notes that clash with the sounding chord are collected as
163
+ * conflicts.
164
+ */
165
+
166
+ /**
167
+ * The musical role a track plays in the arrangement.
168
+ *
169
+ * @category Arrangement & Analysis
170
+ */
171
+ type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
172
+ /**
173
+ * One input track: its notes plus optional name and role.
174
+ *
175
+ * @category Arrangement & Analysis
176
+ */
177
+ type ArrangementTrack = {
178
+ name?: string;
179
+ role?: TrackRole;
180
+ notes: NoteEvent[];
181
+ };
182
+ /**
183
+ * A track after analysis: its resolved name, role, and per-note annotations.
184
+ *
185
+ * @category Arrangement & Analysis
186
+ */
187
+ type TrackAnalysis = {
188
+ name: string;
189
+ role: TrackRole;
190
+ /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
191
+ notes: AnalyzedNote[];
192
+ };
193
+ /**
194
+ * A note that clashes with the harmony sounding beneath it.
195
+ *
196
+ * @category Arrangement & Analysis
197
+ */
198
+ type Conflict = {
199
+ beat: number;
200
+ trackName: string;
201
+ pitch: number;
202
+ /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
203
+ safety: NoteSafety;
204
+ /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
205
+ reasons: number;
206
+ rationale?: string;
207
+ };
208
+ /**
209
+ * The full result of {@link analyzeArrangement}.
210
+ *
211
+ * @category Arrangement & Analysis
212
+ */
213
+ type ArrangementAnalysis = {
214
+ key: KeyScale;
215
+ timeline: ChordTimeline;
216
+ segmentConfidence: number[];
217
+ cadences: CadenceHit[];
218
+ tracks: TrackAnalysis[];
219
+ /** Notes clashing with the sounding harmony, worst severity first. */
220
+ conflicts: Conflict[];
221
+ };
222
+ /**
223
+ * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.
224
+ *
225
+ * @category Arrangement & Analysis
226
+ */
227
+ type ArrangementOptions = {
228
+ /** Key context; inferred from the pooled notes when omitted. */
229
+ key?: KeyScale;
230
+ /**
231
+ * Time signature; defaults to 4/4.
232
+ *
233
+ * @defaultValue `4/4`
234
+ */
235
+ ts?: TimeSignature;
236
+ /**
237
+ * Chord-slot length in beats; defaults to one bar of `ts`.
238
+ *
239
+ * @defaultValue one bar of `ts`
240
+ */
241
+ harmonicRhythm?: number;
242
+ /**
243
+ * Safety profile used for conflict detection; defaults to `pop`.
244
+ *
245
+ * @defaultValue `'pop'`
246
+ */
247
+ profile?: SafetyProfile;
248
+ };
249
+ /**
250
+ * Analyse a whole arrangement against a single inferred harmony.
251
+ *
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.
258
+ *
259
+ * Voice analysis assumes one monophonic voice at a time, so each track is
260
+ * first partitioned into monophonic sub-voices (a polyphonic block-chord track
261
+ * fans out into one sub-voice per simultaneous note; a monophonic track is
262
+ * unaffected). Each sub-voice is labelled note-by-note against the harmony
263
+ * with {@link analyzeVoice}, seeing every other sub-voice — including siblings
264
+ * within the same track — as its accompaniment. Every note is then re-checked
265
+ * with {@link evaluateSafety} at its onset and again at each chord change it
266
+ * sustains across, so a held note that clashes with a later chord is caught:
267
+ * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
268
+ * clashing beat, sorted worst severity first and then by beat.
269
+ *
270
+ * @param tracks The tracks to analyse.
271
+ * @param opts Analysis options; see {@link ArrangementOptions}.
272
+ * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
273
+ * @example
274
+ * ```ts
275
+ * import { analyzeArrangement } from '@libraz/libcantus';
276
+ * const melody = [
277
+ * { pitch: 60, startBeat: 0, durationBeat: 2 },
278
+ * { pitch: 67, startBeat: 2, durationBeat: 2 },
279
+ * ];
280
+ * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
281
+ * conflicts; // notes clashing with the inferred harmony, worst severity first
282
+ * ```
283
+ * @category Arrangement & Analysis
284
+ */
285
+ declare function analyzeArrangement(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementAnalysis;
286
+
287
+ /**
288
+ * Arrangement tension analysis: a coarse harmonic-tension curve sampled at
289
+ * regular beats across a whole multi-track piece, combining the sounding
290
+ * chord's harmonic function, the dissonance of the sounding notes, and their
291
+ * registral span into a single normalized reading.
292
+ */
293
+
294
+ /**
295
+ * A tension reading sampled at a beat.
296
+ *
297
+ * @category Arrangement & Analysis
298
+ */
299
+ type TensionPoint = {
300
+ beat: number;
301
+ /** Combined tension in [0, 1]. */
302
+ tension: number;
303
+ };
304
+ /**
305
+ * Sample the harmonic tension of an arrangement at regular beats.
306
+ *
307
+ * At each sample beat the tension combines three normalized terms:
308
+ * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
309
+ * tonic or no chord `0`), weighted {@link FUNCTION_WEIGHT};
310
+ * - the dissonance of the sounding notes — the larger of the share of sounding
311
+ * pitches that are not chord tones and the share that {@link evaluateSafety}
312
+ * rates {@link NoteSafety.Dissonant} — weighted {@link DISSONANCE_WEIGHT};
313
+ * - the registral span of the sounding notes, saturating at
314
+ * {@link SPAN_SATURATION} semitones, weighted {@link SPAN_WEIGHT}.
315
+ *
316
+ * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
317
+ * pooled together, so the result is deterministic and self-contained.
318
+ *
319
+ * @param tracks The tracks to sample.
320
+ * @param opts Analysis options plus an optional `step` (default one beat).
321
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
322
+ * @throws If `step` is not positive.
323
+ * @example
324
+ * ```ts
325
+ * import { tensionCurve } from '@libraz/libcantus';
326
+ * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
327
+ * const curve = tensionCurve([{ notes }], { step: 1 });
328
+ * curve; // one { beat, tension } sample per beat, in beat order
329
+ * ```
330
+ * @category Arrangement & Analysis
331
+ */
332
+ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
333
+ step?: number;
334
+ }): 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
+ /**
373
+ * Identify chords matching a set of pitches.
374
+ *
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.
381
+ *
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).
419
+ * @example
420
+ * ```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
424
+ * ```
425
+ * @category Recognition
426
+ */
427
+ declare function detectKey(pitches: number[]): KeyMatch[];
428
+
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 };