@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,1804 @@
1
+ import { d as KeyScale, g as NoteEvent, S as SpelledInterval } from './index-B9MNZboQ.cjs';
2
+ import { d as ChordQuality, b as Chord, N as NoteSafety, S as SafetyProfile } from './index-C_8o--VE.cjs';
3
+ import { r as ScaleName } from './masks-DYbE485J.cjs';
4
+ import { i as KeyProfileName, j as KeyProfilePair, R as RejectedCandidate, k as KeyRegion, e as ChordTimeline, C as CadenceHit, a as AnalyzedNote, T as TheoryLabel, K as KeyContext, b as CadenceResult } from './index-Dzr13q3d.cjs';
5
+ import { T as TimeSignature, c as MeterMap, b as MeterLike } from './index-BkGmlyI3.cjs';
6
+
7
+ /**
8
+ * Church-mode candidates for {@link detectKey}.
9
+ *
10
+ * A mode is not a key with a different scale bolted on: it is the same tonal
11
+ * hierarchy — tonic strongest, dominant next, the remaining diatonic degrees
12
+ * behind them — heard over a scale in which one degree has been replaced. Its
13
+ * profile is therefore derived from the profile of the major or minor key it
14
+ * sits closest to, rather than being a table of its own.
15
+ */
16
+
17
+ /**
18
+ * A church mode {@link detectKey} can rank alongside the major and minor keys.
19
+ *
20
+ * Ionian and Aeolian are absent because they are the major and the natural minor
21
+ * scale: a piece in either is already reported by the plain major or minor
22
+ * candidate, and adding a second candidate over the same pitch classes and the
23
+ * same profile would only produce a tie with itself.
24
+ *
25
+ * @category Recognition
26
+ */
27
+ type ModalScaleName = 'dorian' | 'phrygian' | 'lydian' | 'mixolydian' | 'locrian';
28
+ /**
29
+ * How a modal candidate is built out of its parallel major or minor key.
30
+ *
31
+ * @category Recognition
32
+ */
33
+ type ModalCandidate = {
34
+ /** The name reported as {@link KeyMatch.scaleName} when this candidate wins. */
35
+ scaleName: ModalScaleName;
36
+ /** The major or minor key this mode is closest to, reported as `mode`. */
37
+ mode: 'major' | 'minor';
38
+ /** The 12-bit scale mask of the mode. */
39
+ mask: number;
40
+ /**
41
+ * Degree pairs the mode substitutes, as `[replaced, replacing]` semitone
42
+ * offsets above the tonic. Applying them to the parallel scale yields `mask`,
43
+ * and applying them to the parallel profile yields this candidate's profile.
44
+ */
45
+ substitutions: readonly (readonly [number, number])[];
46
+ };
47
+ /**
48
+ * Every mode {@link DetectKeyOptions.modes} accepts, in candidate order.
49
+ *
50
+ * @category Recognition
51
+ */
52
+ declare const MODAL_SCALE_NAMES: readonly ModalScaleName[];
53
+
54
+ /**
55
+ * Recognition: infer a chord name from a set of pitches, or a key from a set of
56
+ * pitch classes. This is the inverse direction of the chord/scale builders,
57
+ * which only go name -> notes.
58
+ */
59
+
60
+ /**
61
+ * A candidate chord interpretation of a pitch set.
62
+ *
63
+ * @category Recognition
64
+ */
65
+ type ChordMatch = {
66
+ rootPc: number;
67
+ quality: ChordQuality;
68
+ /** Chord tones absent from the input (an incomplete voicing). */
69
+ missingPcs: number[];
70
+ /** Input pitch classes not belonging to the chord. */
71
+ extraPcs: number[];
72
+ /** True when the input pitch-class set equals the chord exactly. */
73
+ exact: boolean;
74
+ /**
75
+ * Inversion implied by a known bass: 0 for root position, 1..n for an
76
+ * inversion. Null when no inversion can be named — either the input is an
77
+ * unordered pitch-class set with no bass, or the bass is not a chord tone (a
78
+ * pedal or passing bass), in which case `bassPc` still reports it.
79
+ */
80
+ inversion: number | null;
81
+ /** Bass pitch class when the lowest note is not the root. */
82
+ bassPc?: number;
83
+ };
84
+ /**
85
+ * Input interpretation for {@link detectChord} and {@link detectChordBest}.
86
+ *
87
+ * @category Recognition
88
+ */
89
+ type DetectChordOptions = {
90
+ /**
91
+ * `midi` uses the numerically lowest pitch as bass; `pitchClass` treats the
92
+ * input as unordered. `auto` (default) selects pitch-class mode only when all
93
+ * values lie in 0..11.
94
+ */
95
+ input?: 'auto' | 'midi' | 'pitchClass';
96
+ /** Explicit bass pitch class, including for an unordered pitch-class set. */
97
+ bassPc?: number;
98
+ };
99
+ /**
100
+ * A candidate key interpretation of a pitch-class set.
101
+ *
102
+ * @category Recognition
103
+ */
104
+ type KeyMatch = {
105
+ /** The scale that scored best for this tonic and mode, `variant` included. */
106
+ key: KeyScale;
107
+ /**
108
+ * The major or minor key this match is closest to. A modal match reports the
109
+ * mode it leans on — dorian, phrygian and locrian are minor, lydian and
110
+ * mixolydian major — so a caller that reads nothing but `mode` still gets a
111
+ * musically sane answer; `scaleName` is where the mode itself is named.
112
+ */
113
+ mode: 'major' | 'minor';
114
+ /**
115
+ * Which scale form `key` uses. Minor keys report whichever of the natural,
116
+ * harmonic and melodic masks covers the most input weight; the variant does
117
+ * not take part in ranking. A church mode reports `'modal'`, since its scale
118
+ * is neither of the major nor one of the three minor forms.
119
+ */
120
+ variant: KeyVariant;
121
+ /**
122
+ * The entry of `NAMED_SCALES` whose mask is `key.modeMask12`, naming the scale
123
+ * exactly where `mode` and `variant` only place it: `'major'`,
124
+ * `'naturalMinor'`, `'harmonicMinor'`, `'melodicMinor'`, or one of the church
125
+ * modes when {@link DetectKeyOptions.modes} put them in the running. Ionian
126
+ * and Aeolian are reported as `'major'` and `'naturalMinor'`, the names of the
127
+ * candidates they are the same scale as.
128
+ */
129
+ scaleName: ScaleName;
130
+ /**
131
+ * Fraction of the distinct input pitch classes that belong to `key`, in
132
+ * [0, 1]. Measured against the returned scale, so it always agrees with
133
+ * `isScaleTone(pc, match.key)`. This is a coverage figure, not the ranking:
134
+ * see `score`.
135
+ */
136
+ fit: number;
137
+ /**
138
+ * The value the results are ranked by, in [-1, 1]: the Pearson correlation
139
+ * between the weighted pitch-class distribution of the input and this
140
+ * candidate's key profile rotated onto its tonic. 1 is a distribution shaped
141
+ * exactly like the profile, 0 no relationship, negative an anti-correlation.
142
+ * Unlike `fit` it is a distribution measure, not a membership count, so where
143
+ * the weight falls among the scale degrees is what separates a key from its
144
+ * relative. Always a finite number; see {@link DetectKeyOptions.profile} for
145
+ * the degenerate case.
146
+ */
147
+ score: number;
148
+ /**
149
+ * Why this candidate scored what it did, in the phrasing
150
+ * {@link analyzeVoice} uses for a note.
151
+ *
152
+ * Present only when {@link DetectKeyOptions.explain} asked for it: a ranking
153
+ * is 24 candidates before the modes are counted, and most callers read the
154
+ * first one or two.
155
+ */
156
+ rationale?: string;
157
+ /**
158
+ * The candidates that were considered and rejected, and why each ranked
159
+ * below this one.
160
+ *
161
+ * Carried by the top-ranked match alone — the rivals of a runner-up are the
162
+ * rest of the list, which the caller already holds — and only when
163
+ * {@link DetectKeyOptions.explain} asked for it.
164
+ */
165
+ alternatives?: RejectedCandidate[];
166
+ };
167
+ /**
168
+ * Which form of a scale a {@link KeyMatch} settled on.
169
+ *
170
+ * @category Recognition
171
+ */
172
+ type KeyVariant = 'major' | 'natural' | 'harmonic' | 'melodic' | 'modal';
173
+ /**
174
+ * Input weighting for {@link detectKey}.
175
+ *
176
+ * @category Recognition
177
+ */
178
+ type DetectKeyOptions = {
179
+ /**
180
+ * How much each pitch counts toward the histogram, one entry per pitch.
181
+ * Defaults to one per pitch, which weighs a thirty-second-note ornament as
182
+ * heavily as the whole note under it. {@link detectKeyFromNotes} supplies
183
+ * duration times velocity, matching how chord inference weighs its own
184
+ * histogram.
185
+ */
186
+ weights?: readonly number[];
187
+ /**
188
+ * Which key profile ranks the candidates: the 12 degree weights a key is
189
+ * expected to distribute its music over, correlated against the distribution
190
+ * actually observed.
191
+ *
192
+ * `'krumhansl'` (the default) uses the Krumhansl–Kessler probe-tone ratings,
193
+ * `'temperley'` the Kostka–Payne corpus proportions, and `'flat'` a flat
194
+ * profile over the scale, which reduces the ranking to plain scale membership
195
+ * — the behaviour this detector had before profile correlation, kept as a way
196
+ * back for a caller that depends on it. A custom pair supplies two 12-entry
197
+ * vectors indexed from the tonic (index 0 = tonic, 1 = flat second, and so on
198
+ * to 11 = major seventh).
199
+ *
200
+ * When either the distribution or the rotated profile has zero variance the
201
+ * correlation is undefined; such a candidate is scored by the normalized dot
202
+ * product (cosine similarity) of the same two vectors instead, so a result
203
+ * never carries NaN.
204
+ *
205
+ * @defaultValue `'krumhansl'`
206
+ */
207
+ profile?: KeyProfileName | KeyProfilePair;
208
+ /**
209
+ * Whether the church modes join the 24 major and minor keys as candidates.
210
+ *
211
+ * Off by default, because a modal candidate can only change a ranking by
212
+ * outranking something: a caller working in common-practice repertoire, where
213
+ * a raised sixth over a minor tonic is a melodic-minor inflection rather than
214
+ * a dorian tonality, should keep reading the answer it reads today. Turn it on
215
+ * (`true` for all five, or a list to name the modes a repertoire actually
216
+ * uses) and each enabled mode adds one candidate per tonic, ranked on a
217
+ * profile derived from its parallel major or minor key.
218
+ *
219
+ * Ionian and Aeolian are not on the list: they are the major and natural minor
220
+ * candidates, which already carry `scaleName` `'major'` and `'naturalMinor'`.
221
+ *
222
+ * @defaultValue `false`
223
+ */
224
+ modes?: boolean | readonly ModalScaleName[];
225
+ /**
226
+ * Attach a {@link KeyMatch.rationale} to every candidate, and
227
+ * {@link KeyMatch.alternatives} to the top-ranked one.
228
+ *
229
+ * Off by default, and the one explanation in the library that is: the other
230
+ * entry points explain a single reading, while a ranking explains 24 of them
231
+ * (36 or more with the modes on), each needing a phrase built for a candidate
232
+ * nobody asked about. That cost outweighs the detection itself, so it is paid
233
+ * only on request.
234
+ *
235
+ * @defaultValue false
236
+ */
237
+ explain?: boolean;
238
+ /**
239
+ * Upper bound on the input pitches processed by this detection call.
240
+ *
241
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
242
+ */
243
+ budget?: number;
244
+ };
245
+ /**
246
+ * Identify chords matching a set of pitches.
247
+ *
248
+ * Every input pitch class is tried as a root against every known chord quality.
249
+ * A match is reported when all of the chord's tones are present in the input, or
250
+ * when the only absent tone is the perfect fifth (a common omission in shell
251
+ * voicings); matches are ranked best-first by fewest extra notes, then fewest
252
+ * missing notes, then most specific (largest) chord. An exact match (no extras,
253
+ * no missing) is flagged and ranked first.
254
+ *
255
+ * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).
256
+ * @returns Ranked chord interpretations (may be empty).
257
+ * @example
258
+ * ```ts
259
+ * import { detectChord } from '@libraz/libcantus';
260
+ * const matches = detectChord([60, 64, 67]); // C E G
261
+ * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }
262
+ * ```
263
+ * @category Recognition
264
+ */
265
+ declare function detectChord(pitches: readonly number[], opts?: DetectChordOptions): ChordMatch[];
266
+ /**
267
+ * The single best chord interpretation of a pitch set, or null if none.
268
+ *
269
+ * @param pitches MIDI pitches or bare pitch classes.
270
+ * @returns The top-ranked chord, or null when nothing matches.
271
+ * @example
272
+ * ```ts
273
+ * import { detectChordBest } from '@libraz/libcantus';
274
+ * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }
275
+ * ```
276
+ * @category Recognition
277
+ */
278
+ declare function detectChordBest(pitches: readonly number[], opts?: DetectChordOptions): Chord | null;
279
+ /**
280
+ * Rank major and minor keys by how well a set of pitch classes is distributed
281
+ * like each key.
282
+ *
283
+ * The input becomes a weighted pitch-class distribution, and every one of the
284
+ * 24 candidates is scored by the Pearson correlation between that distribution
285
+ * and its key profile rotated onto the candidate tonic (see
286
+ * {@link DetectKeyOptions.profile}). Correlating against a profile rather than
287
+ * counting scale members is what tells a key apart from its relative: C major
288
+ * and A minor contain the same seven pitch classes, so a membership count can
289
+ * only separate them by a tie-break, while their profiles expect the weight on
290
+ * different degrees.
291
+ *
292
+ * Choosing the minor variant is deliberately not part of the ranking. A minor
293
+ * candidate is ranked once, on the minor profile, which already expects some
294
+ * weight on the raised sixth and seventh; only then does it report whichever of
295
+ * the natural, harmonic and melodic masks covers the most input weight, and
296
+ * `fit` is measured against that mask. That split is why a harmonic-minor
297
+ * cadence ranks its own tonic first without the harmonic variant having to win
298
+ * a scoring contest against the natural one.
299
+ *
300
+ * {@link DetectKeyOptions.modes} adds the church modes to the same contest, one
301
+ * candidate per mode per tonic, each ranked on a profile derived from its
302
+ * parallel major or minor key. A modal winner reports the mode in `scaleName`
303
+ * and still reports the key it leans on in `mode`. The option is off by default,
304
+ * so a caller that does not ask for modes gets the ranking of the 24 keys it has
305
+ * always got.
306
+ *
307
+ * Ties are broken by `fit` descending, then tonic pitch class ascending, then by
308
+ * scale — major, the minor forms, then the modes — so the order is fully
309
+ * determined by the input rather than by the order candidates happen to be built
310
+ * in. Returns all 24 keys ranked best-first, plus 12 more per enabled mode, or
311
+ * an empty array for an empty input (mirroring {@link detectChord}).
312
+ *
313
+ * {@link DetectKeyOptions.explain} adds a `rationale` to every candidate and,
314
+ * to the winner, the runners-up it beat with the link of that tie-break chain
315
+ * that separated each. It is off by default; see the option for why.
316
+ *
317
+ * @param pitches MIDI pitches or bare pitch classes.
318
+ * @param opts How to weigh the input, which profile to rank with, and whether
319
+ * the modes take part; see {@link DetectKeyOptions}.
320
+ * @returns Ranked key interpretations (empty for an empty input), each carrying
321
+ * a finite `score` in [-1, 1].
322
+ * @throws {InvalidInputError} When `weights` does not have one entry per pitch,
323
+ * `profile` is not a known name or a valid pair of 12-entry vectors, or
324
+ * `modes` names something that is not a church mode.
325
+ * @throws {RangeError} When a pitch is outside the MIDI domain or the input
326
+ * exceeds the budget.
327
+ * @example
328
+ * ```ts
329
+ * import { detectKey } from '@libraz/libcantus';
330
+ * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale
331
+ * keys[0].mode; // 'major', with keys[0].key.rootPc === 0
332
+ * keys[1].mode; // 'minor' on 9: A minor, the relative, ranked just below
333
+ * const riff = detectKey([62, 62, 65, 69, 71, 69, 65, 62], { modes: true });
334
+ * riff[0].scaleName; // 'dorian', on rootPc 2, still reported as mode 'minor'
335
+ * ```
336
+ * @category Recognition
337
+ */
338
+ declare function detectKey(pitches: readonly number[], opts?: DetectKeyOptions): KeyMatch[];
339
+ /**
340
+ * Rank keys by how well they contain a set of note events, weighting each note
341
+ * by how much of the music it actually occupies.
342
+ *
343
+ * {@link detectKey} counts every pitch once, which lets a run of fast ornamental
344
+ * notes outvote the sustained harmony that establishes the key. This weighs
345
+ * each note by duration times velocity, the same measure chord inference uses,
346
+ * so the two agree on what the music emphasises. Notes that never sound (zero
347
+ * or negative duration) are ignored. The weighted distribution is then ranked
348
+ * exactly as {@link detectKey} ranks it, by correlation against a key profile,
349
+ * so `score` is a finite correlation in [-1, 1].
350
+ *
351
+ * @param notes The note events to weigh.
352
+ * @param opts Which profile to rank with, whether the modes take part, and the
353
+ * budget for processing imported note events; the weights are derived from the
354
+ * notes themselves.
355
+ * @returns Ranked key interpretations (empty when nothing sounds).
356
+ * @throws {InvalidInputError} When `profile` is not a known name or a valid
357
+ * pair of 12-entry vectors, or `modes` names something that is not a church
358
+ * mode.
359
+ * @throws {RangeError} When a note event is malformed or the input exceeds the
360
+ * budget.
361
+ * @example
362
+ * ```ts
363
+ * import { detectKeyFromNotes } from '@libraz/libcantus';
364
+ * detectKeyFromNotes([{ pitch: 60, startBeat: 0, durationBeat: 4 }])[0]?.key.rootPc; // 0
365
+ * ```
366
+ * @category Recognition
367
+ */
368
+ declare function detectKeyFromNotes(notes: readonly NoteEvent[], opts?: Omit<DetectKeyOptions, 'weights'>): KeyMatch[];
369
+ /**
370
+ * The single best key interpretation of a pitch set.
371
+ *
372
+ * The counterpart of {@link detectChordBest}: the ranked list is the general
373
+ * answer, but a caller that just wants "what key is this" should not have to
374
+ * index into it and assert the result is there. The winner is the candidate
375
+ * whose key profile correlates best with the input distribution, with the same
376
+ * deterministic tie-break {@link detectKey} applies, so its `score` is a finite
377
+ * correlation in [-1, 1] rather than a membership share.
378
+ *
379
+ * This is where {@link DetectKeyOptions.explain} earns its keep: the winner is
380
+ * the only match a caller of this function sees, and with `explain` it carries
381
+ * both its own rationale and the candidates it beat.
382
+ *
383
+ * @param pitches MIDI pitches or bare pitch classes.
384
+ * @param opts How to weigh the input, which profile to rank with, and whether
385
+ * the modes take part; see {@link DetectKeyOptions}.
386
+ * @returns The top-ranked key, or null when nothing sounds.
387
+ * @throws {InvalidInputError} When `weights` does not have one entry per pitch,
388
+ * `profile` is not a known name or a valid pair of 12-entry vectors, or
389
+ * `modes` names something that is not a church mode.
390
+ * @throws {RangeError} When a pitch is outside the MIDI domain or the input
391
+ * exceeds the budget.
392
+ * @example
393
+ * ```ts
394
+ * import { detectKeyBest } from '@libraz/libcantus';
395
+ * detectKeyBest([0, 2, 4, 5, 7, 9, 11])?.mode; // 'major'
396
+ * ```
397
+ * @category Recognition
398
+ */
399
+ declare function detectKeyBest(pitches: readonly number[], opts?: DetectKeyOptions): KeyMatch | null;
400
+
401
+ /**
402
+ * Arrangement track-role analysis: a single harmony is inferred from every
403
+ * track's notes pooled together, each track is then analysed against that shared
404
+ * harmony, and notes that clash with the sounding chord are collected as
405
+ * conflicts.
406
+ */
407
+
408
+ /**
409
+ * The musical role a track plays in the arrangement.
410
+ *
411
+ * Only `drums` changes the analysis: its pitches select instruments rather than
412
+ * naming harmony, so such a track is excluded from chord and key inference and
413
+ * from every voice-leading comparison, and is reported back with no
414
+ * annotations. `harmony` is meaningful through
415
+ * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords
416
+ * are inferred from. The rest are labels carried through to
417
+ * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so
418
+ * a track left unlabelled reports `'other'` rather than a guess.
419
+ *
420
+ * @category Arrangement & Analysis
421
+ */
422
+ type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';
423
+ /**
424
+ * One input track: its notes plus optional name and role.
425
+ *
426
+ * @category Arrangement & Analysis
427
+ */
428
+ type ArrangementTrack = {
429
+ name?: string;
430
+ role?: TrackRole;
431
+ notes: readonly NoteEvent[];
432
+ };
433
+ /**
434
+ * A track after analysis: its resolved name, role, and per-note annotations.
435
+ *
436
+ * @category Arrangement & Analysis
437
+ */
438
+ type TrackAnalysis = {
439
+ name: string;
440
+ role: TrackRole;
441
+ /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
442
+ notes: AnalyzedNote[];
443
+ };
444
+ /**
445
+ * A note that clashes with the harmony sounding beneath it.
446
+ *
447
+ * @category Arrangement & Analysis
448
+ */
449
+ type Conflict = {
450
+ beat: number;
451
+ trackName: string;
452
+ /** The clashing track's index in the caller's input array. */
453
+ trackIndex: number;
454
+ /** The note's index in that track's own note array. */
455
+ originalIndex?: number;
456
+ /** The id the note carries in {@link TrackAnalysis.notes}. */
457
+ noteId: number;
458
+ pitch: number;
459
+ /**
460
+ * The note's theory labels at its onset, so a caller can tell a genuine clash
461
+ * from a passing tone, a neighbour, or a prepared suspension — all of which
462
+ * are ordinary non-chord tones that still evaluate as unsafe.
463
+ */
464
+ labels: TheoryLabel[];
465
+ /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
466
+ safety: NoteSafety;
467
+ /** {@link SafetyResult.reasons} bitmask. */
468
+ reasons: number;
469
+ /** Preferred stepwise resolution, when the safety evaluator provides one. */
470
+ resolveTo?: number;
471
+ /** Nearby fully safe replacement pitches, nearest first. */
472
+ suggestions?: number[];
473
+ rationale?: string;
474
+ };
475
+ /**
476
+ * The full result of {@link analyzeArrangement}.
477
+ *
478
+ * @category Arrangement & Analysis
479
+ */
480
+ type ArrangementAnalysis = {
481
+ /**
482
+ * The key regions the analysis ran against, in time order. Roman numerals,
483
+ * harmonic function and note safety are all judged against the region
484
+ * covering the beat, so a piece that modulates is not read against the key it
485
+ * started in for every bar after it leaves.
486
+ */
487
+ keys: KeyRegion[];
488
+ /** The key held longest across {@link ArrangementAnalysis.keys}. */
489
+ prevailingKey: KeyScale;
490
+ timeline: ChordTimeline;
491
+ segmentConfidence: number[];
492
+ cadences: CadenceHit[];
493
+ tracks: TrackAnalysis[];
494
+ /** Notes clashing with the sounding harmony, worst severity first. */
495
+ conflicts: Conflict[];
496
+ };
497
+ /**
498
+ * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.
499
+ *
500
+ * @category Arrangement & Analysis
501
+ */
502
+ type ArrangementOptions = {
503
+ /**
504
+ * A single key held across the whole arrangement. Omit it, and `keys` with
505
+ * it, to have the key searched for over time so a modulating piece is read
506
+ * against the key actually in force.
507
+ */
508
+ key?: KeyScale;
509
+ /**
510
+ * Key regions to judge against, when a previous pass already worked them out.
511
+ * Takes precedence over `key`; supplying both is answering the same question
512
+ * twice, and the more specific answer wins.
513
+ */
514
+ keys?: KeyRegion[];
515
+ /**
516
+ * A chord timeline to analyse against, instead of inferring one from the
517
+ * notes. Supply it to analyse a hand-written progression, or to reuse the
518
+ * timeline of an earlier call rather than re-running chord inference.
519
+ */
520
+ timeline?: ChordTimeline;
521
+ /**
522
+ * Indices of the tracks the harmony is inferred from. Defaults to every
523
+ * pitched track. Percussion tracks are excluded either way. An index naming
524
+ * no track is an input error rather than one that matches nothing.
525
+ */
526
+ harmonyTracks?: number[];
527
+ /**
528
+ * Lowest severity reported in `conflicts`.
529
+ *
530
+ * @defaultValue {@link NoteSafety.Warning}
531
+ */
532
+ minSeverity?: NoteSafety;
533
+ /**
534
+ * A single time signature held across the whole arrangement, as sugar for a
535
+ * one-element `meters`; defaults to 4/4. Giving both is an input error.
536
+ *
537
+ * @defaultValue `4/4`
538
+ */
539
+ ts?: TimeSignature;
540
+ /**
541
+ * The meter as it changes over the arrangement. Bar lines, downbeats and
542
+ * metric weight all follow the signature in force at the beat in question.
543
+ *
544
+ * @defaultValue 4/4 throughout
545
+ */
546
+ meters?: MeterMap;
547
+ /**
548
+ * Length of the pickup in beats, when the piece starts with one.
549
+ *
550
+ * The first downbeat is beat 0, so an upbeat is written at negative beats.
551
+ * Declaring its length rejects a note that starts before the pickup does;
552
+ * leave it unset to accept any finite onset.
553
+ *
554
+ * @defaultValue no declared pickup
555
+ */
556
+ pickupBeats?: number;
557
+ /**
558
+ * Chord-slot length in beats; defaults to the length of the opening bar.
559
+ *
560
+ * @defaultValue the length of the opening bar
561
+ */
562
+ harmonicRhythm?: number;
563
+ /**
564
+ * Safety profile used for conflict detection; defaults to `pop`.
565
+ *
566
+ * @defaultValue `'pop'`
567
+ */
568
+ profile?: SafetyProfile;
569
+ /**
570
+ * Upper bound on the work this call may do — note counts, windows, and
571
+ * candidate counts are each checked against it before anything is allocated.
572
+ *
573
+ * Raise it to analyse a piece larger than the default allows; the default is
574
+ * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast
575
+ * rather than blocking the thread.
576
+ *
577
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
578
+ */
579
+ budget?: number;
580
+ };
581
+ /**
582
+ * Analyse a whole arrangement against the harmony inferred from it.
583
+ *
584
+ * The chord timeline and the key regions it is read against are inferred from
585
+ * the pooled notes of every pitched track (see
586
+ * {@link chordTimelineFromNotes}); pooling all voices is
587
+ * robust even when roles are absent or a track doubles the harmony, so it is
588
+ * preferred over deriving the harmony from a subset. A track marked
589
+ * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and
590
+ * pooling them would corrupt the key, the timeline and every reading built on
591
+ * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to
592
+ * analyse against a progression you already have. Zero- and negative-length
593
+ * notes never sound, so they are dropped at ingest and appear in neither the
594
+ * annotations nor the conflicts.
595
+ *
596
+ * Voice analysis assumes one monophonic voice at a time, so each track is
597
+ * first partitioned into monophonic sub-voices (a polyphonic block-chord track
598
+ * fans out into one sub-voice per simultaneous note; a monophonic track is
599
+ * unaffected). Each sub-voice is labelled note-by-note against the harmony
600
+ * with {@link analyzeVoice}, seeing every other sub-voice — including siblings
601
+ * within the same track — as its accompaniment. Every note is then re-checked
602
+ * with {@link evaluateSafety} at its onset and again at each chord change it
603
+ * sustains across, so a held note that clashes with a later chord is caught:
604
+ * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
605
+ * clashing beat, sorted worst severity first and then by beat.
606
+ *
607
+ * Every ordinary non-chord tone — a passing note, a neighbour, a prepared
608
+ * suspension — is by definition unsafe against the chord under it, so the
609
+ * conflict list is a report, not a fault list. Each conflict carries the note's
610
+ * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those
611
+ * apart and map a conflict back to the note it passed in; `minSeverity` narrows
612
+ * the list to outright dissonance.
613
+ *
614
+ * @param tracks The tracks to analyse.
615
+ * @param opts Analysis options; see {@link ArrangementOptions}.
616
+ * @returns The inferred harmony, per-track annotations, cadences, and conflicts.
617
+ * @example
618
+ * ```ts
619
+ * import { analyzeArrangement } from '@libraz/libcantus';
620
+ * const melody = [
621
+ * { pitch: 60, startBeat: 0, durationBeat: 2 },
622
+ * { pitch: 67, startBeat: 2, durationBeat: 2 },
623
+ * ];
624
+ * const { prevailingKey, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
625
+ * prevailingKey.rootPc; // the key held longest across the analysis
626
+ * conflicts; // notes clashing with the inferred harmony, worst severity first
627
+ * ```
628
+ * @category Arrangement & Analysis
629
+ */
630
+ declare function analyzeArrangement(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementAnalysis;
631
+
632
+ /**
633
+ * Incremental arrangement analysis: an analysis held open across edits, so a
634
+ * host that re-analyses on every keystroke pays for the beats that changed
635
+ * rather than for the whole piece.
636
+ */
637
+
638
+ /**
639
+ * One track's notes as they stand after an edit.
640
+ *
641
+ * The whole note array is given rather than a description of what moved: a host
642
+ * always has the track's current contents, and working out what actually
643
+ * changed is this module's job, not the caller's.
644
+ *
645
+ * @category Arrangement & Analysis
646
+ */
647
+ type TrackEdit = {
648
+ /** Index of the track in the session's track array. */
649
+ trackIndex: number;
650
+ /** The track's notes after the edit. */
651
+ notes: readonly NoteEvent[];
652
+ };
653
+ /**
654
+ * An arrangement analysis held open for editing.
655
+ *
656
+ * @category Arrangement & Analysis
657
+ */
658
+ type ArrangementSession = {
659
+ /** The tracks the current analysis was made from. */
660
+ readonly tracks: readonly ArrangementTrack[];
661
+ /** The analysis of those tracks. */
662
+ readonly analysis: ArrangementAnalysis;
663
+ /**
664
+ * Re-analyse after replacing the notes of one or more tracks, reusing
665
+ * everything the edit provably cannot reach.
666
+ *
667
+ * The result equals {@link analyzeArrangement} over the edited tracks — the
668
+ * same segments, confidences, key regions, cadences, annotations and
669
+ * conflicts — so a host may hold a session for as long as it likes without
670
+ * the analysis drifting from what a fresh pass would say.
671
+ */
672
+ update: (edits: readonly TrackEdit[]) => ArrangementSession;
673
+ };
674
+ /**
675
+ * Open an arrangement analysis that can be updated a few beats at a time.
676
+ *
677
+ * The first pass is an ordinary {@link analyzeArrangement}; what the session
678
+ * adds is the slot-level evidence behind it, so a later {@link
679
+ * ArrangementSession.update} can work out which beats an edit could possibly
680
+ * have changed and leave the rest of the harmony alone. An update returns a new
681
+ * session, so the analysis before an edit stays valid and a host can keep it
682
+ * for undo.
683
+ *
684
+ * What an update recomputes is derived rather than guessed at:
685
+ *
686
+ * - The edited beats are the beats covered by the notes the old and new track
687
+ * arrays disagree on, which is narrower than the track and usually far
688
+ * narrower than the piece.
689
+ * - A slot's evidence sees exactly the notes overlapping it, so the slots to
690
+ * re-derive are the slots overlapping those beats — plus one either side, so
691
+ * that an onset sitting a floating-point hair off a slot boundary cannot
692
+ * leave a stale slot behind.
693
+ * - The boundary search itself has no such margin. It is a shortest-path search
694
+ * over a whole run of slots, so a changed slot can move a boundary anywhere in
695
+ * the run, and its change cost is calibrated on the mean weight of every
696
+ * sounding slot in the piece, which couples the runs to one another. The
697
+ * search is therefore re-run over the whole piece — arithmetic over the
698
+ * carried-over evidence, with nothing allocated per slot — rather than being
699
+ * spliced at a margin that does not exist.
700
+ * - Each settled segment's chord is carried over when its bounds, its key and
701
+ * its slots are all unchanged, since those are the only things chord
702
+ * inference reads.
703
+ *
704
+ * Key regions and the per-note annotations are recomputed in full. Both are
705
+ * whole-piece passes — the key search is a shortest-path search with no reset
706
+ * points, sub-voice lane assignment is greedy from the first note, and every
707
+ * note is judged against the other voices sounding beneath it — so neither has
708
+ * a sound local answer, and reporting one would mean an update that quietly
709
+ * disagreed with a fresh analysis.
710
+ *
711
+ * An update falls back to a full analysis whenever the edit moves the ground the
712
+ * evidence stands on: a changed piece length, a changed first onset (both of
713
+ * which move the slot grid), or an edit to a session whose harmony was supplied
714
+ * through {@link ArrangementOptions.timeline}. The answer is the same either
715
+ * way; only the work differs.
716
+ *
717
+ * @param tracks The tracks to analyse.
718
+ * @param opts Analysis options; see {@link ArrangementOptions}.
719
+ * @returns A session holding the analysis and the evidence behind it.
720
+ * @example
721
+ * ```ts
722
+ * import { createArrangementSession } from '@libraz/libcantus';
723
+ * const melody = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
724
+ * const session = createArrangementSession([{ role: 'melody', notes: melody }]);
725
+ * const moved = [{ pitch: 62, startBeat: 0, durationBeat: 4 }];
726
+ * const next = session.update([{ trackIndex: 0, notes: moved }]);
727
+ * next.analysis.timeline.segments; // as if analyzed from scratch
728
+ * ```
729
+ * @category Arrangement & Analysis
730
+ */
731
+ declare function createArrangementSession(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementSession;
732
+
733
+ /**
734
+ * Arrangement tension analysis: a coarse harmonic-tension curve sampled at
735
+ * regular beats across a whole multi-track piece, combining the sounding
736
+ * chord's harmonic function, the dissonance of the sounding notes, and their
737
+ * registral span into a single normalized reading.
738
+ */
739
+
740
+ /**
741
+ * A tension reading sampled at a beat.
742
+ *
743
+ * @category Arrangement & Analysis
744
+ */
745
+ type TensionPoint = {
746
+ beat: number;
747
+ /** Combined tension in [0, 1]. */
748
+ tension: number;
749
+ };
750
+ /**
751
+ * Sample the harmonic tension of an arrangement at regular beats.
752
+ *
753
+ * At each sample beat the tension combines three normalized terms:
754
+ * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
755
+ * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;
756
+ * - the dissonance of the sounding notes — the larger of the share of sounding
757
+ * pitches that are not chord tones and the share that {@link evaluateSafety}
758
+ * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;
759
+ * - the registral span of the sounding notes, saturating at
760
+ * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.
761
+ *
762
+ * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
763
+ * pooled together, so the result is deterministic and self-contained.
764
+ *
765
+ * @param tracks The tracks to sample.
766
+ * @param opts Analysis options plus an optional `step` (default one beat).
767
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
768
+ * @throws If `step` is not positive.
769
+ * @example
770
+ * ```ts
771
+ * import { tensionCurve } from '@libraz/libcantus';
772
+ * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
773
+ * const curve = tensionCurve([{ notes }], { step: 1 });
774
+ * curve; // one { beat, tension } sample per beat, in beat order
775
+ * ```
776
+ * @category Arrangement & Analysis
777
+ */
778
+ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
779
+ step?: number;
780
+ }): TensionPoint[];
781
+ /**
782
+ * Sample the tension of an arrangement already analysed by
783
+ * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.
784
+ *
785
+ * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference
786
+ * twice over the same notes. This takes the analysis it already produced, so
787
+ * the curve and the annotations describe the same harmony by construction.
788
+ *
789
+ * @param tracks The same tracks that were analysed.
790
+ * @param analysis The result of {@link analyzeArrangement} for those tracks.
791
+ * @param opts Analysis options plus an optional `step` (default one beat).
792
+ * @returns One {@link TensionPoint} per sampled beat, in beat order.
793
+ * @example
794
+ * ```ts
795
+ * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';
796
+ * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];
797
+ * const analysis = analyzeArrangement(tracks);
798
+ * tensionCurveFrom(tracks, analysis, { step: 1 });
799
+ * ```
800
+ * @category Arrangement & Analysis
801
+ */
802
+ declare function tensionCurveFrom(tracks: ArrangementTrack[], analysis: ArrangementAnalysis, opts?: ArrangementOptions & {
803
+ step?: number;
804
+ }): TensionPoint[];
805
+
806
+ /**
807
+ * Hypermeter: the grouping of bars into hyperbars.
808
+ *
809
+ * Bars are felt in groups the way beats are felt in bars — four bars at a time
810
+ * in most tonal and popular music, but two, three, six or eight elsewhere. The
811
+ * grouping is not written in the score, so it has to be inferred from where the
812
+ * music actually restarts: harmony changes on a hypermetric downbeat, and
813
+ * cadences arrive at the end of a hyperbar.
814
+ */
815
+
816
+ /**
817
+ * A bar grouping inferred from the music.
818
+ *
819
+ * @category Arrangement & Analysis
820
+ */
821
+ type Hypermeter = {
822
+ /**
823
+ * Bars per hyperbar. Falls back to the span's own bar count when the span is
824
+ * too short to hold two of any candidate grouping.
825
+ */
826
+ groupBars: number;
827
+ /**
828
+ * Absolute beat of each hypermetric downbeat inside the analysed span, in
829
+ * time order. A pickup bar is not a group head, so the first entry is the
830
+ * first full bar that starts a group.
831
+ */
832
+ downbeats: number[];
833
+ /**
834
+ * How much the music supports the grouping, in [0, 1]: how strongly harmonic
835
+ * change and cadences prefer these bars over the others, discounted when the
836
+ * runner-up grouping explains the music nearly as well.
837
+ */
838
+ confidence: number;
839
+ /** Why this grouping was chosen. */
840
+ rationale: string;
841
+ };
842
+ /**
843
+ * Options for {@link hypermeter}.
844
+ *
845
+ * @category Arrangement & Analysis
846
+ */
847
+ type HypermeterOptions = {
848
+ /**
849
+ * End of the analysed span in beats; defaults to the end of the last note.
850
+ *
851
+ * @defaultValue the end of the last note
852
+ */
853
+ totalBeats?: number;
854
+ /**
855
+ * Beats at which cadences arrive, as {@link CadenceHit.atBeat} reports them.
856
+ *
857
+ * A cadence is the clearest hypermetric evidence there is — it arrives at the
858
+ * end of a hyperbar — but finding one needs a chord timeline and a key, which
859
+ * this function is not given. Callers that already hold cadences should pass
860
+ * them; {@link phrasesFromTimeline} does.
861
+ *
862
+ * @defaultValue none
863
+ */
864
+ cadenceBeats?: readonly number[];
865
+ /**
866
+ * Upper bound on the work this call may do.
867
+ *
868
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
869
+ */
870
+ budget?: number;
871
+ };
872
+ /**
873
+ * Infer how a piece's bars group into hyperbars.
874
+ *
875
+ * Groupings of 2, 3, 4, 6, and 8 bars are tried at every phase, and each
876
+ * reading is scored on two counts: how much more harmonic change lands on the
877
+ * bars it calls group heads than on the bars between them, and how many of the
878
+ * cadences it is given arrive where it says a hyperbar ends. The commonest
879
+ * groupings are believed a little more readily than the rare ones, which
880
+ * settles ties without letting a prior overrule the music.
881
+ *
882
+ * The meter is read at the beat in question rather than at the start, so a
883
+ * piece that changes metre keeps being cut at its own bar lines. Bar 0 is the
884
+ * first full bar however much pickup precedes it, so an upbeat is bar -1 and is
885
+ * never taken for a hypermetric downbeat. Whether there is an upbeat at all is
886
+ * decided the way the rest of the analysis decides it: a note a millibeat early
887
+ * is playing the downbeat, not anticipating it.
888
+ *
889
+ * @param notes The notes to read, in any order. Notes that never sound are
890
+ * dropped.
891
+ * @param meter A single time signature, or the piece's meter map; defaults to
892
+ * 4/4 throughout.
893
+ * @param opts Analysis options; see {@link HypermeterOptions}.
894
+ * @returns The grouping, its downbeats, a confidence, and a rationale.
895
+ * @example
896
+ * ```ts
897
+ * import { hypermeter } from '@libraz/libcantus';
898
+ * const notes = [
899
+ * { pitch: 60, startBeat: 0, durationBeat: 16 },
900
+ * { pitch: 65, startBeat: 16, durationBeat: 16 },
901
+ * ];
902
+ * hypermeter(notes).groupBars; // 4
903
+ * ```
904
+ * @category Arrangement & Analysis
905
+ */
906
+ declare function hypermeter(notes: readonly NoteEvent[], meter?: MeterLike, opts?: HypermeterOptions): Hypermeter;
907
+
908
+ /**
909
+ * Phrases: the unit of music one bar larger than the chord.
910
+ *
911
+ * Every other analysis in this library is local — a chord against the next
912
+ * chord, a note against the chord under it — and that is the ceiling on what it
913
+ * can say. A cadence is a cadence because it falls at the end of a phrase, so
914
+ * two chords read in isolation cannot settle how much weight one carries. This
915
+ * module draws the boundaries, then ranks the cadences that sit on them.
916
+ *
917
+ * No single signal draws a phrase boundary reliably, so three are combined: the
918
+ * cadences the harmony forms, the rests and long notes the melody breathes at,
919
+ * and the points where the music starts saying something it has said before. A
920
+ * boundary is reported with the evidence behind it and a confidence, because a
921
+ * phrase reading is an interpretation and pretending otherwise would be worse
922
+ * than useless to a caller deciding whether to trust it.
923
+ */
924
+
925
+ /**
926
+ * A reason a phrase boundary was placed where it was.
927
+ *
928
+ * @category Arrangement & Analysis
929
+ */
930
+ type PhraseSignal =
931
+ /** A cadence closed on the beat the boundary falls at. */
932
+ 'cadence'
933
+ /** The melody stopped sounding long enough to have taken a breath. */
934
+ | 'rest'
935
+ /** A note was held long enough to read as an arrival. */
936
+ | 'longNote'
937
+ /** The music began repeating material heard immediately before. */
938
+ | 'repetition'
939
+ /** The boundary falls on a hypermetric downbeat. */
940
+ | 'hypermeter'
941
+ /** The music ends here, which closes whatever phrase was open. */
942
+ | 'pieceEnd';
943
+ /**
944
+ * One phrase: a span of music closed by a boundary, and the evidence for it.
945
+ *
946
+ * @category Arrangement & Analysis
947
+ */
948
+ type Phrase = {
949
+ /** First beat of the phrase; negative when the phrase begins in a pickup. */
950
+ startBeat: number;
951
+ /** End of the phrase, exclusive. */
952
+ endBeat: number;
953
+ /**
954
+ * The cadence closing the phrase, or null when the boundary rests on the
955
+ * melody and the metre alone.
956
+ */
957
+ cadence: CadenceHit | null;
958
+ /** Why the closing boundary was placed here, strongest evidence first. */
959
+ signals: PhraseSignal[];
960
+ /**
961
+ * How well evidenced the phrase is, in [0, 1]: how strong its closing
962
+ * boundary is, and how close its length is to the length expected of a
963
+ * phrase in this piece.
964
+ */
965
+ confidence: number;
966
+ /**
967
+ * How much structural weight the closing cadence carries, in [0, 1]; 0 for a
968
+ * phrase that no cadence closes.
969
+ *
970
+ * This is the number that answers "which cadence matters most": it grades the
971
+ * cadence by how conclusive its type is, then by where the phrase it closes
972
+ * sits — a cadence ending the piece or landing on a hypermetric boundary
973
+ * closes more than one in the middle of a hyperbar.
974
+ * {@link structuralCadences} ranks by it.
975
+ */
976
+ structuralWeight: number;
977
+ /** A short reading of the phrase in words. */
978
+ rationale: string;
979
+ };
980
+ /**
981
+ * Options for {@link phrasesFromTimeline}.
982
+ *
983
+ * @category Arrangement & Analysis
984
+ */
985
+ type PhraseOptions = {
986
+ /**
987
+ * A single time signature held across the whole span, as sugar for a
988
+ * one-element `meters`; defaults to 4/4. Giving both is an input error.
989
+ *
990
+ * @defaultValue `4/4`
991
+ */
992
+ ts?: TimeSignature;
993
+ /**
994
+ * The meter as it changes over the span. Bar lines, hypermetric downbeats and
995
+ * the default phrase length all follow the signature in force at the beat in
996
+ * question, so a piece that changes metre is not read in the one it opened in.
997
+ *
998
+ * @defaultValue 4/4 throughout
999
+ */
1000
+ meters?: MeterMap;
1001
+ /**
1002
+ * Key context for cadence detection: one key for the whole span, or the key
1003
+ * in force at a given beat. Omit it to have the key searched for over time,
1004
+ * so a piece that modulates has each cadence read in the key it arrives in.
1005
+ *
1006
+ * @defaultValue the key inferred from the notes over time
1007
+ */
1008
+ key?: KeyContext;
1009
+ /**
1010
+ * End of the analysed span in beats; defaults to the end of the last note or
1011
+ * timeline segment.
1012
+ *
1013
+ * @defaultValue the end of the input
1014
+ */
1015
+ totalBeats?: number;
1016
+ /**
1017
+ * Length a phrase is expected to run, in beats; defaults to one hyperbar.
1018
+ *
1019
+ * This is a prior, not a rule: a boundary is placed where the evidence puts
1020
+ * it, and this decides how much evidence an irregular length has to carry
1021
+ * before it is preferred to a regular one. Set it to two hyperbars for music
1022
+ * whose phrases are periods.
1023
+ *
1024
+ * @defaultValue one hyperbar of the opening meter
1025
+ */
1026
+ expectedPhraseBeats?: number;
1027
+ /**
1028
+ * Shortest phrase that may be reported, in beats; defaults to one bar of the
1029
+ * opening meter.
1030
+ *
1031
+ * @defaultValue one bar of the opening meter
1032
+ */
1033
+ minPhraseBeats?: number;
1034
+ /**
1035
+ * Silence at least this long divides phrases; defaults to half a bar of the
1036
+ * opening meter.
1037
+ *
1038
+ * @defaultValue half a bar of the opening meter
1039
+ */
1040
+ restBeats?: number;
1041
+ /**
1042
+ * A note held at least this long reads as an arrival and closes a phrase;
1043
+ * defaults to one bar of the opening meter.
1044
+ *
1045
+ * @defaultValue one bar of the opening meter
1046
+ */
1047
+ longNoteBeats?: number;
1048
+ /**
1049
+ * A hypermeter already computed for this piece, to be used instead of
1050
+ * inferring one. Pass it when the caller has already asked for the grouping,
1051
+ * or to impose a grouping the search would not have chosen.
1052
+ *
1053
+ * @defaultValue inferred from the notes and the cadences found
1054
+ */
1055
+ hypermeter?: Hypermeter;
1056
+ /**
1057
+ * Upper bound on the work this call may do.
1058
+ *
1059
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
1060
+ */
1061
+ budget?: number;
1062
+ };
1063
+ /**
1064
+ * A cadence ranked by how much of the structure it closes.
1065
+ *
1066
+ * @category Arrangement & Analysis
1067
+ */
1068
+ type StructuralCadence = {
1069
+ /** Index of the phrase this cadence closes, in the array it came from. */
1070
+ phraseIndex: number;
1071
+ /** The beat the cadence arrives on. */
1072
+ atBeat: number;
1073
+ /** The beat the phrase it closes ends at. */
1074
+ endBeat: number;
1075
+ /** The cadence itself. */
1076
+ cadence: CadenceResult;
1077
+ /** Structural weight in [0, 1]; {@link Phrase.structuralWeight}. */
1078
+ weight: number;
1079
+ /** Why it weighs what it does. */
1080
+ rationale: string;
1081
+ };
1082
+ /**
1083
+ * Split a piece into phrases.
1084
+ *
1085
+ * Three kinds of evidence are gathered independently and then reconciled. The
1086
+ * harmony contributes its cadences, each closing at the end of the chord it
1087
+ * arrives on. The melody contributes the rests it breathes at and the notes it
1088
+ * holds long enough to read as arrivals. Repetition contributes the beats where
1089
+ * the music restarts material it has just played. The metre contributes its
1090
+ * hypermetric downbeats, which confirm a boundary but never declare one on
1091
+ * their own.
1092
+ *
1093
+ * The boundaries are then chosen together rather than one at a time: the
1094
+ * reading that best trades each boundary's evidence against how regular the
1095
+ * phrase lengths come out is selected by dynamic programming, at a fixed cost
1096
+ * per cut. That is what stops a phrase being declared at every plausible beat,
1097
+ * and what lets a well-evidenced irregular phrase survive next to regular ones.
1098
+ *
1099
+ * The result is contiguous: phrases abut, and together they cover the span from
1100
+ * the first note to the end of the music. A cadence found in the middle of a
1101
+ * phrase is not lost — it is still reachable through {@link detectCadences} —
1102
+ * but only the cadences the reading puts at phrase ends are ranked by
1103
+ * {@link structuralCadences}.
1104
+ *
1105
+ * @param timeline The chord timeline the cadences are read from.
1106
+ * @param notes The melody, for its rests, held notes and repetitions. Notes
1107
+ * that never sound are dropped.
1108
+ * @param opts Analysis options; see {@link PhraseOptions}.
1109
+ * @returns The phrases, in time order; one phrase covering the whole span when
1110
+ * the span is too short to divide.
1111
+ * @example
1112
+ * ```ts
1113
+ * import { chordTimelineFromNotes, phrasesFromTimeline } from '@libraz/libcantus';
1114
+ * const notes = [
1115
+ * { pitch: 60, startBeat: 0, durationBeat: 4 },
1116
+ * { pitch: 67, startBeat: 4, durationBeat: 4 },
1117
+ * { pitch: 60, startBeat: 8, durationBeat: 8 },
1118
+ * ];
1119
+ * const { timeline } = chordTimelineFromNotes(notes);
1120
+ * const phrases = phrasesFromTimeline(timeline, notes);
1121
+ * phrases[phrases.length - 1]?.cadence?.cadence.type; // 'authentic', when one closes it
1122
+ * ```
1123
+ * @category Arrangement & Analysis
1124
+ */
1125
+ declare function phrasesFromTimeline(timeline: ChordTimeline, notes: readonly NoteEvent[], opts?: PhraseOptions): Phrase[];
1126
+ /**
1127
+ * Rank the cadences that close phrases by how much structure they close.
1128
+ *
1129
+ * This is the question the phrase layer exists to answer. Two chords alone
1130
+ * cannot say whether a V-to-I is the end of the piece or a passing confirmation
1131
+ * halfway through a hyperbar; a phrase reading can, because it knows what the
1132
+ * cadence closes and where that sits. Phrases no cadence closes are left out.
1133
+ *
1134
+ * @param phrases The phrases, as {@link phrasesFromTimeline} returns them.
1135
+ * @returns The cadences, heaviest first; ties keep the earlier one.
1136
+ * @example
1137
+ * ```ts
1138
+ * import { chordTimelineFromNotes, phrasesFromTimeline, structuralCadences } from '@libraz/libcantus';
1139
+ * const notes = [
1140
+ * { pitch: 60, startBeat: 0, durationBeat: 4 },
1141
+ * { pitch: 67, startBeat: 4, durationBeat: 4 },
1142
+ * { pitch: 60, startBeat: 8, durationBeat: 8 },
1143
+ * ];
1144
+ * const { timeline } = chordTimelineFromNotes(notes);
1145
+ * const ranked = structuralCadences(phrasesFromTimeline(timeline, notes));
1146
+ * ranked[0]; // the cadence carrying the most structural weight, if any
1147
+ * ```
1148
+ * @category Arrangement & Analysis
1149
+ */
1150
+ declare function structuralCadences(phrases: readonly Phrase[]): StructuralCadence[];
1151
+
1152
+ /**
1153
+ * Sections: the largest unit this library reads, recovered from repetition.
1154
+ *
1155
+ * A section is found, not named. What the analysis can see is that a span of
1156
+ * music is a restatement of an earlier one, so the spans are labelled by order
1157
+ * of first appearance — A, B, C — and nothing more is claimed. Calling a
1158
+ * repeated span a chorus would be a guess dressed as a finding: repetition
1159
+ * alone cannot tell a chorus from a second verse, and the label a listener
1160
+ * would give depends on lyrics, arrangement and loudness that the notes do not
1161
+ * carry.
1162
+ */
1163
+
1164
+ /**
1165
+ * A span of music labelled by which earlier span it restates.
1166
+ *
1167
+ * @category Arrangement & Analysis
1168
+ */
1169
+ type FormSection = {
1170
+ /**
1171
+ * The section's letter, assigned in order of first appearance: the opening
1172
+ * material is A, the first material unlike it is B, and a later restatement
1173
+ * of A is A again. Past Z the letters double (AA, AB), the way spreadsheet
1174
+ * columns do. No functional name — verse, chorus, bridge — is ever inferred.
1175
+ */
1176
+ label: string;
1177
+ /** First beat of the section; negative when it opens with a pickup. */
1178
+ startBeat: number;
1179
+ /** End of the section, exclusive. */
1180
+ endBeat: number;
1181
+ /** The section's length in bars. */
1182
+ bars: number;
1183
+ /**
1184
+ * Index, in the returned array, of the first section carrying this label; the
1185
+ * section's own index when it is that first appearance.
1186
+ */
1187
+ firstOccurrence: number;
1188
+ /**
1189
+ * How alike this section and its first occurrence are, in [0, 1]; 1 on the
1190
+ * first occurrence itself. The mean over the units the section was merged
1191
+ * from, each scored against the unit it matched.
1192
+ */
1193
+ similarity: number;
1194
+ /** How the section came to carry its label. */
1195
+ rationale: string;
1196
+ };
1197
+ /**
1198
+ * Options for {@link sectionsFromNotes}.
1199
+ *
1200
+ * @category Arrangement & Analysis
1201
+ */
1202
+ type FormSectionOptions = {
1203
+ /**
1204
+ * A single time signature held across the whole span, as sugar for a
1205
+ * one-element `meters`; defaults to 4/4. Giving both is an input error.
1206
+ *
1207
+ * @defaultValue `4/4`
1208
+ */
1209
+ ts?: TimeSignature;
1210
+ /**
1211
+ * The meter as it changes over the span, so a piece that changes metre is cut
1212
+ * at its own bar lines.
1213
+ *
1214
+ * @defaultValue 4/4 throughout
1215
+ */
1216
+ meters?: MeterMap;
1217
+ /**
1218
+ * Length of the unit spans are compared in, in bars; defaults to one hyperbar.
1219
+ *
1220
+ * Sections are found by comparing equal units and merging the neighbours that
1221
+ * match, so this is the resolution of the answer rather than the length of a
1222
+ * section: a section is any whole number of units long.
1223
+ *
1224
+ * @defaultValue the inferred hyperbar, in bars
1225
+ */
1226
+ unitBars?: number;
1227
+ /**
1228
+ * How alike two units must be before the later one is called a restatement of
1229
+ * the earlier, in [0, 1].
1230
+ *
1231
+ * @defaultValue `0.8`
1232
+ */
1233
+ threshold?: number;
1234
+ /**
1235
+ * End of the analysed span in beats; defaults to the end of the last note.
1236
+ *
1237
+ * @defaultValue the end of the last note
1238
+ */
1239
+ totalBeats?: number;
1240
+ /**
1241
+ * Upper bound on the work this call may do.
1242
+ *
1243
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
1244
+ */
1245
+ budget?: number;
1246
+ };
1247
+ /**
1248
+ * Recover a piece's sections from what it repeats.
1249
+ *
1250
+ * The span is cut into equal units of `unitBars` bars, each unit is compared
1251
+ * with every unit before it, and a unit alike enough to an earlier one takes
1252
+ * that unit's label. Neighbouring units carrying the same label are then merged,
1253
+ * so an eight-bar section built of two like four-bar units is reported as one
1254
+ * section rather than two.
1255
+ *
1256
+ * Likeness is measured on the melodic line and on the harmonic content
1257
+ * together — the same tune under different harmony is a variation, not a
1258
+ * restatement — and the labels are letters in order of first appearance.
1259
+ * Nothing beyond "this is a restatement of that" is asserted: see
1260
+ * {@link FormSection.label}.
1261
+ *
1262
+ * A pickup belongs to the section it leads into, so material before the first
1263
+ * bar is folded into the opening unit rather than made a section of its own.
1264
+ *
1265
+ * @param notes The piece's notes, in any order. Notes that never sound are
1266
+ * dropped.
1267
+ * @param opts Analysis options; see {@link FormSectionOptions}.
1268
+ * @returns The sections, in time order.
1269
+ * @example
1270
+ * ```ts
1271
+ * import { sectionsFromNotes } from '@libraz/libcantus';
1272
+ * const bar = (pitch: number, at: number) => ({ pitch, startBeat: at, durationBeat: 4 });
1273
+ * const notes = [bar(60, 0), bar(64, 4), bar(67, 8), bar(60, 12)];
1274
+ * sectionsFromNotes(notes, { unitBars: 2 }).map((section) => section.label);
1275
+ * ```
1276
+ * @category Arrangement & Analysis
1277
+ */
1278
+ declare function sectionsFromNotes(notes: readonly NoteEvent[], opts?: FormSectionOptions): FormSection[];
1279
+
1280
+ /**
1281
+ * Melody as a shape, rather than as a run of notes.
1282
+ *
1283
+ * A motif is held here as an interval contour plus a rhythmic profile, which is
1284
+ * what makes a restatement at another pitch level the same motif; the absolute
1285
+ * pitches only ever describe one statement of it. On that footing two statements
1286
+ * can be compared and the transformation between them named — repetition,
1287
+ * transposition, inversion, retrograde, augmentation — and phrases that stand in
1288
+ * no such relation can still be scored for similarity.
1289
+ */
1290
+
1291
+ /**
1292
+ * Step direction between two consecutive notes.
1293
+ *
1294
+ * @category Arrangement & Analysis
1295
+ */
1296
+ type ContourDirection = 'up' | 'down' | 'same';
1297
+ /**
1298
+ * The overall shape of a line.
1299
+ *
1300
+ * The first four names are the vocabulary the motif generator's contours are
1301
+ * written in, and mean the same here. `'static'` is the shape a generator never
1302
+ * asks for and an analysis regularly meets: a line that does not move. A line
1303
+ * that dips and comes back — an arch upside down — reads as `'wave'`, since the
1304
+ * shared vocabulary has no name of its own for it.
1305
+ *
1306
+ * A wave has to turn more than once to be heard as one, so a cell the generator
1307
+ * writes for `'wave'` reads back as `'wave'` only from three bars up; the one-
1308
+ * and two-bar cells turn once and read as `'arch'`. The other three shapes come
1309
+ * back under their own name at every length.
1310
+ *
1311
+ * @category Arrangement & Analysis
1312
+ */
1313
+ type MelodicContourShape = 'arch' | 'ascending' | 'descending' | 'wave' | 'static';
1314
+ /**
1315
+ * The abstract shape of a line: its step directions and what they add up to.
1316
+ *
1317
+ * @category Arrangement & Analysis
1318
+ */
1319
+ type MelodicContour = {
1320
+ /** One direction per adjacent pair of notes, in time order. */
1321
+ directions: ContourDirection[];
1322
+ /** What the directions add up to. */
1323
+ shape: MelodicContourShape;
1324
+ /** Index of the highest note; ties keep the earliest. */
1325
+ peakIndex: number;
1326
+ /** Index of the lowest note; ties keep the earliest. */
1327
+ troughIndex: number;
1328
+ /** Semitone distance from the lowest note to the highest. */
1329
+ range: number;
1330
+ /** Why the line was given that shape. */
1331
+ rationale: string;
1332
+ };
1333
+ /**
1334
+ * One statement of a motif, addressed in the melody it was found in.
1335
+ *
1336
+ * The notes themselves are not repeated here: a statement is the motif's notes
1337
+ * moved by `transpose` and stretched by `timeRatio`, and `noteIndex` addresses
1338
+ * the original array for a caller that wants them exactly.
1339
+ *
1340
+ * @category Arrangement & Analysis
1341
+ */
1342
+ type MotifOccurrence = {
1343
+ /**
1344
+ * Index of the statement's first note among the sounding notes of the melody,
1345
+ * in time order. Notes that never sound are dropped before the search, so this
1346
+ * is not an index into the caller's array unless the caller's array has none.
1347
+ */
1348
+ noteIndex: number;
1349
+ /** Onset of the statement's first note. */
1350
+ startBeat: number;
1351
+ /** End of the statement's last note, exclusive. */
1352
+ endBeat: number;
1353
+ /** Semitones this statement stands above the motif's first statement. */
1354
+ transpose: number;
1355
+ /**
1356
+ * How much this statement is stretched against the motif's first: 2 for a
1357
+ * statement in doubled note values, 0.5 for one in halved values, 1 for one in
1358
+ * the same values.
1359
+ */
1360
+ timeRatio: number;
1361
+ };
1362
+ /**
1363
+ * A recurring melodic cell.
1364
+ *
1365
+ * Identity is the interval sequence plus the rhythmic profile, so every
1366
+ * statement of the motif carries the same `intervals` and `rhythm` however far
1367
+ * it has been transposed or however wide its note values are written. `notes`
1368
+ * holds one statement — the first — exactly as it sounds.
1369
+ *
1370
+ * @category Arrangement & Analysis
1371
+ */
1372
+ type MotifData = {
1373
+ /** The first statement, exactly as it sounds in the melody. */
1374
+ notes: NoteEvent[];
1375
+ /**
1376
+ * Semitones between consecutive notes. This is the transposition-invariant
1377
+ * identity of the cell: two statements a fifth apart share it.
1378
+ */
1379
+ intervals: number[];
1380
+ /**
1381
+ * Onset-to-onset gaps as multiples of the first gap, so an even cell reads
1382
+ * `[1, 1, 1]` whatever the tempo or note values it is written in.
1383
+ */
1384
+ rhythm: number[];
1385
+ /** Every statement found, in time order; the first is `notes`. */
1386
+ occurrences: MotifOccurrence[];
1387
+ /** Why this counts as a motif. */
1388
+ rationale: string;
1389
+ };
1390
+ /**
1391
+ * Options controlling {@link extractMotifs}.
1392
+ *
1393
+ * @category Arrangement & Analysis
1394
+ */
1395
+ type ExtractMotifsOptions = {
1396
+ /**
1397
+ * Shortest cell to report, in notes.
1398
+ *
1399
+ * @defaultValue 3
1400
+ */
1401
+ minNotes?: number;
1402
+ /**
1403
+ * Longest cell to report, in notes.
1404
+ *
1405
+ * @defaultValue 8
1406
+ */
1407
+ maxNotes?: number;
1408
+ /**
1409
+ * How many statements a cell needs before it is reported.
1410
+ *
1411
+ * @defaultValue 2
1412
+ */
1413
+ minOccurrences?: number;
1414
+ /**
1415
+ * Upper bound on the work this call may do.
1416
+ *
1417
+ * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
1418
+ */
1419
+ budget?: number;
1420
+ };
1421
+ /**
1422
+ * How one statement of a motif stands to another.
1423
+ *
1424
+ * `'transposition'` is the literal one — every interval preserved, which is what
1425
+ * makes a sequence built on it a *real* sequence. `'tonalTransposition'` shifts
1426
+ * by scale degrees instead and lets the key resize the intervals, which is what
1427
+ * makes a sequence built on it a *tonal* sequence; the two are different devices
1428
+ * and are never reported as one. `'augmentation'` and `'diminution'` name the
1429
+ * relation only when the pitches are untouched, so an inversion in doubled note
1430
+ * values reads as `'inversion'` with a `timeRatio` of 2 rather than losing the
1431
+ * inversion to the stretch.
1432
+ *
1433
+ * @category Arrangement & Analysis
1434
+ */
1435
+ type MotifRelationKind = 'repetition' | 'transposition' | 'tonalTransposition' | 'inversion' | 'retrograde' | 'retrogradeInversion' | 'augmentation' | 'diminution';
1436
+ /**
1437
+ * The transformation that turns one motif into another.
1438
+ *
1439
+ * @category Arrangement & Analysis
1440
+ */
1441
+ type MotifRelation = {
1442
+ /** The named transformation. */
1443
+ kind: MotifRelationKind;
1444
+ /**
1445
+ * Whether the second statement begins where the first ends, which is what
1446
+ * separates a sequence from a restatement elsewhere in the piece: a
1447
+ * `'transposition'` that is one is a real sequence, a `'tonalTransposition'`
1448
+ * that is one is a tonal sequence.
1449
+ */
1450
+ sequence: boolean;
1451
+ /** Semitones from the first statement's first note to the second's. */
1452
+ semitones: number;
1453
+ /** The same distance named as a spelled interval. */
1454
+ interval: SpelledInterval;
1455
+ /** Scale degrees the second statement stands above the first, when tonal. */
1456
+ degrees?: number;
1457
+ /**
1458
+ * How much the second statement is stretched against the first: 2 for doubled
1459
+ * note values, 0.5 for halved, 1 for unchanged.
1460
+ */
1461
+ timeRatio: number;
1462
+ /** Why the pair was given that name. */
1463
+ rationale: string;
1464
+ };
1465
+ /**
1466
+ * Anything that can be read as a melodic line: a motif, or plain note events.
1467
+ *
1468
+ * @category Arrangement & Analysis
1469
+ */
1470
+ type MelodicPhrase = MotifData | readonly NoteEvent[];
1471
+ /**
1472
+ * How alike two lines are, and how the answer was reached.
1473
+ *
1474
+ * @category Arrangement & Analysis
1475
+ */
1476
+ type MelodicComparison = {
1477
+ /** Overall likeness in [0, 1]; 1 for the same line, transposition included. */
1478
+ similarity: number;
1479
+ /** Likeness of the interval sequences alone, in [0, 1]. */
1480
+ pitchSimilarity: number;
1481
+ /** Likeness of the rhythmic profiles alone, in [0, 1]. */
1482
+ rhythmSimilarity: number;
1483
+ /** Why the lines scored what they did. */
1484
+ rationale: string;
1485
+ };
1486
+ /**
1487
+ * Find the cells a melody keeps coming back to.
1488
+ *
1489
+ * Every window of `minNotes` to `maxNotes` consecutive notes is reduced to its
1490
+ * interval sequence and its rhythmic profile, and windows sharing both are
1491
+ * statements of one motif — so a restatement a fifth higher, or one written in
1492
+ * doubled note values, is found as the same cell rather than as a new one.
1493
+ * Overlapping statements of a repetitive figure are counted once, and a cell
1494
+ * whose statements all sit inside the statements of a longer cell that recurs at
1495
+ * least as often is dropped, so the answer names the longest thing that recurs
1496
+ * rather than every fragment of it.
1497
+ *
1498
+ * The melody is expected to be monophonic, as {@link analyzeVoice} expects a
1499
+ * voice to be; split polyphonic material into lines first. Onsets are expected to
1500
+ * be quantised, since two statements must agree on their rhythm to be recognised
1501
+ * as one motif.
1502
+ *
1503
+ * @param notes The melody, in time order. Notes that never sound are dropped.
1504
+ * @param opts Cell-length bounds and the recurrence threshold; see
1505
+ * {@link ExtractMotifsOptions}.
1506
+ * @returns The motifs found, longest first, then by how often they recur, then
1507
+ * by where they first appear. Empty when nothing recurs.
1508
+ * @example
1509
+ * ```ts
1510
+ * import { extractMotifs } from '@libraz/libcantus';
1511
+ * const notes = [
1512
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1513
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
1514
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
1515
+ * { pitch: 67, startBeat: 4, durationBeat: 1 },
1516
+ * { pitch: 69, startBeat: 5, durationBeat: 1 },
1517
+ * { pitch: 71, startBeat: 6, durationBeat: 1 },
1518
+ * ];
1519
+ * const motifs = extractMotifs(notes);
1520
+ * motifs[0]?.occurrences.map((o) => o.startBeat); // [0, 4]
1521
+ * ```
1522
+ * @category Arrangement & Analysis
1523
+ */
1524
+ declare function extractMotifs(notes: readonly NoteEvent[], opts?: ExtractMotifsOptions): MotifData[];
1525
+ /**
1526
+ * Read a run of notes as a single-statement motif.
1527
+ *
1528
+ * {@link extractMotifs} answers with motifs it found recurring; this is how a
1529
+ * caller names one it already has — a subject, an answer, a phrase lifted out of
1530
+ * a score — so that it can be handed to {@link relateMotifs}.
1531
+ *
1532
+ * @param notes The cell, in time order. Notes that never sound are dropped.
1533
+ * @returns The motif, carrying exactly one occurrence.
1534
+ * @example
1535
+ * ```ts
1536
+ * import { motifFromNotes, relateMotifs } from '@libraz/libcantus';
1537
+ * const subject = motifFromNotes([
1538
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1539
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
1540
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
1541
+ * ]);
1542
+ * const answer = motifFromNotes([
1543
+ * { pitch: 67, startBeat: 3, durationBeat: 1 },
1544
+ * { pitch: 69, startBeat: 4, durationBeat: 1 },
1545
+ * { pitch: 71, startBeat: 5, durationBeat: 1 },
1546
+ * ]);
1547
+ * relateMotifs(subject, answer)?.kind; // 'transposition'
1548
+ * ```
1549
+ * @category Arrangement & Analysis
1550
+ */
1551
+ declare function motifFromNotes(notes: readonly NoteEvent[]): MotifData;
1552
+ /**
1553
+ * Name how two statements of a motif relate.
1554
+ *
1555
+ * The two are compared as shapes, not as pitches: the interval sequences decide
1556
+ * the pitch transformation and the onset gaps decide the timing, so a statement
1557
+ * a fourth higher in doubled note values is recognised for what it is. A pair
1558
+ * whose rhythms do not correspond — the same way round for a repetition or a
1559
+ * transposition, back to front for a retrograde — is not a transformation of the
1560
+ * motif but a different figure, and answers null; {@link melodicSimilarity} is
1561
+ * what scores those.
1562
+ *
1563
+ * With a `key` in hand the search also asks whether the second statement is the
1564
+ * first moved by scale degrees rather than by semitones. That is the tonal
1565
+ * answer to the real one, and the two are kept apart: a real transposition keeps
1566
+ * every interval literally, a tonal one lets the key resize them, so a subject
1567
+ * answered a step higher reads as `'transposition'` when its major second stays
1568
+ * major and as `'tonalTransposition'` when the scale turns it minor.
1569
+ *
1570
+ * @param a The model statement.
1571
+ * @param b The statement to name against it.
1572
+ * @param key Key context for the tonal reading; without it, a statement that
1573
+ * only holds diatonically is not recognised.
1574
+ * @returns The relation, or null when the two stand in none.
1575
+ * @example
1576
+ * ```ts
1577
+ * import { majorKey, motifFromNotes, relateMotifs } from '@libraz/libcantus';
1578
+ * const model = motifFromNotes([
1579
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1580
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
1581
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
1582
+ * ]);
1583
+ * const answer = motifFromNotes([
1584
+ * { pitch: 62, startBeat: 3, durationBeat: 1 },
1585
+ * { pitch: 64, startBeat: 4, durationBeat: 1 },
1586
+ * { pitch: 65, startBeat: 5, durationBeat: 1 },
1587
+ * ]);
1588
+ * relateMotifs(model, answer, majorKey(0))?.kind; // 'tonalTransposition'
1589
+ * ```
1590
+ * @category Arrangement & Analysis
1591
+ */
1592
+ declare function relateMotifs(a: MotifData, b: MotifData, key?: KeyScale): MotifRelation | null;
1593
+ /**
1594
+ * Compare two lines and say why they scored what they did.
1595
+ *
1596
+ * The metric is a normalised edit distance over the interval sequence, weighted
1597
+ * against the same over the rhythmic profile. Intervals rather than pitches,
1598
+ * because the same tune in another key is the same tune; edit distance rather
1599
+ * than a step-by-step correlation, because a variant that adds a passing note or
1600
+ * drops one still has to line up with its model, and only an alignment-based
1601
+ * measure can do that. Substituting one interval for another costs by how far
1602
+ * apart they are rather than a flat step, so an answer that widens a third to a
1603
+ * fourth stays close to its model while one that replaces it with a leap does
1604
+ * not. The rhythm carries the smaller share: a melody is recognised mostly by
1605
+ * its pitch shape, but a figure in a wholly different rhythm is a different
1606
+ * figure.
1607
+ *
1608
+ * @param a The first line: a motif, or plain note events.
1609
+ * @param b The second line.
1610
+ * @returns The overall likeness, the two terms behind it, and a rationale.
1611
+ * @example
1612
+ * ```ts
1613
+ * import { compareMelodies } from '@libraz/libcantus';
1614
+ * const line = [
1615
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1616
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
1617
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
1618
+ * ];
1619
+ * compareMelodies(line, line).similarity; // 1
1620
+ * ```
1621
+ * @category Arrangement & Analysis
1622
+ */
1623
+ declare function compareMelodies(a: MelodicPhrase, b: MelodicPhrase): MelodicComparison;
1624
+ /**
1625
+ * How alike two lines are, in [0, 1].
1626
+ *
1627
+ * The number {@link compareMelodies} reaches, for callers that only want to rank
1628
+ * phrases: 1 for the same line — a transposition of it included, since the
1629
+ * measure is built on intervals — and lower the further two lines are from being
1630
+ * each other's variant. Symmetric in its arguments, and deterministic.
1631
+ *
1632
+ * @param a The first line: a motif, or plain note events.
1633
+ * @param b The second line.
1634
+ * @returns The likeness in [0, 1].
1635
+ * @example
1636
+ * ```ts
1637
+ * import { melodicSimilarity } from '@libraz/libcantus';
1638
+ * const line = [
1639
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1640
+ * { pitch: 62, startBeat: 1, durationBeat: 1 },
1641
+ * { pitch: 64, startBeat: 2, durationBeat: 1 },
1642
+ * ];
1643
+ * melodicSimilarity(line, line); // 1
1644
+ * ```
1645
+ * @category Arrangement & Analysis
1646
+ */
1647
+ declare function melodicSimilarity(a: MelodicPhrase, b: MelodicPhrase): number;
1648
+ /**
1649
+ * Read the shape of a line.
1650
+ *
1651
+ * The step directions are the literal answer; the shape is what they add up to.
1652
+ * A line is called ascending or descending when most of its motion runs one way,
1653
+ * not only when every step does — a climb with one dip in it is still heard as a
1654
+ * climb — and an arch when it rises to an interior peak and comes back down.
1655
+ * Anything that keeps changing direction is a wave, and a line that does not move
1656
+ * is static.
1657
+ *
1658
+ * @param notes The line: a motif, or plain note events. Notes that never sound
1659
+ * are dropped.
1660
+ * @returns The directions, the shape, the peak and trough, and a rationale.
1661
+ * @example
1662
+ * ```ts
1663
+ * import { melodicContour } from '@libraz/libcantus';
1664
+ * const notes = [
1665
+ * { pitch: 60, startBeat: 0, durationBeat: 1 },
1666
+ * { pitch: 64, startBeat: 1, durationBeat: 1 },
1667
+ * { pitch: 60, startBeat: 2, durationBeat: 1 },
1668
+ * ];
1669
+ * melodicContour(notes).shape; // 'arch'
1670
+ * ```
1671
+ * @category Arrangement & Analysis
1672
+ */
1673
+ declare function melodicContour(notes: MelodicPhrase): MelodicContour;
1674
+
1675
+ /**
1676
+ * Harmonic reduction: which chords carry the progression and which decorate it.
1677
+ *
1678
+ * `analyzeVoice` can say that a note is a passing tone; a timeline is flat, so
1679
+ * nothing could say the same of a chord. This is that answer one level up — the
1680
+ * middle chord of `Cmaj7 - C#dim7 - Dm7` is an embellishment, and the frame
1681
+ * under `I - IV - #ivo7 - V - I` is `I - IV - V - I`.
1682
+ *
1683
+ * **The position taken here.** What counts as structural is a theoretical
1684
+ * choice, not a fact, and this module makes the functional one: *a chord is
1685
+ * structural unless it is demonstrably an embellishment of its neighbours*. The
1686
+ * frame is what carries harmonic function in the prevailing key — the tonic, the
1687
+ * dominant, and the chords that cadence — so those are never reduced away; a
1688
+ * chord that tonicizes something other than the tonic is prolonging, not
1689
+ * framing, however dominant its sonority. Everything else keeps its place unless
1690
+ * its root traces one of two figures against the chords around it, the same two
1691
+ * figures {@link analyzeVoice} matches at the note level: a step through
1692
+ * (passing) and a step away and back (neighbor). Defaulting to structural, and
1693
+ * demoting only on evidence, is what keeps a reduction from quietly deleting
1694
+ * harmony it merely failed to recognize.
1695
+ *
1696
+ * The salience reading — that the frame is whatever lasts longest, regardless of
1697
+ * function — is the main alternative, and it is a live one for repertoire where
1698
+ * function is weak. {@link ReduceProgressionOptions.basis} switches to it: the
1699
+ * figures stay the same, only what protects a chord from them changes.
1700
+ *
1701
+ * Both readings are deterministic: the same timeline and key always reduce the
1702
+ * same way.
1703
+ */
1704
+
1705
+ /**
1706
+ * The place a chord holds in a progression.
1707
+ *
1708
+ * `'structural'` is the frame; `'passing'` and `'neighbor'` are the two
1709
+ * embellishing figures, spelled exactly as {@link TheoryLabel} spells them for
1710
+ * a note, so one legend covers both levels of analysis.
1711
+ *
1712
+ * @category Arrangement & Analysis
1713
+ */
1714
+ type ReductionLevel = 'structural' | 'passing' | 'neighbor';
1715
+ /**
1716
+ * A chord with the place it holds, the beats it holds it for, and a short
1717
+ * rationale.
1718
+ *
1719
+ * One entry per timeline segment, in the same order. The entry carries the
1720
+ * segment's own beat range, so the chords that survive a filter still say when
1721
+ * they sound: holding the structural chords fixed is what a reharmonizer needs,
1722
+ * and it cannot hold a chord at a place the filter threw away.
1723
+ *
1724
+ * @category Arrangement & Analysis
1725
+ */
1726
+ type ReducedChord = {
1727
+ chord: Chord;
1728
+ startBeat: number;
1729
+ endBeat: number;
1730
+ level: ReductionLevel;
1731
+ rationale: string;
1732
+ };
1733
+ /**
1734
+ * What a reduction treats as the frame.
1735
+ *
1736
+ * `'function'` is the functional reading: the tonic, the dominant and the
1737
+ * chords that cadence are the frame. `'duration'` is the salience reading: the
1738
+ * chords that hold the harmony at least as long as the median chord are the
1739
+ * frame, whatever function they carry.
1740
+ *
1741
+ * @category Arrangement & Analysis
1742
+ */
1743
+ type ReductionBasis = 'function' | 'duration';
1744
+ /**
1745
+ * Options for {@link reduceProgression}.
1746
+ *
1747
+ * @category Arrangement & Analysis
1748
+ */
1749
+ type ReduceProgressionOptions = {
1750
+ /**
1751
+ * Which reading decides what the frame is; see {@link ReductionBasis}.
1752
+ *
1753
+ * @defaultValue `'function'`
1754
+ */
1755
+ basis?: ReductionBasis;
1756
+ };
1757
+ /**
1758
+ * Reduce a chord progression to its frame, labelling every chord.
1759
+ *
1760
+ * One entry comes back per timeline segment, in the same order, so the two read
1761
+ * in step. Filtering the result to `'structural'` is the skeleton view — the
1762
+ * `I - IV - V - I` under a progression that spells far more chords than that —
1763
+ * and holding those chords fixed is what a reharmonizer needs in order to
1764
+ * rewrite the surface without moving the harmony. Each entry carries its own
1765
+ * `startBeat` and `endBeat`, so a filtered chord still knows where it sounds
1766
+ * once the segments it was read against are gone.
1767
+ *
1768
+ * A chord is structural unless one of two figures demotes it: its root stepping
1769
+ * in from the previous chord and on to the next in the same direction (passing),
1770
+ * or stepping away from a harmony that returns (neighbor). Chords carrying the
1771
+ * frame are never demoted, and what carries the frame is the module's stated
1772
+ * theoretical position — see {@link ReductionBasis} for the reading this takes
1773
+ * by default and the one it can be switched to.
1774
+ *
1775
+ * Chords separated by a rest form no figure across it, the way
1776
+ * {@link detectCadences} pairs no chords across one.
1777
+ *
1778
+ * @param timeline The chord timeline to reduce.
1779
+ * @param key The prevailing key, or the key in force at a given beat, for music
1780
+ * that modulates.
1781
+ * @param opts Which reading decides the frame; see
1782
+ * {@link ReduceProgressionOptions}.
1783
+ * @returns One labelled chord per segment, in time order.
1784
+ * @throws If `opts.basis` is not one of the readings {@link ReductionBasis}
1785
+ * names.
1786
+ * @example
1787
+ * ```ts
1788
+ * import { chordTimelineFromChords, majorKey, reduceProgression } from '@libraz/libcantus';
1789
+ * const timeline = chordTimelineFromChords(
1790
+ * [
1791
+ * { rootPc: 0, quality: 'maj7', startBeat: 0 },
1792
+ * { rootPc: 1, quality: 'dim7', startBeat: 4 },
1793
+ * { rootPc: 2, quality: 'min7', startBeat: 8 },
1794
+ * ],
1795
+ * 12,
1796
+ * );
1797
+ * reduceProgression(timeline, majorKey(0)).map((entry) => entry.level);
1798
+ * // ['structural', 'passing', 'structural']
1799
+ * ```
1800
+ * @category Arrangement & Analysis
1801
+ */
1802
+ declare function reduceProgression(timeline: ChordTimeline, key: KeyContext, opts?: ReduceProgressionOptions): ReducedChord[];
1803
+
1804
+ export { reduceProgression as $, type ArrangementAnalysis as A, type TrackRole as B, type ChordMatch as C, type DetectChordOptions as D, type ExtractMotifsOptions as E, type FormSection as F, analyzeArrangement as G, type Hypermeter as H, compareMelodies as I, createArrangementSession as J, type KeyMatch as K, detectChord as L, MODAL_SCALE_NAMES as M, detectChordBest as N, detectKey as O, type Phrase as P, detectKeyBest as Q, type ReduceProgressionOptions as R, type StructuralCadence as S, type TensionPoint as T, detectKeyFromNotes as U, extractMotifs as V, hypermeter as W, melodicContour as X, melodicSimilarity as Y, motifFromNotes as Z, phrasesFromTimeline as _, type ArrangementOptions as a, relateMotifs as a0, sectionsFromNotes as a1, structuralCadences as a2, tensionCurve as a3, tensionCurveFrom as a4, type ArrangementSession as b, type ArrangementTrack as c, type Conflict as d, type ContourDirection as e, type DetectKeyOptions as f, type FormSectionOptions as g, type HypermeterOptions as h, type KeyVariant as i, type MelodicComparison as j, type MelodicContour as k, type MelodicContourShape as l, type MelodicPhrase as m, type ModalCandidate as n, type ModalScaleName as o, type MotifData as p, type MotifOccurrence as q, type MotifRelation as r, type MotifRelationKind as s, type PhraseOptions as t, type PhraseSignal as u, type ReducedChord as v, type ReductionBasis as w, type ReductionLevel as x, type TrackAnalysis as y, type TrackEdit as z };