@libraz/libcantus 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -2
- package/dist/analyze/index.cjs +1697 -0
- package/dist/analyze/index.cjs.map +1 -0
- package/dist/analyze/index.d.cts +429 -0
- package/dist/analyze/index.d.ts +429 -0
- package/dist/analyze/index.js +51 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/chunk-5HTNMQW4.js +653 -0
- package/dist/chunk-5HTNMQW4.js.map +1 -0
- package/dist/chunk-7Y7WF64D.js +410 -0
- package/dist/chunk-7Y7WF64D.js.map +1 -0
- package/dist/chunk-A4532ZFR.js +259 -0
- package/dist/chunk-A4532ZFR.js.map +1 -0
- package/dist/chunk-BHUT2S5G.js +22 -0
- package/dist/chunk-BHUT2S5G.js.map +1 -0
- package/dist/chunk-CCBLVAPK.js +42 -0
- package/dist/chunk-CCBLVAPK.js.map +1 -0
- package/dist/chunk-COKTX2G5.js +907 -0
- package/dist/chunk-COKTX2G5.js.map +1 -0
- package/dist/chunk-DYWFNC4A.js +152 -0
- package/dist/chunk-DYWFNC4A.js.map +1 -0
- package/dist/chunk-GQKN5KDT.js +159 -0
- package/dist/chunk-GQKN5KDT.js.map +1 -0
- package/dist/chunk-LN7ZZMD2.js +530 -0
- package/dist/chunk-LN7ZZMD2.js.map +1 -0
- package/dist/chunk-MSWFZF7J.js +118 -0
- package/dist/chunk-MSWFZF7J.js.map +1 -0
- package/dist/chunk-R3S2LUBH.js +2722 -0
- package/dist/chunk-R3S2LUBH.js.map +1 -0
- package/dist/chunk-SDM3VJJ5.js +41 -0
- package/dist/chunk-SDM3VJJ5.js.map +1 -0
- package/dist/chunk-XTC3X6MS.js +816 -0
- package/dist/chunk-XTC3X6MS.js.map +1 -0
- package/dist/chunk-ZXW5YIWK.js +55 -0
- package/dist/chunk-ZXW5YIWK.js.map +1 -0
- package/dist/core/index.cjs +432 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +210 -0
- package/dist/core/index.d.ts +210 -0
- package/dist/core/index.js +73 -0
- package/dist/core/index.js.map +1 -0
- package/dist/function-2bJW1Zp_.d.ts +187 -0
- package/dist/function-r2sh-jOO.d.cts +187 -0
- package/dist/generate/index.cjs +3787 -0
- package/dist/generate/index.cjs.map +1 -0
- package/dist/generate/index.d.cts +980 -0
- package/dist/generate/index.d.ts +980 -0
- package/dist/generate/index.js +52 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index-BMPxwjwV.d.ts +151 -0
- package/dist/index-BOBXBNwP.d.cts +157 -0
- package/dist/index-BdeQ1r_p.d.cts +56 -0
- package/dist/index-BreoJRw0.d.ts +127 -0
- package/dist/index-CGMZNCx6.d.cts +140 -0
- package/dist/index-CGMZNCx6.d.ts +140 -0
- package/dist/index-CM5W4rcm.d.ts +157 -0
- package/dist/index-CP3wAfEH.d.ts +56 -0
- package/dist/index-Cl7mhOFM.d.cts +164 -0
- package/dist/index-Cl7mhOFM.d.ts +164 -0
- package/dist/index-DZ1M21do.d.cts +127 -0
- package/dist/index-D_ZTUNKp.d.cts +151 -0
- package/dist/index.cjs +5277 -4893
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -2621
- package/dist/index.d.ts +14 -2621
- package/dist/index.js +170 -6152
- package/dist/index.js.map +1 -1
- package/dist/model/index.cjs +2435 -0
- package/dist/model/index.cjs.map +1 -0
- package/dist/model/index.d.cts +630 -0
- package/dist/model/index.d.ts +630 -0
- package/dist/model/index.js +21 -0
- package/dist/model/index.js.map +1 -0
- package/dist/styled-BjXtOFNk.d.cts +282 -0
- package/dist/styled-D7BAx3S3.d.ts +282 -0
- package/dist/theory/index.cjs +1677 -0
- package/dist/theory/index.cjs.map +1 -0
- package/dist/theory/index.d.cts +535 -0
- package/dist/theory/index.d.ts +535 -0
- package/dist/theory/index.js +149 -0
- package/dist/theory/index.js.map +1 -0
- package/dist/types-MGP29r3t.d.cts +30 -0
- package/dist/types-MGP29r3t.d.ts +30 -0
- package/package.json +31 -1
- package/src/analyze/arrange/index.ts +21 -0
- package/src/analyze/arrange/internal.ts +125 -0
- package/src/analyze/arrange/tension.ts +187 -0
- package/src/analyze/arrange/tracks.ts +279 -0
- package/src/{detect → analyze/detect}/index.ts +82 -23
- package/src/analyze/functional/borrowed.ts +84 -0
- package/src/analyze/functional/cadence.ts +66 -0
- package/src/analyze/functional/function.ts +184 -0
- package/src/analyze/functional/index.ts +23 -0
- package/src/analyze/functional/internal.ts +29 -0
- package/src/analyze/functional/roman.ts +393 -0
- package/src/analyze/index.ts +49 -0
- package/src/{timeline → analyze/timeline}/index.ts +87 -24
- package/src/{analysis → analyze/voice}/index.ts +81 -12
- package/src/core/index.ts +47 -0
- package/src/{interval → core/interval}/index.ts +15 -1
- package/src/{meter → core/meter}/index.ts +108 -5
- package/src/{pitch → core/pitch}/index.ts +58 -3
- package/src/{random → core/random}/index.ts +13 -1
- package/src/{tuning → core/tuning}/index.ts +28 -1
- package/src/{bass → generate/bass}/index.ts +66 -17
- package/src/{countermelody → generate/countermelody}/index.ts +54 -24
- package/src/{drums → generate/drums}/fills.ts +31 -20
- package/src/{drums → generate/drums}/index.ts +127 -9
- package/src/{drums → generate/drums}/internal.ts +7 -1
- package/src/{drums → generate/drums}/rng.ts +2 -2
- package/src/{drums → generate/drums}/swing.ts +7 -2
- package/src/{groove → generate/groove}/index.ts +118 -22
- package/src/{harmonize → generate/harmonize}/index.ts +96 -23
- package/src/generate/index.ts +59 -0
- package/src/{motif → generate/motif}/index.ts +94 -17
- package/src/{progression → generate/progression}/index.ts +61 -27
- package/src/{reharmony → generate/reharmony}/index.ts +46 -15
- package/src/{rhythm → generate/rhythm}/index.ts +47 -13
- package/src/index.ts +130 -116
- package/src/model/chord.ts +423 -0
- package/src/model/index.ts +5 -947
- package/src/model/interval.ts +82 -0
- package/src/model/key.ts +266 -0
- package/src/model/note.ts +176 -0
- package/src/model/progression.ts +161 -0
- package/src/model/shared.ts +12 -0
- package/src/{chord → theory/chord}/index.ts +85 -4
- package/src/{chordscale → theory/chordscale}/index.ts +67 -9
- package/src/{counterpoint → theory/counterpoint}/index.ts +23 -2
- package/src/{harmony → theory/harmony}/index.ts +19 -2
- package/src/theory/index.ts +107 -0
- package/src/{safety → theory/safety}/index.ts +59 -12
- package/src/theory/scale/degrees.ts +123 -0
- package/src/theory/scale/index.ts +28 -0
- package/src/theory/scale/key.ts +56 -0
- package/src/theory/scale/masks.ts +153 -0
- package/src/{spelling → theory/spelling}/index.ts +21 -3
- package/src/{symbol → theory/symbol}/index.ts +91 -36
- package/src/theory/voicing/index.ts +5 -0
- package/src/theory/voicing/internal.ts +168 -0
- package/src/theory/voicing/leading.ts +112 -0
- package/src/theory/voicing/satb.ts +195 -0
- package/src/theory/voicing/styled.ts +176 -0
- package/src/arrange/index.ts +0 -409
- package/src/functional/index.ts +0 -602
- package/src/scale/index.ts +0 -215
- package/src/voicing/index.ts +0 -523
- /package/src/{types.ts → core/types.ts} +0 -0
- /package/src/{drums → generate/drums}/beat.ts +0 -0
- /package/src/{drums → generate/drums}/euclid.ts +0 -0
- /package/src/{drums → generate/drums}/ghost.ts +0 -0
- /package/src/{drums → generate/drums}/hihat.ts +0 -0
- /package/src/{drums → generate/drums}/hit.ts +0 -0
- /package/src/{drums → generate/drums}/kick.ts +0 -0
- /package/src/{drums → generate/drums}/percussion.ts +0 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { T as TimeSignature } from '../index-Cl7mhOFM.cjs';
|
|
2
|
+
import { K as KeyScale, N as NoteEvent } from '../types-MGP29r3t.cjs';
|
|
3
|
+
import { V as VoiceSnapshot, N as NoteSafety, S as SafetyProfile } from '../index-DZ1M21do.cjs';
|
|
4
|
+
import { b as ChordTimeline, C as CadenceHit } from '../index-D_ZTUNKp.cjs';
|
|
5
|
+
export { a as ChordSegment, c as ChordTimelineOptions, d as ChordTimelineResult, e as chordTimelineFromChords, f as chordTimelineFromNotes, g as detectCadences } from '../index-D_ZTUNKp.cjs';
|
|
6
|
+
import { C as Chord, a as ChordQuality } from '../index-BOBXBNwP.cjs';
|
|
7
|
+
export { B as BorrowedSource, C as Cadence, a as ChordAnalysis, H as HarmonicFunction, b as analyzeChord, c as borrowedSource, d as detectCadence, f as functionOf, i as isBorrowedChord, e as isDiatonic, g as isMinorKey, p as parallelKey, s as secondaryDominant } from '../function-r2sh-jOO.cjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Roman-numeral parsing and formatting.
|
|
11
|
+
*
|
|
12
|
+
* The {@link ROMAN_STYLE} table is the single source of truth for both
|
|
13
|
+
* directions, so {@link chordToRoman} and {@link romanToChord} stay mutual
|
|
14
|
+
* inverses by construction.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build the chord denoted by a Roman numeral in a key.
|
|
19
|
+
*
|
|
20
|
+
* Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`
|
|
21
|
+
* suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions
|
|
22
|
+
* (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash
|
|
23
|
+
* (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree
|
|
24
|
+
* whose root becomes a local major tonic for the applied chord. Inverted chords
|
|
25
|
+
* carry a `bassPc`.
|
|
26
|
+
*
|
|
27
|
+
* The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==
|
|
28
|
+
* `m7b5`), since no half-diminished triad exists. Extension and added-tone
|
|
29
|
+
* suffixes are the canonical case-sensitive forms emitted by
|
|
30
|
+
* {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->
|
|
31
|
+
* `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->
|
|
32
|
+
* `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->
|
|
33
|
+
* `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.
|
|
34
|
+
* A figure string that is neither a recognized inversion nor a supported
|
|
35
|
+
* quality suffix throws rather than silently degrading to a triad.
|
|
36
|
+
*
|
|
37
|
+
* @param text The Roman numeral.
|
|
38
|
+
* @param key The prevailing key.
|
|
39
|
+
* @returns The chord.
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { romanToChord, majorKey } from '@libraz/libcantus';
|
|
43
|
+
* romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }
|
|
44
|
+
* ```
|
|
45
|
+
* @category Functional Harmony
|
|
46
|
+
*/
|
|
47
|
+
declare function romanToChord(text: string, key: KeyScale): Chord;
|
|
48
|
+
/**
|
|
49
|
+
* Render a chord as a Roman numeral relative to a key.
|
|
50
|
+
*
|
|
51
|
+
* Diatonic roots take their scale-degree numeral directly, so numerals are
|
|
52
|
+
* correct in both major and minor keys (and any custom scale). The quality
|
|
53
|
+
* selects the case and suffix; chromatic roots receive a flat/sharp spelling by
|
|
54
|
+
* convention. When the chord carries a `bassPc` on a chord tone, a figured-bass
|
|
55
|
+
* inversion (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true
|
|
56
|
+
* seventh chords; added-tone and extended qualities have no lossless figure and
|
|
57
|
+
* render in root position instead (the bass is dropped, pitch classes are
|
|
58
|
+
* preserved).
|
|
59
|
+
*
|
|
60
|
+
* @param chord The chord to name.
|
|
61
|
+
* @param key The prevailing key.
|
|
62
|
+
* @returns The Roman numeral string.
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';
|
|
66
|
+
* chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)
|
|
67
|
+
* ```
|
|
68
|
+
* @category Functional Harmony
|
|
69
|
+
*/
|
|
70
|
+
declare function chordToRoman(chord: Chord, key: KeyScale): string;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A theory annotation attached to a note.
|
|
74
|
+
*
|
|
75
|
+
* @category Arrangement & Analysis
|
|
76
|
+
*/
|
|
77
|
+
type TheoryLabel = {
|
|
78
|
+
kind: 'chordTone';
|
|
79
|
+
role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
|
|
80
|
+
} | {
|
|
81
|
+
kind: 'tension';
|
|
82
|
+
degree: 9 | 11 | 13;
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'avoid';
|
|
85
|
+
} | {
|
|
86
|
+
kind: 'passing';
|
|
87
|
+
} | {
|
|
88
|
+
kind: 'neighbor';
|
|
89
|
+
} | {
|
|
90
|
+
kind: 'suspension';
|
|
91
|
+
type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';
|
|
92
|
+
resolveTo: number;
|
|
93
|
+
} | {
|
|
94
|
+
kind: 'anticipation';
|
|
95
|
+
} | {
|
|
96
|
+
kind: 'escape';
|
|
97
|
+
} | {
|
|
98
|
+
kind: 'needsResolution';
|
|
99
|
+
resolveTo: number;
|
|
100
|
+
} | {
|
|
101
|
+
kind: 'leadingTone';
|
|
102
|
+
resolveTo: number;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* A note with its theory labels and a short rationale.
|
|
106
|
+
*
|
|
107
|
+
* @category Arrangement & Analysis
|
|
108
|
+
*/
|
|
109
|
+
type AnalyzedNote = {
|
|
110
|
+
noteId: number;
|
|
111
|
+
labels: TheoryLabel[];
|
|
112
|
+
rationale?: string;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* A single note in a monophonic voice: a {@link NoteEvent} with a stable id.
|
|
116
|
+
*
|
|
117
|
+
* @category Arrangement & Analysis
|
|
118
|
+
*/
|
|
119
|
+
type VoiceNote = NoteEvent & {
|
|
120
|
+
id: number;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Label every note of a voice with its theory roles.
|
|
124
|
+
*
|
|
125
|
+
* Each note is classified against the chord sounding at its beat. Chord tones
|
|
126
|
+
* get a role label; non-chord tones are matched, in order, as suspensions
|
|
127
|
+
* (prepared by an identical consonant pitch and resolving by step), passing
|
|
128
|
+
* tones, neighbors, anticipations, and escape tones, then fall back to tension,
|
|
129
|
+
* avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted
|
|
130
|
+
* additionally.
|
|
131
|
+
*
|
|
132
|
+
* The voice is expected to be monophonic (one note at a time). Notes sharing an
|
|
133
|
+
* onset are treated as simultaneous cluster members, not melodic neighbors, so
|
|
134
|
+
* they receive no melodic labels (suspension, passing, neighbor, anticipation,
|
|
135
|
+
* escape) — only harmonic ones. Callers with truly polyphonic material should
|
|
136
|
+
* split it into monophonic sub-voices first, as `analyzeArrangement` does.
|
|
137
|
+
*
|
|
138
|
+
* @param voice The monophonic voice, in time order.
|
|
139
|
+
* @param chordAtBeat Chord sounding at a given beat, or null.
|
|
140
|
+
* @param key Key context for leading-tone detection.
|
|
141
|
+
* @param otherVoicesAtBeat Other sounding voices at a given beat.
|
|
142
|
+
* @returns One annotation per input note.
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';
|
|
146
|
+
* const voice = [
|
|
147
|
+
* { id: 0, pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
148
|
+
* { id: 1, pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
149
|
+
* { id: 2, pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
150
|
+
* ];
|
|
151
|
+
* const cMajor = makeChord(0, 'maj');
|
|
152
|
+
* const labels = analyzeVoice(voice, () => cMajor, majorKey(0), () => []);
|
|
153
|
+
* labels; // one AnalyzedNote per input note, in the same order
|
|
154
|
+
* ```
|
|
155
|
+
* @category Arrangement & Analysis
|
|
156
|
+
*/
|
|
157
|
+
declare function analyzeVoice(voice: VoiceNote[], chordAtBeat: (beat: number) => Chord | null, key: KeyScale, otherVoicesAtBeat: (beat: number) => VoiceSnapshot[]): AnalyzedNote[];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Arrangement track-role analysis: a single harmony is inferred from every
|
|
161
|
+
* track's notes pooled together, each track is then analysed against that shared
|
|
162
|
+
* harmony, and notes that clash with the sounding chord are collected as
|
|
163
|
+
* conflicts.
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The musical role a track plays in the arrangement.
|
|
168
|
+
*
|
|
169
|
+
* @category Arrangement & Analysis
|
|
170
|
+
*/
|
|
171
|
+
type TrackRole = 'melody' | 'harmony' | 'bass' | 'other';
|
|
172
|
+
/**
|
|
173
|
+
* One input track: its notes plus optional name and role.
|
|
174
|
+
*
|
|
175
|
+
* @category Arrangement & Analysis
|
|
176
|
+
*/
|
|
177
|
+
type ArrangementTrack = {
|
|
178
|
+
name?: string;
|
|
179
|
+
role?: TrackRole;
|
|
180
|
+
notes: NoteEvent[];
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* A track after analysis: its resolved name, role, and per-note annotations.
|
|
184
|
+
*
|
|
185
|
+
* @category Arrangement & Analysis
|
|
186
|
+
*/
|
|
187
|
+
type TrackAnalysis = {
|
|
188
|
+
name: string;
|
|
189
|
+
role: TrackRole;
|
|
190
|
+
/** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
|
|
191
|
+
notes: AnalyzedNote[];
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* A note that clashes with the harmony sounding beneath it.
|
|
195
|
+
*
|
|
196
|
+
* @category Arrangement & Analysis
|
|
197
|
+
*/
|
|
198
|
+
type Conflict = {
|
|
199
|
+
beat: number;
|
|
200
|
+
trackName: string;
|
|
201
|
+
pitch: number;
|
|
202
|
+
/** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */
|
|
203
|
+
safety: NoteSafety;
|
|
204
|
+
/** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */
|
|
205
|
+
reasons: number;
|
|
206
|
+
rationale?: string;
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* The full result of {@link analyzeArrangement}.
|
|
210
|
+
*
|
|
211
|
+
* @category Arrangement & Analysis
|
|
212
|
+
*/
|
|
213
|
+
type ArrangementAnalysis = {
|
|
214
|
+
key: KeyScale;
|
|
215
|
+
timeline: ChordTimeline;
|
|
216
|
+
segmentConfidence: number[];
|
|
217
|
+
cadences: CadenceHit[];
|
|
218
|
+
tracks: TrackAnalysis[];
|
|
219
|
+
/** Notes clashing with the sounding harmony, worst severity first. */
|
|
220
|
+
conflicts: Conflict[];
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Options controlling {@link analyzeArrangement} and {@link tensionCurve}.
|
|
224
|
+
*
|
|
225
|
+
* @category Arrangement & Analysis
|
|
226
|
+
*/
|
|
227
|
+
type ArrangementOptions = {
|
|
228
|
+
/** Key context; inferred from the pooled notes when omitted. */
|
|
229
|
+
key?: KeyScale;
|
|
230
|
+
/**
|
|
231
|
+
* Time signature; defaults to 4/4.
|
|
232
|
+
*
|
|
233
|
+
* @defaultValue `4/4`
|
|
234
|
+
*/
|
|
235
|
+
ts?: TimeSignature;
|
|
236
|
+
/**
|
|
237
|
+
* Chord-slot length in beats; defaults to one bar of `ts`.
|
|
238
|
+
*
|
|
239
|
+
* @defaultValue one bar of `ts`
|
|
240
|
+
*/
|
|
241
|
+
harmonicRhythm?: number;
|
|
242
|
+
/**
|
|
243
|
+
* Safety profile used for conflict detection; defaults to `pop`.
|
|
244
|
+
*
|
|
245
|
+
* @defaultValue `'pop'`
|
|
246
|
+
*/
|
|
247
|
+
profile?: SafetyProfile;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Analyse a whole arrangement against a single inferred harmony.
|
|
251
|
+
*
|
|
252
|
+
* The chord timeline and key are inferred from the pooled notes of every track
|
|
253
|
+
* (see {@link chordTimelineFromNotes}); pooling all voices is robust even when
|
|
254
|
+
* roles are absent or a track doubles the harmony, so it is preferred over
|
|
255
|
+
* deriving the harmony from a subset. Zero- and negative-length notes never
|
|
256
|
+
* sound, so they are dropped at ingest and appear in neither the annotations
|
|
257
|
+
* nor the conflicts.
|
|
258
|
+
*
|
|
259
|
+
* Voice analysis assumes one monophonic voice at a time, so each track is
|
|
260
|
+
* first partitioned into monophonic sub-voices (a polyphonic block-chord track
|
|
261
|
+
* fans out into one sub-voice per simultaneous note; a monophonic track is
|
|
262
|
+
* unaffected). Each sub-voice is labelled note-by-note against the harmony
|
|
263
|
+
* with {@link analyzeVoice}, seeing every other sub-voice — including siblings
|
|
264
|
+
* within the same track — as its accompaniment. Every note is then re-checked
|
|
265
|
+
* with {@link evaluateSafety} at its onset and again at each chord change it
|
|
266
|
+
* sustains across, so a held note that clashes with a later chord is caught:
|
|
267
|
+
* evaluations that are not {@link NoteSafety.Safe} become conflicts, one per
|
|
268
|
+
* clashing beat, sorted worst severity first and then by beat.
|
|
269
|
+
*
|
|
270
|
+
* @param tracks The tracks to analyse.
|
|
271
|
+
* @param opts Analysis options; see {@link ArrangementOptions}.
|
|
272
|
+
* @returns The inferred harmony, per-track annotations, cadences, and conflicts.
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* import { analyzeArrangement } from '@libraz/libcantus';
|
|
276
|
+
* const melody = [
|
|
277
|
+
* { pitch: 60, startBeat: 0, durationBeat: 2 },
|
|
278
|
+
* { pitch: 67, startBeat: 2, durationBeat: 2 },
|
|
279
|
+
* ];
|
|
280
|
+
* const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
|
|
281
|
+
* conflicts; // notes clashing with the inferred harmony, worst severity first
|
|
282
|
+
* ```
|
|
283
|
+
* @category Arrangement & Analysis
|
|
284
|
+
*/
|
|
285
|
+
declare function analyzeArrangement(tracks: ArrangementTrack[], opts?: ArrangementOptions): ArrangementAnalysis;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Arrangement tension analysis: a coarse harmonic-tension curve sampled at
|
|
289
|
+
* regular beats across a whole multi-track piece, combining the sounding
|
|
290
|
+
* chord's harmonic function, the dissonance of the sounding notes, and their
|
|
291
|
+
* registral span into a single normalized reading.
|
|
292
|
+
*/
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* A tension reading sampled at a beat.
|
|
296
|
+
*
|
|
297
|
+
* @category Arrangement & Analysis
|
|
298
|
+
*/
|
|
299
|
+
type TensionPoint = {
|
|
300
|
+
beat: number;
|
|
301
|
+
/** Combined tension in [0, 1]. */
|
|
302
|
+
tension: number;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Sample the harmonic tension of an arrangement at regular beats.
|
|
306
|
+
*
|
|
307
|
+
* At each sample beat the tension combines three normalized terms:
|
|
308
|
+
* - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,
|
|
309
|
+
* tonic or no chord `0`), weighted {@link FUNCTION_WEIGHT};
|
|
310
|
+
* - the dissonance of the sounding notes — the larger of the share of sounding
|
|
311
|
+
* pitches that are not chord tones and the share that {@link evaluateSafety}
|
|
312
|
+
* rates {@link NoteSafety.Dissonant} — weighted {@link DISSONANCE_WEIGHT};
|
|
313
|
+
* - the registral span of the sounding notes, saturating at
|
|
314
|
+
* {@link SPAN_SATURATION} semitones, weighted {@link SPAN_WEIGHT}.
|
|
315
|
+
*
|
|
316
|
+
* The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks
|
|
317
|
+
* pooled together, so the result is deterministic and self-contained.
|
|
318
|
+
*
|
|
319
|
+
* @param tracks The tracks to sample.
|
|
320
|
+
* @param opts Analysis options plus an optional `step` (default one beat).
|
|
321
|
+
* @returns One {@link TensionPoint} per sampled beat, in beat order.
|
|
322
|
+
* @throws If `step` is not positive.
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* import { tensionCurve } from '@libraz/libcantus';
|
|
326
|
+
* const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];
|
|
327
|
+
* const curve = tensionCurve([{ notes }], { step: 1 });
|
|
328
|
+
* curve; // one { beat, tension } sample per beat, in beat order
|
|
329
|
+
* ```
|
|
330
|
+
* @category Arrangement & Analysis
|
|
331
|
+
*/
|
|
332
|
+
declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOptions & {
|
|
333
|
+
step?: number;
|
|
334
|
+
}): TensionPoint[];
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Recognition: infer a chord name from a set of pitches, or a key from a set of
|
|
338
|
+
* pitch classes. This is the inverse direction of the chord/scale builders,
|
|
339
|
+
* which only go name -> notes.
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* A candidate chord interpretation of a pitch set.
|
|
344
|
+
*
|
|
345
|
+
* @category Recognition
|
|
346
|
+
*/
|
|
347
|
+
type ChordMatch = {
|
|
348
|
+
rootPc: number;
|
|
349
|
+
quality: ChordQuality;
|
|
350
|
+
/** Chord tones absent from the input (an incomplete voicing). */
|
|
351
|
+
missingPcs: number[];
|
|
352
|
+
/** Input pitch classes not belonging to the chord. */
|
|
353
|
+
extraPcs: number[];
|
|
354
|
+
/** True when the input pitch-class set equals the chord exactly. */
|
|
355
|
+
exact: boolean;
|
|
356
|
+
/** Inversion implied by the lowest note: 0 root position, 1 first, ... */
|
|
357
|
+
inversion: number;
|
|
358
|
+
/** Bass pitch class when inverted (the lowest note is not the root). */
|
|
359
|
+
bassPc?: number;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* A candidate key interpretation of a pitch-class set.
|
|
363
|
+
*
|
|
364
|
+
* @category Recognition
|
|
365
|
+
*/
|
|
366
|
+
type KeyMatch = {
|
|
367
|
+
key: KeyScale;
|
|
368
|
+
mode: 'major' | 'minor';
|
|
369
|
+
/** Fraction of input pitch classes that are in the scale, in [0, 1]. */
|
|
370
|
+
fit: number;
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Identify chords matching a set of pitches.
|
|
374
|
+
*
|
|
375
|
+
* Every input pitch class is tried as a root against every known chord quality.
|
|
376
|
+
* A match is reported when all of the chord's tones are present in the input, or
|
|
377
|
+
* when the only absent tone is the perfect fifth (a common omission in shell
|
|
378
|
+
* voicings); matches are ranked best-first by fewest extra notes, then fewest
|
|
379
|
+
* missing notes, then most specific (largest) chord. An exact match (no extras,
|
|
380
|
+
* no missing) is flagged and ranked first.
|
|
381
|
+
*
|
|
382
|
+
* @param pitches MIDI pitches or bare pitch classes (octave-agnostic).
|
|
383
|
+
* @returns Ranked chord interpretations (may be empty).
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* import { detectChord } from '@libraz/libcantus';
|
|
387
|
+
* const matches = detectChord([60, 64, 67]); // C E G
|
|
388
|
+
* matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }
|
|
389
|
+
* ```
|
|
390
|
+
* @category Recognition
|
|
391
|
+
*/
|
|
392
|
+
declare function detectChord(pitches: number[]): ChordMatch[];
|
|
393
|
+
/**
|
|
394
|
+
* The single best chord interpretation of a pitch set, or null if none.
|
|
395
|
+
*
|
|
396
|
+
* @param pitches MIDI pitches or bare pitch classes.
|
|
397
|
+
* @returns The top-ranked chord, or null when nothing matches.
|
|
398
|
+
* @example
|
|
399
|
+
* ```ts
|
|
400
|
+
* import { detectChordBest } from '@libraz/libcantus';
|
|
401
|
+
* detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }
|
|
402
|
+
* ```
|
|
403
|
+
* @category Recognition
|
|
404
|
+
*/
|
|
405
|
+
declare function detectChordBest(pitches: number[]): Chord | null;
|
|
406
|
+
/**
|
|
407
|
+
* Rank major and minor keys by how well they contain a set of pitch classes.
|
|
408
|
+
*
|
|
409
|
+
* The tonic is weighted so that, among equally-fitting keys, the one whose root
|
|
410
|
+
* appears in the input is preferred. Minor candidates are scored against the
|
|
411
|
+
* natural, harmonic, and melodic minor variants and keep the best of the three,
|
|
412
|
+
* so a minor cadence containing the leading tone still resolves to its own
|
|
413
|
+
* tonic; the returned key is always the natural-minor scale. Returns all 24
|
|
414
|
+
* keys ranked best-first, or an empty array for an empty input (mirroring
|
|
415
|
+
* {@link detectChord}).
|
|
416
|
+
*
|
|
417
|
+
* @param pitches MIDI pitches or bare pitch classes.
|
|
418
|
+
* @returns Ranked key interpretations (empty for an empty input).
|
|
419
|
+
* @example
|
|
420
|
+
* ```ts
|
|
421
|
+
* import { detectKey } from '@libraz/libcantus';
|
|
422
|
+
* const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale
|
|
423
|
+
* keys[0].mode; // 'major', with keys[0].key.rootPc === 0
|
|
424
|
+
* ```
|
|
425
|
+
* @category Recognition
|
|
426
|
+
*/
|
|
427
|
+
declare function detectKey(pitches: number[]): KeyMatch[];
|
|
428
|
+
|
|
429
|
+
export { type AnalyzedNote, type ArrangementAnalysis, type ArrangementOptions, type ArrangementTrack, CadenceHit, type ChordMatch, ChordTimeline, type Conflict, type KeyMatch, type TensionPoint, type TheoryLabel, type TrackAnalysis, type TrackRole, type VoiceNote, analyzeArrangement, analyzeVoice, chordToRoman, detectChord, detectChordBest, detectKey, romanToChord, tensionCurve };
|