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