@libraz/libcantus 0.9.4 → 1.0.0

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 (186) hide show
  1. package/README.md +61 -261
  2. package/README_ja.md +115 -0
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +213 -456
  6. package/dist/analyze/index.d.ts +213 -456
  7. package/dist/analyze/index.js +56 -9
  8. package/dist/chunk-3JXCW6DX.cjs +3220 -0
  9. package/dist/chunk-3JXCW6DX.cjs.map +1 -0
  10. package/dist/chunk-4A2RMUCC.js +7647 -0
  11. package/dist/chunk-4A2RMUCC.js.map +1 -0
  12. package/dist/chunk-6XPTBE7Y.cjs +49 -0
  13. package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
  14. package/dist/chunk-AFM2ULJH.cjs +5121 -0
  15. package/dist/chunk-AFM2ULJH.cjs.map +1 -0
  16. package/dist/chunk-E3F4AHAH.cjs +690 -0
  17. package/dist/chunk-E3F4AHAH.cjs.map +1 -0
  18. package/dist/chunk-ELCTZUJJ.js +379 -0
  19. package/dist/chunk-ELCTZUJJ.js.map +1 -0
  20. package/dist/chunk-FVSI36UV.js +690 -0
  21. package/dist/chunk-FVSI36UV.js.map +1 -0
  22. package/dist/chunk-GEDTJHAC.cjs +1 -0
  23. package/dist/chunk-GEDTJHAC.cjs.map +1 -0
  24. package/dist/chunk-GOZG57ER.js +3220 -0
  25. package/dist/chunk-GOZG57ER.js.map +1 -0
  26. package/dist/chunk-H3VORETI.js +2243 -0
  27. package/dist/chunk-H3VORETI.js.map +1 -0
  28. package/dist/chunk-H42UHPYW.js +1577 -0
  29. package/dist/chunk-H42UHPYW.js.map +1 -0
  30. package/dist/chunk-HFPS7JFD.js +1 -0
  31. package/dist/chunk-INHVXP6Z.js +3053 -0
  32. package/dist/chunk-INHVXP6Z.js.map +1 -0
  33. package/dist/chunk-K4ZJUGF4.cjs +379 -0
  34. package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
  35. package/dist/chunk-KNJEKLKV.js +49 -0
  36. package/dist/chunk-KNJEKLKV.js.map +1 -0
  37. package/dist/chunk-M4CWTJWJ.cjs +7647 -0
  38. package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
  39. package/dist/chunk-MXO72PLY.cjs +211 -0
  40. package/dist/chunk-MXO72PLY.cjs.map +1 -0
  41. package/dist/chunk-NLEU4QSE.cjs +360 -0
  42. package/dist/chunk-NLEU4QSE.cjs.map +1 -0
  43. package/dist/chunk-OE6WK2Y2.cjs +1577 -0
  44. package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
  45. package/dist/chunk-Q2WGKJNN.cjs +1064 -0
  46. package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
  47. package/dist/chunk-QYQQ5J6E.js +1 -0
  48. package/dist/chunk-QYQQ5J6E.js.map +1 -0
  49. package/dist/chunk-RHIJ3S4I.cjs +1 -0
  50. package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
  51. package/dist/chunk-TTCLJX6D.js +211 -0
  52. package/dist/chunk-TTCLJX6D.js.map +1 -0
  53. package/dist/chunk-UADYAYAT.cjs +3053 -0
  54. package/dist/chunk-UADYAYAT.cjs.map +1 -0
  55. package/dist/chunk-VOHZB45S.js +5121 -0
  56. package/dist/chunk-VOHZB45S.js.map +1 -0
  57. package/dist/chunk-VTI6FOLB.js +1064 -0
  58. package/dist/chunk-VTI6FOLB.js.map +1 -0
  59. package/dist/chunk-WWURE4XT.cjs +2243 -0
  60. package/dist/chunk-WWURE4XT.cjs.map +1 -0
  61. package/dist/chunk-YH3BIKHH.js +360 -0
  62. package/dist/chunk-YH3BIKHH.js.map +1 -0
  63. package/dist/core/index.cjs +103 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +96 -345
  66. package/dist/core/index.d.ts +96 -345
  67. package/dist/core/index.js +104 -7
  68. package/dist/generate/index.cjs +89 -10
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +437 -1095
  71. package/dist/generate/index.d.ts +437 -1095
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-D6b6na69.d.cts → index-B4o55NDr.d.cts} +13 -9
  75. package/dist/index-B9MNZboQ.d.cts +770 -0
  76. package/dist/index-B9MNZboQ.d.ts +770 -0
  77. package/dist/index-BGFW-8P2.d.cts +2307 -0
  78. package/dist/index-BO6xE9YU.d.cts +1804 -0
  79. package/dist/index-BkGmlyI3.d.cts +470 -0
  80. package/dist/index-CCNJRG-P.d.cts +1095 -0
  81. package/dist/index-CGpRv5sl.d.cts +643 -0
  82. package/dist/index-C_8o--VE.d.cts +1242 -0
  83. package/dist/index-CgHqxdps.d.ts +2307 -0
  84. package/dist/index-Chbst3Jx.d.ts +1343 -0
  85. package/dist/index-Cj49F892.d.ts +470 -0
  86. package/dist/{index-tSQh-xwY.d.ts → index-CunYKLiu.d.ts} +13 -9
  87. package/dist/index-D57QRbVn.d.ts +1804 -0
  88. package/dist/index-DInQqT8Q.d.cts +66 -0
  89. package/dist/index-DInQqT8Q.d.ts +66 -0
  90. package/dist/index-Dzr13q3d.d.cts +1343 -0
  91. package/dist/index-G1uwGGWQ.d.ts +643 -0
  92. package/dist/index-szBv3496.d.ts +1242 -0
  93. package/dist/index.cjs +358 -15
  94. package/dist/index.cjs.map +1 -1
  95. package/dist/index.d.cts +17 -15
  96. package/dist/index.d.ts +17 -15
  97. package/dist/index.js +407 -64
  98. package/dist/masks-DYbE485J.d.cts +445 -0
  99. package/dist/masks-DYbE485J.d.ts +445 -0
  100. package/dist/model/index.cjs +39 -7
  101. package/dist/model/index.cjs.map +1 -1
  102. package/dist/model/index.d.cts +4685 -243
  103. package/dist/model/index.d.ts +4685 -243
  104. package/dist/model/index.js +40 -8
  105. package/dist/positional-Cptlj-xs.d.ts +287 -0
  106. package/dist/positional-DvH_sd8u.d.cts +287 -0
  107. package/dist/theory/index.cjs +101 -7
  108. package/dist/theory/index.cjs.map +1 -1
  109. package/dist/theory/index.d.cts +513 -173
  110. package/dist/theory/index.d.ts +513 -173
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +16 -11
  113. package/dist/chunk-36N73KZ3.cjs +0 -246
  114. package/dist/chunk-36N73KZ3.cjs.map +0 -1
  115. package/dist/chunk-4HLLZPXT.cjs +0 -3077
  116. package/dist/chunk-4HLLZPXT.cjs.map +0 -1
  117. package/dist/chunk-7MGZ7D62.cjs +0 -498
  118. package/dist/chunk-7MGZ7D62.cjs.map +0 -1
  119. package/dist/chunk-7NKDPF74.js +0 -3077
  120. package/dist/chunk-7NKDPF74.js.map +0 -1
  121. package/dist/chunk-CAN76NCU.cjs +0 -679
  122. package/dist/chunk-CAN76NCU.cjs.map +0 -1
  123. package/dist/chunk-D2FAJBYL.js +0 -479
  124. package/dist/chunk-D2FAJBYL.js.map +0 -1
  125. package/dist/chunk-EHVEBWM5.js +0 -1449
  126. package/dist/chunk-EHVEBWM5.js.map +0 -1
  127. package/dist/chunk-FCANXBAT.cjs +0 -30
  128. package/dist/chunk-FCANXBAT.cjs.map +0 -1
  129. package/dist/chunk-FNDWXRRI.cjs +0 -479
  130. package/dist/chunk-FNDWXRRI.cjs.map +0 -1
  131. package/dist/chunk-GQSGS2N4.js +0 -173
  132. package/dist/chunk-GQSGS2N4.js.map +0 -1
  133. package/dist/chunk-HKGYCQ34.js +0 -53
  134. package/dist/chunk-HKGYCQ34.js.map +0 -1
  135. package/dist/chunk-I24MV65U.js +0 -106
  136. package/dist/chunk-I24MV65U.js.map +0 -1
  137. package/dist/chunk-KMHOICIY.js +0 -679
  138. package/dist/chunk-KMHOICIY.js.map +0 -1
  139. package/dist/chunk-LW53AHQU.cjs +0 -1117
  140. package/dist/chunk-LW53AHQU.cjs.map +0 -1
  141. package/dist/chunk-N6IHQUOX.js +0 -1
  142. package/dist/chunk-NKIBQ6PT.js +0 -30
  143. package/dist/chunk-NKIBQ6PT.js.map +0 -1
  144. package/dist/chunk-NP4NA2FB.cjs +0 -106
  145. package/dist/chunk-NP4NA2FB.cjs.map +0 -1
  146. package/dist/chunk-NVE34YGZ.cjs +0 -1449
  147. package/dist/chunk-NVE34YGZ.cjs.map +0 -1
  148. package/dist/chunk-NYYWMV2C.js +0 -327
  149. package/dist/chunk-NYYWMV2C.js.map +0 -1
  150. package/dist/chunk-Q2XUP227.js +0 -845
  151. package/dist/chunk-Q2XUP227.js.map +0 -1
  152. package/dist/chunk-Q6OP4U43.cjs +0 -327
  153. package/dist/chunk-Q6OP4U43.cjs.map +0 -1
  154. package/dist/chunk-RC6GBMCD.cjs +0 -845
  155. package/dist/chunk-RC6GBMCD.cjs.map +0 -1
  156. package/dist/chunk-RK35RIN7.js +0 -1117
  157. package/dist/chunk-RK35RIN7.js.map +0 -1
  158. package/dist/chunk-RUMU3UA6.cjs +0 -53
  159. package/dist/chunk-RUMU3UA6.cjs.map +0 -1
  160. package/dist/chunk-T64C7ZD7.cjs +0 -1
  161. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  162. package/dist/chunk-XEBG3G53.js +0 -498
  163. package/dist/chunk-XEBG3G53.js.map +0 -1
  164. package/dist/chunk-YLEMGEFH.js +0 -246
  165. package/dist/chunk-YLEMGEFH.js.map +0 -1
  166. package/dist/chunk-ZEJR4TKL.cjs +0 -173
  167. package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
  168. package/dist/function-BtiZb9i9.d.ts +0 -208
  169. package/dist/function-DNugAOrt.d.cts +0 -208
  170. package/dist/index-6VR8-kit.d.ts +0 -143
  171. package/dist/index-70p168VM.d.ts +0 -192
  172. package/dist/index-Be74mcon.d.cts +0 -152
  173. package/dist/index-BtXT6UQ0.d.cts +0 -192
  174. package/dist/index-C5UBbQ4G.d.ts +0 -152
  175. package/dist/index-C83mZI1P.d.ts +0 -242
  176. package/dist/index-DXd9JikN.d.cts +0 -246
  177. package/dist/index-DXd9JikN.d.ts +0 -246
  178. package/dist/index-OG65ocOr.d.cts +0 -272
  179. package/dist/index-OG65ocOr.d.ts +0 -272
  180. package/dist/index-tJhkmQgE.d.cts +0 -143
  181. package/dist/index-y_ak9_Hi.d.cts +0 -242
  182. package/dist/styled-DIRkk9Uz.d.ts +0 -506
  183. package/dist/styled-DJbkJQfj.d.cts +0 -506
  184. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  185. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  186. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -0,0 +1,1343 @@
1
+ import { c as ChordLike, K as KeyLike, j as ChordToneRole, b as Chord, k as KeyRelation, e as ChordSegment, V as VoiceSnapshot, g as ChordSpan } from './index-C_8o--VE.cjs';
2
+ import { T as TimeSignature, c as MeterMap } from './index-BkGmlyI3.cjs';
3
+ import { d as KeyScale, g as NoteEvent } from './index-B9MNZboQ.cjs';
4
+
5
+ /**
6
+ * The vocabulary the analyze layer explains itself in.
7
+ *
8
+ * `analyzeVoice` has always returned a short `rationale` beside the labels it
9
+ * assigns; the chord, numeral, cadence and key readings say the same kind of
10
+ * thing about a larger unit. One vocabulary keeps a rationale readable wherever
11
+ * it comes from: the conclusion first, a colon, then the fact it rests on, as a
12
+ * phrase rather than a sentence.
13
+ *
14
+ * The reading that was turned down is worth as much to a reader arguing with the
15
+ * result as the one that was kept, so every entry point that ranks readings can
16
+ * report them in the one shape below.
17
+ */
18
+ /**
19
+ * A reading that was considered and rejected, and why it lost.
20
+ *
21
+ * @category Arrangement & Analysis
22
+ */
23
+ type RejectedCandidate = {
24
+ /** The rejected reading, named the way the accepted one is named. */
25
+ label: string;
26
+ /** Why the accepted reading was preferred over this one. */
27
+ reason: string;
28
+ };
29
+
30
+ /**
31
+ * Key-profile vectors for {@link detectKey}.
32
+ *
33
+ * A key profile is a 12-entry vector indexed from the tonic (0 = tonic, 1 = flat
34
+ * second, ... 11 = major seventh) giving how strongly each chromatic degree is
35
+ * expected to sound in that key. Ranking a candidate key means rotating its
36
+ * profile onto the candidate tonic and correlating it with the pitch-class
37
+ * distribution of the music, which is what separates a key from its relative:
38
+ * C major and A minor contain the same seven pitch classes, but they expect the
39
+ * weight to fall on different degrees.
40
+ *
41
+ * The published vectors below are transcribed from their sources and are not
42
+ * tuned for this library; a citation accompanies each so a reader can check the
43
+ * digits.
44
+ */
45
+ /**
46
+ * A pair of 12-entry key profiles, one per mode, each indexed from its own
47
+ * tonic (index 0 = tonic, 1 = flat second, ... 11 = major seventh).
48
+ *
49
+ * @category Recognition
50
+ */
51
+ type KeyProfilePair = {
52
+ /** Degree weights for a major candidate, indexed from the major tonic. */
53
+ major: readonly number[];
54
+ /** Degree weights for a minor candidate, indexed from the minor tonic. */
55
+ minor: readonly number[];
56
+ };
57
+ /**
58
+ * Name of a built-in key profile accepted by {@link DetectKeyOptions.profile}.
59
+ *
60
+ * @category Recognition
61
+ */
62
+ type KeyProfileName = 'krumhansl' | 'temperley' | 'flat';
63
+
64
+ /**
65
+ * Borrowed chords (modal interchange) and the Neapolitan.
66
+ *
67
+ * Roots are pitch classes measured against the key tonic, so borrowed and
68
+ * chromatic chords are handled by their semitone offset rather than requiring a
69
+ * spelled key signature.
70
+ */
71
+
72
+ /**
73
+ * The origin of a recognized non-diatonic chord, or null when none applies.
74
+ *
75
+ * @category Functional Harmony
76
+ */
77
+ type BorrowedSource = 'parallelMinor' | 'parallelMajor' | 'neapolitan' | null;
78
+ /**
79
+ * Whether a chord is borrowed from the parallel mode (modal interchange).
80
+ *
81
+ * True when the chord is not diatonic to `key` but is diatonic to its
82
+ * {@link parallelKeyOf | parallel key} — e.g. iv, bVI, or bVII in a major key, or the Picardy
83
+ * tonic and major IV in a minor key — and true for the Neapolitan, which is
84
+ * counted as a borrowing of its own even though it belongs to neither parallel
85
+ * mode. Two non-diatonic families are excluded: any other chord diatonic to
86
+ * neither mode (those are chromatic, not borrowed), and, in a minor key, chords
87
+ * explained by the altered minor forms (the major dominant and
88
+ * raised-leading-tone chords), which are in-key alterations rather than
89
+ * interchange even though they happen to fit the parallel major.
90
+ *
91
+ * @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.
92
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
93
+ * @returns True if the chord is borrowed from the parallel mode.
94
+ * @category Functional Harmony
95
+ */
96
+ declare function isBorrowedChord(chord: ChordLike, key: KeyLike): boolean;
97
+ /**
98
+ * Identify where a non-diatonic chord comes from.
99
+ *
100
+ * The Neapolitan (major triad on b2 of a key that does not have that degree
101
+ * already) is recognized first, since it sits outside both parallel modes;
102
+ * parallel-mode borrowing follows the {@link isBorrowedChord} rules. Diatonic
103
+ * chords and altered-minor chords, and unrecognized chromatic chords all yield
104
+ * null.
105
+ *
106
+ * @param chord The chord to classify, as a chord symbol, chord data, or a
107
+ * `Chord`.
108
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
109
+ * @returns The borrowing source, or null.
110
+ * @category Functional Harmony
111
+ */
112
+ declare function borrowedSource(chord: ChordLike, key: KeyLike): BorrowedSource;
113
+
114
+ /**
115
+ * Cadence detection between two successive chords.
116
+ *
117
+ * Roots are pitch classes measured against the key tonic, so borrowed and
118
+ * chromatic chords are handled by their semitone offset rather than requiring a
119
+ * spelled key signature.
120
+ */
121
+
122
+ /**
123
+ * A recognized cadence, with the voice-leading facts its reading rests on.
124
+ *
125
+ * @category Functional Harmony
126
+ */
127
+ type CadenceResult = {
128
+ /**
129
+ * The cadence the chord pair forms, or null when it forms none.
130
+ *
131
+ * `'phrygian'` and `'modal'` are the two named species: the first is a
132
+ * specific half cadence and is reported in place of `'half'`, the second is
133
+ * the bVII-to-I arrival that no common-practice type covers. Code counting
134
+ * half cadences has to count `'phrygian'` alongside `'half'`.
135
+ */
136
+ type: 'authentic' | 'plagal' | 'half' | 'deceptive' | 'phrygian' | 'modal' | null;
137
+ /**
138
+ * How conclusive an authentic cadence is: `'perfect'` when it is a PAC,
139
+ * `'imperfect'` when it is an IAC.
140
+ *
141
+ * Null for every other cadence type — the distinction is only defined for the
142
+ * authentic cadence — and null for an authentic cadence whose soprano is
143
+ * unknown, which is the case whenever no `voicing` is given.
144
+ */
145
+ strength: 'perfect' | 'imperfect' | null;
146
+ /**
147
+ * The role the final chord's highest sounding pitch plays in that chord.
148
+ *
149
+ * Omitted when no `voicing` was given, and when the top voice is a tension
150
+ * with no basic chord-tone role.
151
+ */
152
+ soprano?: ChordToneRole;
153
+ /**
154
+ * True when both chords stand on their own root — the inversion condition a
155
+ * perfect authentic cadence has to meet.
156
+ */
157
+ rootPosition: boolean;
158
+ /**
159
+ * True when the dominant resolved onto an inverted tonic (V to I6 and its
160
+ * relatives) instead of the root-position arrival it prepared, so the
161
+ * cadential weight is withheld even though the type still holds.
162
+ */
163
+ evaded: boolean;
164
+ /**
165
+ * Why the pair reads this way: the motion the type rests on, what graded an
166
+ * authentic cadence, and what a null type failed to be. Always present, in
167
+ * the phrasing {@link analyzeVoice} uses for a note.
168
+ */
169
+ rationale: string;
170
+ /**
171
+ * The cadences that were considered and rejected, empty unless
172
+ * {@link DetectCadenceOptions.alternatives} asked for them.
173
+ *
174
+ * These are the near misses — the readings a condition or two away from
175
+ * holding — rather than every type that did not fire, which for most pairs
176
+ * would be all of them.
177
+ */
178
+ alternatives: RejectedCandidate[];
179
+ };
180
+ /**
181
+ * Options for {@link detectCadence}.
182
+ *
183
+ * @category Functional Harmony
184
+ */
185
+ type DetectCadenceOptions = {
186
+ /**
187
+ * The pitches actually sounding, as MIDI note numbers: the penultimate
188
+ * chord's voicing first, then the final chord's.
189
+ *
190
+ * The lowest pitch of each is its bass and so decides inversion, overriding
191
+ * whatever the chords' own `bassPc` says; the highest pitch of the final
192
+ * chord is the soprano. Without a voicing the soprano is unknown, and a
193
+ * perfect authentic cadence cannot be told from an imperfect one.
194
+ */
195
+ voicing?: [number[], number[]];
196
+ /**
197
+ * Report the cadences this pair came close to forming and why each was
198
+ * turned down.
199
+ *
200
+ * Off by default: the rationale is built from facts the classification
201
+ * already established, while a rival has to be phrased against conditions
202
+ * that did not fire, and most callers read the type only.
203
+ *
204
+ * @defaultValue false
205
+ */
206
+ alternatives?: boolean;
207
+ /**
208
+ * The chord before `from`, which is what tells a cadential six-four from an
209
+ * inverted tonic.
210
+ *
211
+ * A dominant reached from the tonic triad standing on its own bass was
212
+ * already sounding when that six-four did, so the cadence began there. Only
213
+ * the chord before the dominant settles that, and a caller reading a pair at
214
+ * a time has it to hand; without it the pair is classified on its own, as
215
+ * before.
216
+ *
217
+ * Accepted as a chord symbol, chord data, or a `Chord`.
218
+ */
219
+ approach?: ChordLike;
220
+ };
221
+ /**
222
+ * Classify the cadence formed by moving from one chord to the next.
223
+ *
224
+ * - authentic: V (dominant a fifth above the tonic) to I
225
+ * - plagal: IV (a fourth above the tonic) to I
226
+ * - modal: bVII to I, the cadence of rock and modal writing
227
+ * - deceptive: V to the key's own submediant, plus the borrowed bVI of a major
228
+ * key
229
+ * - phrygian: iv6 to V of a minor key, the half cadence whose bass falls a
230
+ * semitone from b6 to 5
231
+ * - half: any other chord than the dominant itself to V
232
+ *
233
+ * Both are read as scale degrees rather than as semitone distances, so a mode
234
+ * cadences onto the submediant and the dominant it actually has: the deceptive
235
+ * arrival of dorian is a major sixth above the tonic and that of phrygian a
236
+ * minor sixth. A half cadence normally rests on the major third that carries
237
+ * the leading tone, but where the key's own dominant has no leading tone —
238
+ * aeolian and dorian, the modal `v` of pop writing — the arrival's own third
239
+ * suffices as long as the key contains it. That relaxation never applies to a
240
+ * key whose dominant does carry the leading tone, so a borrowed minor `v` in a
241
+ * major key is no half cadence.
242
+ *
243
+ * A static V-to-V repeat with no root motion is not a cadence and yields a null
244
+ * type, and so is the tonic six-four resolving onto the dominant it already
245
+ * stands on: that pair is one harmony moving inside itself. Passing that
246
+ * six-four as `approach` when the dominant is the `from` chord is what lets the
247
+ * cadence be reported as the single event it is — the type and the beat are
248
+ * still the dominant's resolution, while the `rationale` says the cadence began
249
+ * at the six-four. A six-four the bass leaves instead (`IV-I64-IV`, or a
250
+ * neighbouring one over the tonic) is an ordinary inverted tonic and is read as
251
+ * one.
252
+ *
253
+ * An authentic cadence is graded perfect or imperfect. Perfect takes the
254
+ * dominant to the tonic with both chords in root position and the tonic in the
255
+ * soprano; anything else authentic is imperfect, including the leading-tone
256
+ * cadence, which has no dominant root to stand on. Only a `voicing` names the
257
+ * soprano, so without one the grade is null — the pair could still be either,
258
+ * and reporting perfect would be a guess.
259
+ *
260
+ * The `rationale` says which motion the reading rests on, including for a pair
261
+ * that cadences not at all; `alternatives` is empty unless asked for, and then
262
+ * names the cadences the pair came close to forming.
263
+ *
264
+ * @param from The penultimate chord, as a chord symbol, chord data, or a
265
+ * `Chord`.
266
+ * @param to The final chord, in any of the same forms.
267
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
268
+ * @param opts Voice-leading detail, the chord before `from`, and whether to
269
+ * collect the rejected readings; see {@link DetectCadenceOptions}.
270
+ * @returns The cadence, its strength, and the facts behind them.
271
+ * @example
272
+ * ```ts
273
+ * import { detectCadence, makeChord, majorKey } from '@libraz/libcantus';
274
+ * detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0));
275
+ * // { type: 'authentic', strength: null, rootPosition: true, evaded: false, ... }
276
+ * detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0), {
277
+ * voicing: [[55, 62, 71], [48, 64, 72]],
278
+ * });
279
+ * // { type: 'authentic', strength: 'perfect', soprano: 'root', ... }
280
+ * const sixFour = detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0), {
281
+ * approach: makeChord(0, 'maj', 7), // C/G, the cadential six-four
282
+ * });
283
+ * sixFour.type; // 'authentic'
284
+ * // The rationale says where the cadence began.
285
+ * sixFour.rationale?.includes('cadential six-four'); // true
286
+ * ```
287
+ * @category Functional Harmony
288
+ */
289
+ declare function detectCadence(from: ChordLike, to: ChordLike, key: KeyLike, opts?: DetectCadenceOptions): CadenceResult;
290
+
291
+ /**
292
+ * Roman-numeral parsing and formatting.
293
+ *
294
+ * The {@link ROMAN_STYLE} table is the single source of truth for both
295
+ * directions, so {@link chordToRoman} and {@link romanToChord} stay mutual
296
+ * inverses by construction. The chromatic chords no numeral can spell — the
297
+ * augmented sixths, and the Neapolitan under its figured name — are matched as
298
+ * whole symbols beside that table.
299
+ */
300
+
301
+ /**
302
+ * Build the chord denoted by a Roman numeral in a key.
303
+ *
304
+ * Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`
305
+ * suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions
306
+ * (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash
307
+ * (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree
308
+ * whose root becomes a local major tonic for the applied chord. Inverted chords
309
+ * carry a `bassPc`.
310
+ *
311
+ * The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==
312
+ * `m7b5`), since no half-diminished triad exists. Extension and added-tone
313
+ * suffixes are the canonical case-sensitive forms emitted by
314
+ * {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->
315
+ * `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->
316
+ * `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->
317
+ * `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.
318
+ * A figure string that is neither a recognized inversion nor a supported
319
+ * quality suffix throws rather than silently degrading to a triad.
320
+ *
321
+ * Four chromatic chords are named rather than numbered, and are accepted as
322
+ * whole symbols: the augmented sixths `It6`, `Fr6` and `Ger6` (also written
323
+ * `Ger65`), all three on the lowered submediant in the bass, and `N6` for the
324
+ * first-inversion Neapolitan, which builds exactly the chord `bII6` does.
325
+ * Each may be applied to a degree like any other numeral, so `Ger6/V` is the
326
+ * German sixth of the dominant.
327
+ *
328
+ * @param text The Roman numeral.
329
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
330
+ * @returns The chord.
331
+ * @example
332
+ * ```ts
333
+ * import { romanToChord, majorKey } from '@libraz/libcantus';
334
+ * romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }
335
+ * romanToChord('Ger6', majorKey(0)); // Ab C Eb F#, bass Ab
336
+ * ```
337
+ * @category Functional Harmony
338
+ */
339
+ declare function romanToChord(text: string, key: KeyLike): Chord;
340
+ /**
341
+ * Options for {@link chordToRoman}.
342
+ *
343
+ * @category Functional Harmony
344
+ */
345
+ type ChordToRomanOptions = {
346
+ /**
347
+ * Render a tonicizing chord as an applied numeral (`V7/V`, `viio7/ii`)
348
+ * instead of naming its root against the home key (`II7`, `#ivo7`).
349
+ *
350
+ * Off by default: naming the root is always a correct spelling, whereas
351
+ * whether a chromatic dominant is genuinely applied is a reading only the
352
+ * caller can make. Turning it on names the chords {@link secondaryDominant}
353
+ * builds as the applied numerals they are, except where the target degree
354
+ * carries no perfect fifth: nothing tonicizes a diminished triad, so a chord
355
+ * over the seventh degree of a major key stays `#IV7` rather than becoming a
356
+ * numeral no harmony text writes.
357
+ */
358
+ applied?: boolean;
359
+ /**
360
+ * Render a first-inversion Neapolitan under its figured name `N6` instead of
361
+ * the numeral `bII6`.
362
+ *
363
+ * Off by default, for the opposite reason to `applied`: both spellings name
364
+ * the same chord and `bII6` is already correct, so which one to write is a
365
+ * house-style choice rather than an analysis. `romanToChord` accepts either
366
+ * whatever this is set to. The augmented sixths need no such switch — no
367
+ * numeral spells them at all, so they always render as `It6`, `Fr6`
368
+ * and `Ger6`.
369
+ */
370
+ neapolitan?: boolean;
371
+ };
372
+ /**
373
+ * Render a chord as a Roman numeral relative to a key.
374
+ *
375
+ * Diatonic roots take their scale-degree numeral directly, so numerals are
376
+ * correct in both major and minor keys (and any custom scale). The quality
377
+ * selects the case and suffix; chromatic roots receive a flat/sharp spelling by
378
+ * convention. A key that is not heptatonic has no numeral of its own for every
379
+ * degree, so its roots are named against its parallel major.
380
+ *
381
+ * When the chord carries a `bassPc` on a chord tone, a figured-bass inversion
382
+ * (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true seventh
383
+ * chords; added-tone and extended qualities have no lossless figure and render
384
+ * in root position instead (the bass is dropped, pitch classes are preserved).
385
+ *
386
+ * A `bassPc` that is *not* a chord tone — a pedal or passing bass such as
387
+ * `C/D` — is likewise dropped, and the numeral names the upper chord alone.
388
+ * Roman-numeral notation spends its slash on applied chords (`V7/V`), so there
389
+ * is no unambiguous way to write such a bass; a caller that must keep it should
390
+ * read `chord.bassPc` alongside the numeral.
391
+ *
392
+ * A chord sounding one of the augmented sixths over an explicit bass on the
393
+ * lowered submediant renders as `It6`, `Fr6` or `Ger6`, since no numeral names
394
+ * those chords; see {@link augmentedSixthKind} for exactly when that applies.
395
+ *
396
+ * @param chord The chord to name, as a chord symbol, chord data, or a `Chord`.
397
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
398
+ * @param opts `applied` renders tonicizing chords as `V7/V`-style numerals;
399
+ * `neapolitan` renders a first-inversion Neapolitan as `N6`.
400
+ * @returns The Roman numeral string.
401
+ * @example
402
+ * ```ts
403
+ * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';
404
+ * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)
405
+ * chordToRoman(makeChord(2, 'dom7'), majorKey(0), { applied: true }); // => 'V7/V'
406
+ * ```
407
+ * @see {@link explainRoman} for the same numeral with the reasoning behind it.
408
+ * @category Functional Harmony
409
+ */
410
+ declare function chordToRoman(chord: ChordLike, key: KeyLike, opts?: ChordToRomanOptions): string;
411
+ /**
412
+ * A numeral with the reasoning behind it.
413
+ *
414
+ * @category Functional Harmony
415
+ */
416
+ type RomanExplanation = {
417
+ /** The numeral, exactly as {@link chordToRoman} renders it. */
418
+ roman: string;
419
+ /** How the numeral was arrived at: the degree, the quality, and the bass. */
420
+ rationale: string;
421
+ /**
422
+ * The spellings the options turned down, empty unless the `alternatives`
423
+ * option of {@link ExplainRomanOptions} asked for them.
424
+ */
425
+ alternatives: RejectedCandidate[];
426
+ };
427
+ /**
428
+ * Options for {@link explainRoman}: {@link ChordToRomanOptions} plus the switch
429
+ * for the rival spellings.
430
+ *
431
+ * @category Functional Harmony
432
+ */
433
+ type ExplainRomanOptions = ChordToRomanOptions & {
434
+ /**
435
+ * Report the numerals the other option settings would have rendered, and why
436
+ * this one was rendered instead.
437
+ *
438
+ * Off by default: each rival costs a further rendering pass, and a caller
439
+ * that only wants the numeral explained should not pay for the ones it did
440
+ * not get.
441
+ *
442
+ * @defaultValue false
443
+ */
444
+ alternatives?: boolean;
445
+ };
446
+ /**
447
+ * Render a chord as a Roman numeral and say how the numeral was arrived at.
448
+ *
449
+ * The numeral is exactly what {@link chordToRoman} returns for the same
450
+ * arguments — this is that function with its reasoning attached, for teaching
451
+ * material and for any interface whose users argue with the analysis. The
452
+ * rationale names the degree the root was spelled as, the quality that set the
453
+ * numeral's case and suffix, and what became of the bass, including the two
454
+ * cases where a bass exists and no numeral can carry it.
455
+ *
456
+ * @param chord The chord to name, as a chord symbol, chord data, or a `Chord`.
457
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
458
+ * @param opts The rendering options of {@link chordToRoman}, plus
459
+ * `alternatives` to collect the spellings they turned down.
460
+ * @returns The numeral, its rationale, and the rejected spellings.
461
+ * @example
462
+ * ```ts
463
+ * import { explainRoman, makeChord, majorKey } from '@libraz/libcantus';
464
+ * explainRoman(makeChord(7, 'dom7'), majorKey(0)).rationale;
465
+ * // 'V7: the root is the fifth degree of the key, the case and suffix come from the dom7 quality, and the chord stands on its own root'
466
+ * const applied = explainRoman(makeChord(2, 'dom7'), majorKey(0), { alternatives: true });
467
+ * applied.alternatives?.map((rejected) => rejected.label); // ['V7/V']
468
+ * ```
469
+ * @category Functional Harmony
470
+ */
471
+ declare function explainRoman(chord: ChordLike, key: KeyLike, opts?: ExplainRomanOptions): RomanExplanation;
472
+
473
+ /**
474
+ * Harmonic function (tonic / subdominant / dominant), diatonicity, parallel-key
475
+ * mirroring, and full chord analysis.
476
+ *
477
+ * Roots are pitch classes measured against the key tonic, so borrowed and
478
+ * chromatic chords are handled by their semitone offset rather than requiring a
479
+ * spelled key signature.
480
+ */
481
+
482
+ /**
483
+ * The three broad harmonic functions of tonal music.
484
+ *
485
+ * @category Functional Harmony
486
+ */
487
+ type HarmonicFunction = 'tonic' | 'subdominant' | 'dominant';
488
+ /**
489
+ * Whether a key's scale has a minor third and no major third (a minor key).
490
+ *
491
+ * @param key The key to test, as a key name, a key/scale, or a `Key`.
492
+ * @returns True if the key's scale is a minor one.
493
+ * @category Functional Harmony
494
+ */
495
+ declare function isMinorKey(key: KeyLike): boolean;
496
+ /**
497
+ * The harmonic function of a chord in a key.
498
+ *
499
+ * The root's offset above the tonic gives the baseline mapping, which follows
500
+ * common-practice major-key function and is a useful approximation in minor and
501
+ * for borrowed chords. Chord quality then refines it where the sonority settles
502
+ * an ambiguity the root offset cannot:
503
+ *
504
+ * - A chord that *sounds* like a dominant — a major third with a minor seventh,
505
+ * or a diminished-family chord — and is not diatonic to the key has dominant
506
+ * function when its root resolves down a fifth or by a semitone onto a
507
+ * diatonic degree. This is what makes an applied dominant (`A7` in C, which
508
+ * tonicizes ii) and its tritone substitute (`Db7`) read as dominant rather
509
+ * than inheriting the function of the degree they happen to sit on.
510
+ * - The Neapolitan is subdominant.
511
+ * - An augmented sixth is subdominant. All three are altered predominants that
512
+ * resolve outward onto the dominant, so they take subdominant function even
513
+ * though the German sixth sounds a dominant seventh and would otherwise read
514
+ * as the tritone substitute it shares its pitch classes with.
515
+ * - A major triad on bVI or bVII of a major key is subdominant — the borrowed
516
+ * pop cadence chord, distinct from the bVII7 above, which has a seventh and
517
+ * is a dominant sonority.
518
+ *
519
+ * @param chord The chord, as a chord symbol, chord data, or a `Chord`.
520
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
521
+ * @returns The harmonic function.
522
+ * @example
523
+ * ```ts
524
+ * import { functionOf, makeChord, majorKey } from '@libraz/libcantus';
525
+ * functionOf(makeChord(9, 'dom7'), majorKey(0)); // 'dominant' — A7 tonicizes ii
526
+ * functionOf('Bb', 'C major'); // 'subdominant' — borrowed bVII
527
+ * ```
528
+ * @category Functional Harmony
529
+ */
530
+ declare function functionOf(chord: ChordLike, key: KeyLike): HarmonicFunction;
531
+ /**
532
+ * The result of {@link analyzeChord}: function, borrowing, and Roman numeral.
533
+ *
534
+ * @category Functional Harmony
535
+ */
536
+ type ChordAnalysis = {
537
+ function: HarmonicFunction;
538
+ borrowed: boolean;
539
+ source: BorrowedSource;
540
+ roman: string;
541
+ /**
542
+ * Why the chord reads this way: the rule that settled its function, and where
543
+ * it was borrowed from when it was borrowed. Always present, in the phrasing
544
+ * {@link analyzeVoice} uses for a note.
545
+ */
546
+ rationale: string;
547
+ /**
548
+ * The readings that were considered and rejected, empty unless the
549
+ * `alternatives` option of {@link AnalyzeChordOptions} asked for them.
550
+ */
551
+ alternatives: RejectedCandidate[];
552
+ };
553
+ /**
554
+ * Options for {@link analyzeChord}: {@link ChordToRomanOptions} plus the switch
555
+ * for the rival readings.
556
+ *
557
+ * @category Functional Harmony
558
+ */
559
+ type AnalyzeChordOptions = ChordToRomanOptions & {
560
+ /**
561
+ * Report the readings this analysis turned down: the function the chord's
562
+ * degree alone would have carried, the tonicizing reading a dominant sonority
563
+ * could have had, and the numerals the other rendering options would emit.
564
+ *
565
+ * Off by default. The rationale is built from facts the analysis already
566
+ * established, but a rival costs work nothing else needs — a second and third
567
+ * numeral rendering among them — and most callers read the conclusion only.
568
+ *
569
+ * @defaultValue false
570
+ */
571
+ alternatives?: boolean;
572
+ };
573
+ /**
574
+ * Whether every pitch class of a chord belongs to the key's scale.
575
+ *
576
+ * The test is strict against the key's own mode mask: in a natural-minor key
577
+ * the harmonic-minor dominant (major V) is *not* diatonic, since the raised
578
+ * leading tone lies outside the mask. Borrowing predicates treat that case as
579
+ * an in-key alteration separately (see {@link isBorrowedChord}).
580
+ *
581
+ * @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.
582
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
583
+ * @returns True if all chord pitch classes are scale tones.
584
+ * @category Functional Harmony
585
+ */
586
+ declare function isDiatonic(chord: ChordLike, key: KeyLike): boolean;
587
+ /**
588
+ * Analyze a chord in a key: harmonic function, borrowing, and Roman numeral.
589
+ *
590
+ * The function is quality-aware (see the predicates behind it), the source
591
+ * follows {@link borrowedSource}, and the numeral comes from
592
+ * {@link chordToRoman}. `borrowed` is true whenever a source is identified,
593
+ * which includes the Neapolitan: it belongs to neither parallel mode, and both
594
+ * this and {@link isBorrowedChord} count it as a borrowing all the same.
595
+ *
596
+ * The `rationale` says which of those rules settled the function, so a reader
597
+ * who disagrees can see what the reading rests on. `alternatives` is empty
598
+ * unless asked for, and then names the readings that were turned down.
599
+ *
600
+ * @param chord The chord to analyze, as a chord symbol, chord data, or a
601
+ * `Chord`.
602
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
603
+ * @param opts Options used when rendering the Roman numeral, plus
604
+ * `alternatives` to collect the rejected readings.
605
+ * @returns The chord analysis.
606
+ * @example
607
+ * ```ts
608
+ * import { analyzeChord, makeChord, majorKey } from '@libraz/libcantus';
609
+ * analyzeChord(makeChord(7, 'dom7'), majorKey(0));
610
+ * // { function: 'dominant', borrowed: false, source: null, roman: 'V7', ... }
611
+ * analyzeChord(makeChord(9, 'dom7'), majorKey(0), { alternatives: true }).alternatives;
612
+ * // [{ label: 'tonic by scale degree', reason: '...' }, ...]
613
+ * ```
614
+ * @category Functional Harmony
615
+ */
616
+ declare function analyzeChord(chord: ChordLike, key: KeyLike, opts?: AnalyzeChordOptions): ChordAnalysis;
617
+ /**
618
+ * The secondary dominant (V7) that tonicizes a scale degree.
619
+ *
620
+ * @param targetDegree 1-based scale degree to tonicize: 5 is the dominant, so
621
+ * `secondaryDominant(5, majorKey(0))` is the V of V.
622
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
623
+ * @returns A dominant-seventh chord a fifth above the target's root.
624
+ * @throws If `targetDegree` is not an integer naming a degree in `key`.
625
+ * @see {@link secondaryDominantOf} to tonicize a chord that has no degree in
626
+ * the key, or when no key is at hand.
627
+ * @category Functional Harmony
628
+ */
629
+ declare function secondaryDominant(targetDegree: number, key: KeyLike): Chord;
630
+ /**
631
+ * The secondary dominant (V7) that tonicizes a chord.
632
+ *
633
+ * {@link secondaryDominant} names its target by scale degree, so it only
634
+ * reaches the degrees a key actually has. This takes the target chord itself
635
+ * and needs no key at all, which is what lets a borrowed or chromatic target —
636
+ * bVI of a major key, a Neapolitan, any chord a modulation left behind — have
637
+ * its dominant built without first inventing a degree for it.
638
+ *
639
+ * A `rootSpelling` hint on the target moves with the root, so a flat-named
640
+ * target yields a flat-named dominant (Eb gives Bb7, not A#7).
641
+ *
642
+ * @param target The chord to tonicize, as a chord symbol, chord data, or a
643
+ * `Chord`.
644
+ * @returns A dominant-seventh chord a perfect fifth above the target's root.
645
+ * @throws If the target's root pitch class is not a finite number.
646
+ * @example
647
+ * ```ts
648
+ * import { makeChord, secondaryDominantOf } from '@libraz/libcantus';
649
+ * secondaryDominantOf(makeChord(8, 'maj'));
650
+ * // { rootPc: 3, quality: 'dom7' } — Eb7 tonicizes the borrowed bVI of C major
651
+ * ```
652
+ * @category Functional Harmony
653
+ */
654
+ declare function secondaryDominantOf(target: ChordLike): Chord;
655
+
656
+ /**
657
+ * Pivot chords: the common ground two keys share for a modulation.
658
+ *
659
+ * Roots are pitch classes measured against the key tonic, so the two readings
660
+ * of a pivot follow from the chord's degree in each key rather than requiring a
661
+ * spelled key signature.
662
+ */
663
+
664
+ /**
665
+ * A chord that is diatonic to two keys at once, with its reading in each.
666
+ *
667
+ * @category Functional Harmony
668
+ */
669
+ type PivotChord = {
670
+ /** The shared chord. */
671
+ chord: Chord;
672
+ /** Roman numeral in the key being left. */
673
+ romanFrom: string;
674
+ /** Roman numeral in the key being entered. */
675
+ romanTo: string;
676
+ };
677
+ /**
678
+ * List the chords a modulation from `from` to `to` can pivot on.
679
+ *
680
+ * A pivot chord belongs to both keys at once, so it can be heard as a degree of
681
+ * the key being left and reinterpreted as a degree of the key being entered —
682
+ * the seamless way into the new key. Candidates are the diatonic triads of
683
+ * `from`, degrees 1 through 7, kept when every one of their pitch classes is
684
+ * also a scale tone of `to`; the result is ordered by their degree in `from`.
685
+ *
686
+ * Only triads are candidates. The textbook pivot is a triad: a seventh narrows
687
+ * the shared ground by a whole tone for no analytical gain, and the chord that
688
+ * turns the modulation is heard as a triad whether or not a seventh is played
689
+ * over it.
690
+ *
691
+ * A key that is not heptatonic has no diatonic triads at all — stacking scale
692
+ * thirds only lands on a triad over seven degrees — so such a `from` key yields
693
+ * an empty list rather than an error. A `to` key is only tested for membership,
694
+ * so it may be any scale; its numerals then follow the {@link chordToRoman}
695
+ * fallback of naming roots against the parallel major.
696
+ *
697
+ * @param from The key being left, as a key name, a key/scale, or a `Key`.
698
+ * @param to The key being entered, in any of the same forms.
699
+ * @returns The shared triads, ascending by their degree in `from`.
700
+ * @example
701
+ * ```ts
702
+ * import { majorKey, pivotChords } from '@libraz/libcantus';
703
+ * pivotChords(majorKey(0), majorKey(7)).map((p) => `${p.romanFrom}=${p.romanTo}`);
704
+ * // ['I=IV', 'iii=vi', 'V=I', 'vi=ii'] — C major into G major
705
+ * ```
706
+ * @category Functional Harmony
707
+ */
708
+ declare function pivotChords(from: KeyLike, to: KeyLike): PivotChord[];
709
+
710
+ /**
711
+ * The key as it changes over time.
712
+ *
713
+ * A piece that modulates has no single key, so a whole-piece answer is wrong
714
+ * for every bar after the modulation. This module answers instead with a run of
715
+ * regions, each carrying the key in force over one span, how it relates to the
716
+ * key before it, and — when chords are available — the chord the modulation
717
+ * turned on.
718
+ */
719
+
720
+ /**
721
+ * A span of the piece over which one key is in force.
722
+ *
723
+ * @category Arrangement & Analysis
724
+ */
725
+ type KeyRegion = {
726
+ /** First beat of the region. */
727
+ startBeat: number;
728
+ /** End of the region, exclusive. */
729
+ endBeat: number;
730
+ /** The key in force across the region. */
731
+ key: KeyScale;
732
+ /**
733
+ * How well the region's music fits the key, in [0, 1]: the correlation
734
+ * between the span's pitch-class distribution and the key's profile, with
735
+ * negative correlations reported as 0.
736
+ *
737
+ * One statistic whichever entry point built the region, so a threshold or a
738
+ * comparison means the same thing across them. Where the region came from
739
+ * chords rather than notes, the distribution is each chord's tones weighted
740
+ * by how long the chord holds.
741
+ */
742
+ confidence: number;
743
+ /**
744
+ * How this key relates to the previous region's — `'dominant'` for a move to
745
+ * the dominant, `'relative'` for a move to the relative minor, and so on.
746
+ * Absent on the first region, and absent when the two keys stand in none of
747
+ * the named relations.
748
+ *
749
+ * Never `'same'`: a region is the maximal span of one key, so consecutive
750
+ * regions never share one and a region carrying this field is a place the
751
+ * piece moved.
752
+ */
753
+ modulation?: KeyRelation;
754
+ /**
755
+ * The chord the modulation turned on, when one was identified. Only the
756
+ * chord-aware entry points fill this in: a pivot is a chord that reads in
757
+ * both keys, so it cannot be named without knowing which chord sounded.
758
+ */
759
+ pivot?: PivotChord;
760
+ };
761
+ /**
762
+ * Options shared by the key-region entry points.
763
+ *
764
+ * @category Arrangement & Analysis
765
+ */
766
+ type KeyTimelineOptions = {
767
+ /**
768
+ * A single time signature held across the whole span, as sugar for a
769
+ * one-element `meters`; defaults to 4/4. Giving both is an input error.
770
+ *
771
+ * @defaultValue `4/4`
772
+ */
773
+ ts?: TimeSignature;
774
+ /**
775
+ * The meter as it changes over the span. Bar lines, downbeats and the metric
776
+ * accents the search discounts a modulation by all follow the signature in
777
+ * force at the beat in question, so a piece that changes meter is not read in
778
+ * the one it opened in.
779
+ *
780
+ * @defaultValue 4/4 throughout
781
+ */
782
+ meters?: MeterMap;
783
+ /**
784
+ * Expected length of one key area in beats; defaults to four bars of the
785
+ * opening meter.
786
+ *
787
+ * This is a prior, not a window: the longer a key is expected to hold, the
788
+ * more evidence a modulation needs before the search will report one. Keys
789
+ * change far more slowly than chords do, which is why the default is measured
790
+ * in bars rather than beats.
791
+ *
792
+ * @defaultValue four bars of the opening meter
793
+ */
794
+ expectedKeyBeats?: number;
795
+ /**
796
+ * Shortest key area the search may report, in beats; defaults to one bar of
797
+ * the opening meter. Values above `expectedKeyBeats` are clamped to it.
798
+ *
799
+ * @defaultValue one bar of the opening meter
800
+ */
801
+ minKeyBeats?: number;
802
+ /**
803
+ * End of the analyzed span in beats; defaults to the end of the last note or
804
+ * chord segment.
805
+ *
806
+ * @defaultValue the end of the input
807
+ */
808
+ totalBeats?: number;
809
+ /**
810
+ * Key profile the regions are scored against; defaults to `'krumhansl'`.
811
+ * Same meaning as the option of the same name on the key detectors.
812
+ *
813
+ * Every entry point reports `confidence` as the correlation between a
814
+ * region's pitch-class distribution and this profile, so the option is what
815
+ * that number is measured against. Which keys {@link detectModulations}
816
+ * reports is settled by how each chord reads in a key rather than by a
817
+ * pitch-class profile, so there the profile scores the answer without
818
+ * choosing it.
819
+ *
820
+ * @defaultValue `'krumhansl'`
821
+ */
822
+ profile?: KeyProfileName | KeyProfilePair;
823
+ /**
824
+ * Upper bound on the work this call may do.
825
+ *
826
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
827
+ */
828
+ budget?: number;
829
+ };
830
+ /**
831
+ * Infer where the key changes from raw notes.
832
+ *
833
+ * The span is examined in `minKeyBeats` slots. Each slot's pitch-class
834
+ * distribution is correlated against all 24 major and minor key profiles, and
835
+ * the run of keys that best explains the piece is chosen, paying a cost per
836
+ * modulation that rises with the distance travelled around the circle of
837
+ * fifths and falls on strong beats. Adjacent slots agreeing on a key become one
838
+ * region.
839
+ *
840
+ * The metric accents that discount a modulation follow the signature in force
841
+ * at the beat in question, so a piece that changes meter is read in the meter
842
+ * it is actually in. The first downbeat is beat 0, so a pickup is written at
843
+ * negative beats and its notes argue for the key like any others rather than
844
+ * falling outside the span.
845
+ *
846
+ * Pivot chords are not reported here: naming one requires knowing which chord
847
+ * sounded at the boundary, which raw notes do not say. Use
848
+ * {@link detectModulations} when a chord timeline is in hand.
849
+ *
850
+ * @param notes The notes to analyze (any number of tracks, flattened).
851
+ * @param opts Analysis options; see {@link KeyTimelineOptions}.
852
+ * @returns The key regions in time order; empty when nothing sounds.
853
+ * @example
854
+ * ```ts
855
+ * import { keyTimelineFromNotes } from '@libraz/libcantus';
856
+ * const notes = [
857
+ * { pitch: 60, startBeat: 0, durationBeat: 4 },
858
+ * { pitch: 64, startBeat: 4, durationBeat: 4 },
859
+ * ];
860
+ * const regions = keyTimelineFromNotes(notes);
861
+ * regions[0]?.key; // the key in force at the start
862
+ * ```
863
+ * @category Arrangement & Analysis
864
+ */
865
+ declare function keyTimelineFromNotes(notes: readonly NoteEvent[], opts?: KeyTimelineOptions): KeyRegion[];
866
+ /**
867
+ * Infer where the key changes from a chord sequence, naming the pivot chords.
868
+ *
869
+ * Each chord segment is one slot of the same search {@link keyTimelineFromNotes}
870
+ * runs, scored by how well the chord belongs to each key rather than by a
871
+ * pitch-class profile — a dominant seventh argues for its key far more strongly
872
+ * than its three or four pitch classes alone would suggest. Where the key
873
+ * changes, the chord sounding into the boundary is reported as the pivot if it
874
+ * reads in both keys.
875
+ *
876
+ * The chords supply the slots, so `minKeyBeats` cannot size them as it does in
877
+ * the note path; a region shorter than it is folded into the neighbouring key
878
+ * that reads its chords best instead, which is the same guarantee — a shorter
879
+ * region is reported only where the analyzed span itself ends, or across a gap
880
+ * in the chords that no neighbour adjoins. `profile` scores each region's
881
+ * confidence, as it does in the note path, but takes no part in choosing which
882
+ * keys are reported: that is settled by how the chords read.
883
+ *
884
+ * @param chords The chord segments to analyze, in time order; pass a chord
885
+ * timeline's `segments`.
886
+ * @param opts Analysis options; see {@link KeyTimelineOptions}.
887
+ * @returns The key regions in time order; empty when no chords were given.
888
+ * @example
889
+ * ```ts
890
+ * import { chordTimelineFromNotes, detectModulations } from '@libraz/libcantus';
891
+ * const notes = [
892
+ * { pitch: 60, startBeat: 0, durationBeat: 4 },
893
+ * { pitch: 64, startBeat: 0, durationBeat: 4 },
894
+ * { pitch: 67, startBeat: 0, durationBeat: 4 },
895
+ * ];
896
+ * const { timeline } = chordTimelineFromNotes(notes);
897
+ * const regions = detectModulations(timeline.segments);
898
+ * ```
899
+ * @category Arrangement & Analysis
900
+ */
901
+ declare function detectModulations(chords: readonly ChordSegment[], opts?: KeyTimelineOptions): KeyRegion[];
902
+ /**
903
+ * The single key that holds for most of a run of regions.
904
+ *
905
+ * Callers that only want one key — a key signature to print, a scale to offer —
906
+ * need the one the piece spends the most time in, not the one it happens to
907
+ * start on.
908
+ *
909
+ * @param regions The regions to weigh, as returned by the key-region entry points.
910
+ * @returns The longest-held key, or null when there are no regions.
911
+ * @example
912
+ * ```ts
913
+ * import { keyTimelineFromNotes, prevailingKeyOf } from '@libraz/libcantus';
914
+ * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
915
+ * const key = prevailingKeyOf(keyTimelineFromNotes(notes));
916
+ * ```
917
+ * @category Arrangement & Analysis
918
+ */
919
+ declare function prevailingKeyOf(regions: readonly KeyRegion[]): KeyScale | null;
920
+
921
+ /**
922
+ * A suspension figure, named by the interval above the bass and the interval it
923
+ * resolves to.
924
+ *
925
+ * A figure is reported only when the suspended note forms the interval the
926
+ * figure names. A dissonance that resolves by step from anywhere else — a
927
+ * retardation rising a semitone from the seventh to the octave, say — is left
928
+ * unfigured rather than named after an interval it does not form.
929
+ *
930
+ * @category Arrangement & Analysis
931
+ */
932
+ type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';
933
+ /**
934
+ * A theory annotation attached to a note.
935
+ *
936
+ * @category Arrangement & Analysis
937
+ */
938
+ type TheoryLabel = {
939
+ kind: 'chordTone';
940
+ role: ChordToneRole;
941
+ } | {
942
+ kind: 'tension';
943
+ degree: 9 | 11 | 13;
944
+ } | {
945
+ kind: 'avoid';
946
+ } | {
947
+ kind: 'passing';
948
+ } | {
949
+ kind: 'neighbor';
950
+ } | {
951
+ kind: 'suspension';
952
+ type: SuspensionFigure;
953
+ resolveTo: number;
954
+ }
955
+ /**
956
+ * A note leant on: reached by leap and given up by step the other way, with
957
+ * `resolveTo` naming the pitch it gives way to. The word is the one
958
+ * {@link MelodyToneRole} uses for the same figure, so a melody read through
959
+ * this layer and through the harmonizer comes back under one vocabulary
960
+ * rather than under two names for one note.
961
+ */
962
+ | {
963
+ kind: 'appoggiatura';
964
+ resolveTo: number;
965
+ } | {
966
+ kind: 'anticipation';
967
+ } | {
968
+ kind: 'escape';
969
+ } | {
970
+ kind: 'needsResolution';
971
+ resolveTo: number;
972
+ } | {
973
+ kind: 'leadingTone';
974
+ resolveTo: number;
975
+ };
976
+ /**
977
+ * A note with its theory labels and a short rationale.
978
+ *
979
+ * The labels describe the note against the chord sounding at its **onset**;
980
+ * they are not re-derived for the later chords a long note sustains across.
981
+ * `analyzeArrangement` does re-check every crossed chord change, and reports
982
+ * what it finds as conflicts, so a note labelled a chord tone here can still
983
+ * appear in `conflicts` at a later beat.
984
+ *
985
+ * The identity fields make an annotation mappable back to the caller's input:
986
+ * `originalIndex` is the note's position in the array that was passed in, and
987
+ * `trackIndex` is set by {@link analyzeArrangement}.
988
+ *
989
+ * @category Arrangement & Analysis
990
+ */
991
+ type AnalyzedNote = {
992
+ noteId: number;
993
+ /** The note's index in the caller's own array, when the caller supplied one. */
994
+ originalIndex?: number;
995
+ /** The note's track, set by `analyzeArrangement`. */
996
+ trackIndex?: number;
997
+ pitch: number;
998
+ startBeat: number;
999
+ durationBeat: number;
1000
+ labels: TheoryLabel[];
1001
+ rationale?: string;
1002
+ };
1003
+ /**
1004
+ * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,
1005
+ * optionally, its index in the caller's original array.
1006
+ *
1007
+ * @category Arrangement & Analysis
1008
+ */
1009
+ type VoiceNote = NoteEvent & {
1010
+ id?: number;
1011
+ originalIndex?: number;
1012
+ };
1013
+ /**
1014
+ * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and
1015
+ * {@link analyzeArrangement} works in.
1016
+ *
1017
+ * @category Arrangement & Analysis
1018
+ */
1019
+ type IdentifiedVoiceNote = VoiceNote & {
1020
+ id: number;
1021
+ };
1022
+ /**
1023
+ * A key that applies to the whole voice, or the key in force at a given beat.
1024
+ *
1025
+ * The callback form is what a piece that modulates needs: it is asked for the
1026
+ * key at a specific beat, the same way a chord is asked for with `chordAtBeat`.
1027
+ *
1028
+ * @example
1029
+ * ```ts
1030
+ * import { majorKey } from '@libraz/libcantus';
1031
+ * const wholePiece = majorKey(0); // C major throughout
1032
+ * const modulating = (beat: number) => (beat < 8 ? majorKey(5) : majorKey(0));
1033
+ * ```
1034
+ * @category Arrangement & Analysis
1035
+ */
1036
+ type KeyContext = KeyScale | ((beat: number) => KeyScale);
1037
+ /**
1038
+ * Give plain note events the ids {@link analyzeVoice} reports back.
1039
+ *
1040
+ * The id is only an identity handle, so the array position serves; this exists
1041
+ * so the mapping is a named call rather than a `map` every caller writes.
1042
+ *
1043
+ * @param events The note events, in time order.
1044
+ * @returns The same notes as voice notes, each carrying its index as its id.
1045
+ * @category Arrangement & Analysis
1046
+ */
1047
+ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[];
1048
+ /**
1049
+ * Label every note of a voice with its theory roles.
1050
+ *
1051
+ * Each note is classified against the chord sounding at its beat. Chord tones
1052
+ * get a role label; non-chord tones are matched, in order, as suspensions
1053
+ * (prepared by an identical consonant pitch and resolving by step), passing
1054
+ * tones, neighbors, anticipations, appoggiaturas (approached by leap and
1055
+ * resolving by step the other way), and escape tones, then fall back to
1056
+ * tension, avoid, or an unresolved-dissonance label. The figures are named as
1057
+ * {@link classifyMelodyTones} names them, so a caller reading a melody through
1058
+ * both layers gets one vocabulary; that classifier weighs the metre as well,
1059
+ * which this one is not given, so it is the stricter of the two. Leading-tone resolutions are noted
1060
+ * additionally, judged against the key in force at the beat the resolution
1061
+ * lands on, so a modulation is heard from its new tonic.
1062
+ *
1063
+ * The voice is expected to be monophonic (one note at a time). Notes sharing an
1064
+ * onset are treated as simultaneous cluster members, not melodic neighbors, so
1065
+ * they receive no melodic labels (suspension, passing, neighbor, anticipation,
1066
+ * escape) — only harmonic ones. Callers with truly polyphonic material should
1067
+ * split it into monophonic sub-voices first, as `analyzeArrangement` does.
1068
+ *
1069
+ * @param voice The monophonic voice, in time order. A note without an `id` is
1070
+ * identified by its position, so plain note events can be passed straight in.
1071
+ * @param chordAtBeat Chord sounding at a given beat, or null.
1072
+ * @param key Key context for leading-tone detection: a single {@link KeyScale}
1073
+ * covering the whole voice, or a callback giving the key in force at a given
1074
+ * beat, for music that modulates.
1075
+ * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
1076
+ * none, which is the whole story for a solo line.
1077
+ * @returns One annotation per input note.
1078
+ * @example
1079
+ * ```ts
1080
+ * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
1081
+ * const voice = [
1082
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1083
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
1084
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
1085
+ * ];
1086
+ * const cMajor = makeChord(0, 'maj');
1087
+ * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));
1088
+ * labels; // one AnalyzedNote per input note, in the same order
1089
+ * // A voice that modulates to C major at beat 2:
1090
+ * analyzeVoice(voice, () => cMajor, (beat) => (beat < 2 ? majorKey(5) : majorKey(0)));
1091
+ * ```
1092
+ * @category Arrangement & Analysis
1093
+ */
1094
+ declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyContext, otherVoicesAtBeat?: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
1095
+
1096
+ /**
1097
+ * A beat-indexed sequence of chord segments.
1098
+ *
1099
+ * @category Arrangement & Analysis
1100
+ */
1101
+ type ChordTimeline = {
1102
+ /** The chord sounding at a beat, or null when no segment covers it. */
1103
+ at: (beat: number) => Chord | null;
1104
+ segments: ChordSegment[];
1105
+ };
1106
+ /**
1107
+ * Build a chord timeline from placed chords.
1108
+ *
1109
+ * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last
1110
+ * chord runs to `totalBeats`. Segments with no positive length (from duplicate
1111
+ * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns
1112
+ * the covering segment's chord, or null when the beat lies outside every segment.
1113
+ *
1114
+ * @param chords Placed chords in time order.
1115
+ * @param totalBeats End of the timeline in beats.
1116
+ * @returns A queryable chord timeline.
1117
+ * @category Arrangement & Analysis
1118
+ */
1119
+ declare function chordTimelineFromChords(chords: readonly ChordSpan[], totalBeats: number): ChordTimeline;
1120
+ /**
1121
+ * How {@link chordTimelineFromNotes} decides where one chord ends and the next
1122
+ * begins.
1123
+ *
1124
+ * `'dynamic'` searches for the boundaries the notes actually imply, so a bar
1125
+ * holding two chords yields two segments and a bar holding one yields one.
1126
+ * `'grid'` cuts a segment every `harmonicRhythm` beats regardless of what
1127
+ * sounds, which is only right when the harmonic rhythm is known to be fixed.
1128
+ *
1129
+ * @category Arrangement & Analysis
1130
+ */
1131
+ type ChordSegmentation = 'dynamic' | 'grid';
1132
+ /**
1133
+ * Options controlling {@link chordTimelineFromNotes}.
1134
+ *
1135
+ * @category Arrangement & Analysis
1136
+ */
1137
+ type ChordTimelineOptions = {
1138
+ /**
1139
+ * Key context held across the whole span. Omit it to have the key searched
1140
+ * for over time with {@link keyTimelineFromNotes}, which is what lets a piece
1141
+ * that modulates be analysed against the key actually in force.
1142
+ */
1143
+ key?: KeyScale;
1144
+ /**
1145
+ * A single time signature held across the whole span, as sugar for a
1146
+ * one-element `meters`; defaults to 4/4. Giving both is an input error.
1147
+ *
1148
+ * @defaultValue `4/4`
1149
+ */
1150
+ ts?: TimeSignature;
1151
+ /**
1152
+ * The meter as it changes over the span. Bar lines, downbeats and metric
1153
+ * weight all follow the signature in force at the beat in question, so a
1154
+ * piece that changes meter is not read in the one it opened in.
1155
+ *
1156
+ * @defaultValue 4/4 throughout
1157
+ */
1158
+ meters?: MeterMap;
1159
+ /**
1160
+ * Length of the pickup in beats, when the piece starts with one.
1161
+ *
1162
+ * The first downbeat is beat 0 whatever precedes it, so an upbeat is written
1163
+ * at negative beats — a one-beat pickup in 4/4 sounds at beat -1. Declaring
1164
+ * its length rejects a note that starts before the pickup does; leave it
1165
+ * unset to accept any finite onset.
1166
+ *
1167
+ * @defaultValue no declared pickup
1168
+ */
1169
+ pickupBeats?: number;
1170
+ /**
1171
+ * Where chord boundaries may fall; defaults to `'dynamic'`.
1172
+ *
1173
+ * @defaultValue `'dynamic'`
1174
+ */
1175
+ segmentation?: ChordSegmentation;
1176
+ /**
1177
+ * Expected chord length in beats; defaults to the length of the opening bar.
1178
+ *
1179
+ * Under `'grid'` segmentation this is the exact window length. Under
1180
+ * `'dynamic'` it is a prior: the longer a chord is expected to last, the more
1181
+ * evidence a change needs before the search will place one.
1182
+ *
1183
+ * @defaultValue the length of the opening bar
1184
+ */
1185
+ harmonicRhythm?: number;
1186
+ /**
1187
+ * Shortest chord the `'dynamic'` search may report, in beats; defaults to one
1188
+ * beat. Lower it to catch changes on off-beats, at the cost of proportionally
1189
+ * more work. Values above `harmonicRhythm` are clamped to it, and the option
1190
+ * is ignored under `'grid'` segmentation.
1191
+ *
1192
+ * The search reads it as a resolution rather than as an exact slot length: it
1193
+ * rounds down to a whole division of `harmonicRhythm`, so `0.6` against a
1194
+ * four-beat chord is read in sevenths of it. That keeps the beats one
1195
+ * expected chord gives way to the next on the grid whatever resolution is
1196
+ * asked for, and with them every change a coarser setting found.
1197
+ *
1198
+ * @defaultValue `1`
1199
+ */
1200
+ minChordBeats?: number;
1201
+ /**
1202
+ * End of the analyzed span in beats; defaults to the end of the last note.
1203
+ *
1204
+ * @defaultValue the end of the last note
1205
+ */
1206
+ totalBeats?: number;
1207
+ /**
1208
+ * Upper bound on the work this call may do — note counts, windows, and
1209
+ * candidate counts are each checked against it before anything is allocated.
1210
+ *
1211
+ * Raise it to analyse a piece larger than the default allows; the default is
1212
+ * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast
1213
+ * rather than blocking the thread.
1214
+ *
1215
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
1216
+ */
1217
+ budget?: number;
1218
+ };
1219
+ /**
1220
+ * The result of {@link chordTimelineFromNotes}.
1221
+ *
1222
+ * @category Arrangement & Analysis
1223
+ */
1224
+ type ChordTimelineResult = {
1225
+ /** The inferred timeline, with adjacent identical chords merged. */
1226
+ timeline: ChordTimeline;
1227
+ /**
1228
+ * The key regions the analysis ran against, in time order. A piece that does
1229
+ * not modulate yields one region covering the whole span; a given `key`
1230
+ * yields exactly one region too, since the caller has already answered the
1231
+ * question. Either way the first region starts where the analysis does, which
1232
+ * is before beat 0 when the piece opens with a pickup. Pivot chords are filled
1233
+ * in where the chords support one.
1234
+ */
1235
+ keys: KeyRegion[];
1236
+ /**
1237
+ * The key held longest across {@link ChordTimelineResult.keys} — the one to
1238
+ * print on a key signature or hand to a generator that takes a single key.
1239
+ */
1240
+ prevailingKey: KeyScale;
1241
+ /** One confidence value in [0, 1] per segment, in segment order. */
1242
+ segmentConfidence: number[];
1243
+ };
1244
+ /**
1245
+ * Infer a chord timeline from raw multi-track notes.
1246
+ *
1247
+ * Chord boundaries are searched for rather than assumed: the span is examined
1248
+ * in `minChordBeats` slots, and the change points that best explain the notes
1249
+ * are chosen, trading each slot's harmonic fit against a cost per chord change
1250
+ * that a strong beat discounts. A bar holding two chords therefore yields two
1251
+ * segments and a bar holding one yields one, without the caller having to know
1252
+ * the harmonic rhythm in advance. Pass `segmentation: 'grid'` to cut a segment
1253
+ * every `harmonicRhythm` beats instead.
1254
+ *
1255
+ * Each settled segment's chord is inferred from a pitch-class weight histogram
1256
+ * of the notes overlapping it (weight = overlap duration x velocity x
1257
+ * metric-accent bonus for onsets in the segment). Adjacent segments carrying
1258
+ * the identical chord are merged; a stretch with no notes produces no segment,
1259
+ * so `at(beat)` returns null there. Each segment carries a confidence in [0, 1]
1260
+ * — the fraction of its weight explained by chord tones, reduced when the match
1261
+ * is inexact, and duration-weighted across anything merged into it.
1262
+ *
1263
+ * Notes with a zero or negative duration never sound, so they are dropped at
1264
+ * ingest: they contribute to neither the key inference, the span, nor any
1265
+ * window's histogram.
1266
+ *
1267
+ * Bar lines, downbeats and the metric accents the boundary search discounts by
1268
+ * all follow the signature in force at the beat in question, so a piece that
1269
+ * changes meter is read in the meter it is actually in. The first downbeat is
1270
+ * beat 0, so a pickup is written at negative beats and the search still finds
1271
+ * the bar lines where the score has them.
1272
+ *
1273
+ * @param notes The notes to analyze (any number of tracks, flattened).
1274
+ * @param opts Analysis options; see {@link ChordTimelineOptions}.
1275
+ * @returns The inferred timeline, the key regions it ran against, the key that
1276
+ * prevails across them, and per-segment confidences.
1277
+ * @throws If `harmonicRhythm` is not positive.
1278
+ * @example
1279
+ * ```ts
1280
+ * import { chordTimelineFromNotes } from '@libraz/libcantus';
1281
+ * const notes = [
1282
+ * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C
1283
+ * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E
1284
+ * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G
1285
+ * ];
1286
+ * const { timeline, prevailingKey } = chordTimelineFromNotes(notes);
1287
+ * timeline.at(0); // the chord inferred over beat 0, or null
1288
+ * prevailingKey; // the key held longest across the piece
1289
+ * ```
1290
+ * @category Arrangement & Analysis
1291
+ */
1292
+ declare function chordTimelineFromNotes(notes: readonly NoteEvent[], opts?: ChordTimelineOptions): ChordTimelineResult;
1293
+ /**
1294
+ * A cadence found between two consecutive timeline segments.
1295
+ *
1296
+ * @category Arrangement & Analysis
1297
+ */
1298
+ type CadenceHit = {
1299
+ /** The beat where the cadence arrives (the second chord's onset). */
1300
+ atBeat: number;
1301
+ /**
1302
+ * The cadence formed by the pair. Its `type` is never null: a pair forming no
1303
+ * cadence is not a hit. A timeline names no voicing, so `strength` is null for
1304
+ * an authentic cadence whose chords are both in root position.
1305
+ */
1306
+ cadence: CadenceResult;
1307
+ from: Chord;
1308
+ to: Chord;
1309
+ };
1310
+ /**
1311
+ * Find cadences between consecutive segments of a chord timeline.
1312
+ *
1313
+ * Each temporally adjacent segment pair is classified with
1314
+ * {@link detectCadence}; pairs forming no cadence are skipped. Segments
1315
+ * separated by a gap (a rest in the timeline) are not a chord-to-chord
1316
+ * progression, so they are never paired.
1317
+ *
1318
+ * The segment before the pair is passed as the approach, which is what tells a
1319
+ * cadential six-four from an inverted tonic: `I I64 V I` is one cadence
1320
+ * arriving on the final tonic, described from the six-four the dominant was
1321
+ * already sounding under, rather than a half cadence onto the dominant
1322
+ * followed by an authentic one. A passing six-four — one whose dominant is not
1323
+ * what follows it — is unaffected, and so is a pair whose predecessor is
1324
+ * separated from it by a rest.
1325
+ *
1326
+ * @param timeline The chord timeline to scan.
1327
+ * @param key The prevailing key, or the key in force at a given beat.
1328
+ * @returns The cadences found, in time order.
1329
+ * @example
1330
+ * ```ts
1331
+ * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
1332
+ * const notes = [
1333
+ * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant
1334
+ * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic
1335
+ * ];
1336
+ * const { timeline, prevailingKey } = chordTimelineFromNotes(notes);
1337
+ * const cadences = detectCadences(timeline, prevailingKey);
1338
+ * ```
1339
+ * @category Arrangement & Analysis
1340
+ */
1341
+ declare function detectCadences(timeline: ChordTimeline, key: KeyContext): CadenceHit[];
1342
+
1343
+ export { type AnalyzeChordOptions as A, type BorrowedSource as B, type CadenceHit as C, type DetectCadenceOptions as D, type ExplainRomanOptions as E, isMinorKey as F, keyTimelineFromNotes as G, type HarmonicFunction as H, type IdentifiedVoiceNote as I, pivotChords as J, type KeyContext as K, prevailingKeyOf as L, romanToChord as M, secondaryDominant as N, secondaryDominantOf as O, type PivotChord as P, toVoiceNotes as Q, type RejectedCandidate as R, type SuspensionFigure as S, type TheoryLabel as T, type VoiceNote as V, type AnalyzedNote as a, type CadenceResult as b, type ChordAnalysis as c, type ChordSegmentation as d, type ChordTimeline as e, type ChordTimelineOptions as f, type ChordTimelineResult as g, type ChordToRomanOptions as h, type KeyProfileName as i, type KeyProfilePair as j, type KeyRegion as k, type KeyTimelineOptions as l, type RomanExplanation as m, analyzeChord as n, analyzeVoice as o, borrowedSource as p, chordTimelineFromChords as q, chordTimelineFromNotes as r, chordToRoman as s, detectCadence as t, detectCadences as u, detectModulations as v, explainRoman as w, functionOf as x, isBorrowedChord as y, isDiatonic as z };