@libraz/libcantus 1.2.0 → 1.4.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/NOTICE +63 -0
- package/README.md +7 -1
- package/README_ja.md +17 -11
- package/dist/analyze/index.cjs +31 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +161 -13
- package/dist/analyze/index.d.ts +161 -13
- package/dist/analyze/index.js +37 -13
- package/dist/{chunk-NB25P3EH.js → chunk-2G5MCGYU.js} +33 -11
- package/dist/chunk-2G5MCGYU.js.map +1 -0
- package/dist/{chunk-NM7UAMC3.cjs → chunk-3FDBMIPR.cjs} +64 -64
- package/dist/chunk-3FDBMIPR.cjs.map +1 -0
- package/dist/{chunk-HPVSH7SF.cjs → chunk-3MDOEFIQ.cjs} +618 -954
- package/dist/chunk-3MDOEFIQ.cjs.map +1 -0
- package/dist/{chunk-FBH75DAY.js → chunk-5MWRBYAT.js} +560 -159
- package/dist/chunk-5MWRBYAT.js.map +1 -0
- package/dist/{chunk-J7FHVZII.cjs → chunk-6TTBEON3.cjs} +3074 -740
- package/dist/chunk-6TTBEON3.cjs.map +1 -0
- package/dist/{chunk-UMWZ4ZET.js → chunk-7NQ6AL3H.js} +4 -4
- package/dist/chunk-7NQ6AL3H.js.map +1 -0
- package/dist/{chunk-HWHGOVO6.cjs → chunk-E3QO3UET.cjs} +31 -23
- package/dist/chunk-E3QO3UET.cjs.map +1 -0
- package/dist/{chunk-H2KJHBW4.js → chunk-FJFULJZE.js} +2375 -191
- package/dist/chunk-FJFULJZE.js.map +1 -0
- package/dist/{chunk-Q6LFSAKV.js → chunk-FK3DGAIZ.js} +2657 -323
- package/dist/chunk-FK3DGAIZ.js.map +1 -0
- package/dist/chunk-GEDTJHAC.cjs.map +1 -1
- package/dist/{chunk-RFIBF2RR.js → chunk-H7FVMBUC.js} +138 -65
- package/dist/chunk-H7FVMBUC.js.map +1 -0
- package/dist/{chunk-DSMJRXCZ.cjs → chunk-HYSVSFNI.cjs} +931 -530
- package/dist/chunk-HYSVSFNI.cjs.map +1 -0
- package/dist/{chunk-35NBV2FT.cjs → chunk-KMYGRFZ6.cjs} +47 -24
- package/dist/chunk-KMYGRFZ6.cjs.map +1 -0
- package/dist/chunk-LQZQDBCD.cjs +5513 -0
- package/dist/chunk-LQZQDBCD.cjs.map +1 -0
- package/dist/{chunk-PM4XJE2Y.cjs → chunk-P3CPXAJ5.cjs} +76 -54
- package/dist/chunk-P3CPXAJ5.cjs.map +1 -0
- package/dist/{chunk-S5BKZZGM.cjs → chunk-S3QELJUE.cjs} +72 -56
- package/dist/chunk-S3QELJUE.cjs.map +1 -0
- package/dist/{chunk-PUKJP2Z5.js → chunk-TXFWSU5D.js} +31 -8
- package/dist/chunk-TXFWSU5D.js.map +1 -0
- package/dist/{chunk-76ZBTRG2.cjs → chunk-U4EQTDGN.cjs} +258 -218
- package/dist/chunk-U4EQTDGN.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -1
- package/dist/{chunk-HYJJ2V2I.js → chunk-UTE5A36R.js} +483 -819
- package/dist/chunk-UTE5A36R.js.map +1 -0
- package/dist/{chunk-NFJ4P6ZZ.js → chunk-UZKQISRF.js} +17 -9
- package/dist/chunk-UZKQISRF.js.map +1 -0
- package/dist/{chunk-7KG3RTYS.js → chunk-VL5NXVCN.js} +281 -155
- package/dist/chunk-VL5NXVCN.js.map +1 -0
- package/dist/{chunk-CGBADIO5.cjs → chunk-WOMCXN2W.cjs} +328 -202
- package/dist/chunk-WOMCXN2W.cjs.map +1 -0
- package/dist/{chunk-3R3QVKY5.cjs → chunk-YDGMWH43.cjs} +397 -324
- package/dist/chunk-YDGMWH43.cjs.map +1 -0
- package/dist/{chunk-AGOVW7U6.js → chunk-YYVXRV3V.js} +72 -56
- package/dist/chunk-YYVXRV3V.js.map +1 -0
- package/dist/{chunk-M5RYMAFI.js → chunk-ZLNSH4UL.js} +71 -31
- package/dist/chunk-ZLNSH4UL.js.map +1 -0
- package/dist/{coerce-DRERzdve.d.cts → coerce-CUhHc3Tu.d.ts} +16 -8
- package/dist/{coerce-DP0vYu-x.d.ts → coerce-Ytt2Eiq9.d.cts} +16 -8
- package/dist/core/index.cjs +5 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +9 -8
- package/dist/core/index.d.ts +9 -8
- package/dist/core/index.js +4 -4
- package/dist/generate/index.cjs +20 -8
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +105 -17
- package/dist/generate/index.d.ts +105 -17
- package/dist/generate/index.js +19 -7
- package/dist/{index-CL4b4gUQ.d.ts → index-5VJgKz_Q.d.cts} +6 -2
- package/dist/{index-B9QLziDe.d.cts → index-BLuDesVG.d.ts} +20 -13
- package/dist/{index-BHIUyjhZ.d.ts → index-BV6APVa6.d.cts} +20 -13
- package/dist/{index-BMweUlm5.d.cts → index-Bf0Sv8a9.d.ts} +6 -2
- package/dist/{index-Dw2DP7vm.d.ts → index-BkT21EMv.d.cts} +3 -2
- package/dist/{index-BL5N2Oyz.d.cts → index-C7KCaTRz.d.ts} +3 -2
- package/dist/{index-Cy15oTvh.d.ts → index-C9MYbuKS.d.cts} +7 -3
- package/dist/{index-Cy15oTvh.d.cts → index-C9MYbuKS.d.ts} +7 -3
- package/dist/{index-BNSM-DSk.d.cts → index-DVInzCDS.d.cts} +533 -28
- package/dist/{index-9eoQs2C1.d.ts → index-Dg-SAfhz.d.ts} +6 -4
- package/dist/{index-BJgP97N1.d.ts → index-jgeug9EA.d.ts} +533 -28
- package/dist/{index-cHvbYgJI.d.cts → index-pQihL4z7.d.cts} +6 -4
- package/dist/index.cjs +49 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.js +55 -19
- package/dist/{masks-Cc7-MzzY.d.ts → masks-BnzXnBj0.d.cts} +3 -2
- package/dist/{masks-pADfTOiD.d.cts → masks-tycmkrgs.d.ts} +3 -2
- package/dist/model/index.cjs +12 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +259 -48
- package/dist/model/index.d.ts +259 -48
- package/dist/model/index.js +11 -11
- package/dist/{positional-DJgs6ltD.d.ts → positional-CscpxtlE.d.cts} +3 -2
- package/dist/{positional-DMZer-FM.d.cts → positional-DLnfUgqG.d.ts} +3 -2
- package/dist/{index-BRMbPkHE.d.ts → profile-B4WqcM4H.d.ts} +85 -610
- package/dist/{index-DGCNKAUr.d.cts → profile-BJd1XkPB.d.cts} +85 -610
- package/dist/theory/index.cjs +5 -5
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +18 -14
- package/dist/theory/index.d.ts +18 -14
- package/dist/theory/index.js +4 -4
- package/dist/{index-CJBBiAmU.d.ts → types-7mGFwvMm.d.ts} +1210 -31
- package/dist/{index-BBWNZS0f.d.cts → types-_ofpb2be.d.cts} +1210 -31
- package/package.json +13 -13
- package/dist/chunk-35NBV2FT.cjs.map +0 -1
- package/dist/chunk-3R3QVKY5.cjs.map +0 -1
- package/dist/chunk-76ZBTRG2.cjs.map +0 -1
- package/dist/chunk-7KG3RTYS.js.map +0 -1
- package/dist/chunk-AGOVW7U6.js.map +0 -1
- package/dist/chunk-CGBADIO5.cjs.map +0 -1
- package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
- package/dist/chunk-FBH75DAY.js.map +0 -1
- package/dist/chunk-H2KJHBW4.js.map +0 -1
- package/dist/chunk-HPVSH7SF.cjs.map +0 -1
- package/dist/chunk-HWHGOVO6.cjs.map +0 -1
- package/dist/chunk-HYJJ2V2I.js.map +0 -1
- package/dist/chunk-J7FHVZII.cjs.map +0 -1
- package/dist/chunk-M5RYMAFI.js.map +0 -1
- package/dist/chunk-NB25P3EH.js.map +0 -1
- package/dist/chunk-NFJ4P6ZZ.js.map +0 -1
- package/dist/chunk-NM7UAMC3.cjs.map +0 -1
- package/dist/chunk-PM4XJE2Y.cjs.map +0 -1
- package/dist/chunk-PUKJP2Z5.js.map +0 -1
- package/dist/chunk-Q6LFSAKV.js.map +0 -1
- package/dist/chunk-RFIBF2RR.js.map +0 -1
- package/dist/chunk-S5BKZZGM.cjs.map +0 -1
- package/dist/chunk-SGLEQVJH.cjs +0 -3329
- package/dist/chunk-SGLEQVJH.cjs.map +0 -1
- package/dist/chunk-UMWZ4ZET.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { b as
|
|
3
|
-
import { h as NoteEvent, e as KeyScale } from './index-
|
|
1
|
+
import { b as MeterLike, T as TimeSignature, c as MeterMap } from './index-Bf0Sv8a9.js';
|
|
2
|
+
import { c as ChordLike, K as KeyLike, i as ChordToneRole, r as SpelledKeyLike, b as Chord, n as ResolvedKey, j as KeyRelation, e as ChordSegment, V as VoiceSnapshot, g as ChordSpan } from './coerce-CUhHc3Tu.js';
|
|
3
|
+
import { h as NoteEvent, e as KeyScale, S as SpelledInterval } from './index-C9MYbuKS.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The vocabulary the analyze layer explains itself in.
|
|
@@ -119,21 +119,31 @@ declare function borrowedSource(chord: ChordLike, key: KeyLike): BorrowedSource;
|
|
|
119
119
|
* spelled key signature.
|
|
120
120
|
*/
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* The named cadence types {@link detectCadence} classifies a chord pair as.
|
|
124
|
+
*
|
|
125
|
+
* @category Functional Harmony
|
|
126
|
+
*/
|
|
127
|
+
declare const CADENCE_TYPES: readonly ["authentic", "plagal", "half", "deceptive", "phrygian", "modal"];
|
|
128
|
+
/**
|
|
129
|
+
* The cadence a chord pair forms.
|
|
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
|
+
* @category Functional Harmony
|
|
137
|
+
*/
|
|
138
|
+
type CadenceType = (typeof CADENCE_TYPES)[number];
|
|
122
139
|
/**
|
|
123
140
|
* A recognized cadence, with the voice-leading facts its reading rests on.
|
|
124
141
|
*
|
|
125
142
|
* @category Functional Harmony
|
|
126
143
|
*/
|
|
127
144
|
type CadenceResult = {
|
|
128
|
-
/**
|
|
129
|
-
|
|
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;
|
|
145
|
+
/** The cadence the chord pair forms, or null when it forms none. */
|
|
146
|
+
type: CadenceType | null;
|
|
137
147
|
/**
|
|
138
148
|
* How conclusive an authentic cadence is: `'perfect'` when it is a PAC,
|
|
139
149
|
* `'imperfect'` when it is an IAC.
|
|
@@ -336,6 +346,8 @@ declare function detectCadence(from: ChordLike, to: ChordLike, key: KeyLike, opt
|
|
|
336
346
|
* @param text The Roman numeral.
|
|
337
347
|
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
338
348
|
* @returns The chord.
|
|
349
|
+
* @throws If `key` names no key, `text` is not a string, or it is no numeral
|
|
350
|
+
* this reads.
|
|
339
351
|
* @example
|
|
340
352
|
* ```ts
|
|
341
353
|
* import { romanToChord, majorKey } from '@libraz/libcantus';
|
|
@@ -358,7 +370,7 @@ type ChordToRomanOptions = {
|
|
|
358
370
|
* Off by default: naming the root is always a correct spelling, whereas
|
|
359
371
|
* whether a chromatic dominant is genuinely applied is a reading only the
|
|
360
372
|
* caller can make. Turning it on names the chords {@link secondaryDominant}
|
|
361
|
-
* builds as the applied numerals they are, except in
|
|
373
|
+
* builds as the applied numerals they are, except in four cases, where the
|
|
362
374
|
* numeral names the root against the home key as it does with this off:
|
|
363
375
|
*
|
|
364
376
|
* - The target degree carries no perfect fifth: nothing tonicizes a
|
|
@@ -369,6 +381,9 @@ type ChordToRomanOptions = {
|
|
|
369
381
|
* - The chord is diatonic to the key, which keeps the function of its degree
|
|
370
382
|
* rather than tonicizing another. `G7` is `VII7` in A minor and `I7` in C
|
|
371
383
|
* mixolydian, not `V7/III` and `V7/IV`.
|
|
384
|
+
* - The chord is borrowed from the parallel mode, which sounds its own degree:
|
|
385
|
+
* the Picardy tonic and the major IV of a minor key are `I` and `IV`, not
|
|
386
|
+
* `V/iv` and `V/VII`.
|
|
372
387
|
*/
|
|
373
388
|
applied?: boolean;
|
|
374
389
|
/**
|
|
@@ -499,12 +514,18 @@ declare function explainRoman(chord: ChordLike, key: KeyLike, opts?: ExplainRoma
|
|
|
499
514
|
* spelled key signature.
|
|
500
515
|
*/
|
|
501
516
|
|
|
517
|
+
/**
|
|
518
|
+
* The harmonic functions {@link functionOf} can assign.
|
|
519
|
+
*
|
|
520
|
+
* @category Functional Harmony
|
|
521
|
+
*/
|
|
522
|
+
declare const HARMONIC_FUNCTIONS: readonly ["tonic", "subdominant", "dominant"];
|
|
502
523
|
/**
|
|
503
524
|
* The three broad harmonic functions of tonal music.
|
|
504
525
|
*
|
|
505
526
|
* @category Functional Harmony
|
|
506
527
|
*/
|
|
507
|
-
type HarmonicFunction =
|
|
528
|
+
type HarmonicFunction = (typeof HARMONIC_FUNCTIONS)[number];
|
|
508
529
|
/**
|
|
509
530
|
* Whether a key's scale has a minor third and no major third (a minor key).
|
|
510
531
|
*
|
|
@@ -643,13 +664,14 @@ declare function analyzeChord(chord: ChordLike, key: KeyLike, opts?: AnalyzeChor
|
|
|
643
664
|
* @param targetDegree 1-based scale degree to tonicize: 5 is the dominant, so
|
|
644
665
|
* `secondaryDominant(5, majorKey(0))` is the V of V.
|
|
645
666
|
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
646
|
-
* @returns A dominant-seventh chord a fifth above the target's root
|
|
667
|
+
* @returns A dominant-seventh chord a fifth above the target's root, spelled the
|
|
668
|
+
* way the key writes it (in Ab major the V of IV is Eb7, not D#7).
|
|
647
669
|
* @throws If `targetDegree` is not an integer naming a degree in `key`.
|
|
648
670
|
* @see {@link secondaryDominantOf} to tonicize a chord that has no degree in
|
|
649
671
|
* the key, or when no key is at hand.
|
|
650
672
|
* @category Functional Harmony
|
|
651
673
|
*/
|
|
652
|
-
declare function secondaryDominant(targetDegree: number, key:
|
|
674
|
+
declare function secondaryDominant(targetDegree: number, key: SpelledKeyLike): Chord;
|
|
653
675
|
/**
|
|
654
676
|
* The secondary dominant (V7) that tonicizes a chord.
|
|
655
677
|
*
|
|
@@ -717,7 +739,8 @@ type PivotChord = {
|
|
|
717
739
|
* so it may be any scale; its numerals then follow the {@link chordToRoman}
|
|
718
740
|
* fallback of naming roots against the parallel major.
|
|
719
741
|
*
|
|
720
|
-
* @param from The key being left, as a key name, a key/scale, or a `Key
|
|
742
|
+
* @param from The key being left, as a key name, a key/scale, or a `Key`; the
|
|
743
|
+
* shared chords are spelled the way it writes them.
|
|
721
744
|
* @param to The key being entered, in any of the same forms.
|
|
722
745
|
* @returns The shared triads, ascending by their degree in `from`.
|
|
723
746
|
* @example
|
|
@@ -728,7 +751,7 @@ type PivotChord = {
|
|
|
728
751
|
* ```
|
|
729
752
|
* @category Functional Harmony
|
|
730
753
|
*/
|
|
731
|
-
declare function pivotChords(from:
|
|
754
|
+
declare function pivotChords(from: SpelledKeyLike, to: SpelledKeyLike): PivotChord[];
|
|
732
755
|
|
|
733
756
|
/**
|
|
734
757
|
* The key as it changes over time.
|
|
@@ -1107,7 +1130,9 @@ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote
|
|
|
1107
1130
|
* onset are treated as simultaneous cluster members, not melodic neighbors, so
|
|
1108
1131
|
* they receive no melodic labels (suspension, passing, neighbor, anticipation,
|
|
1109
1132
|
* escape) — only harmonic ones. Callers with truly polyphonic material should
|
|
1110
|
-
* split it into monophonic sub-voices first, as `analyzeArrangement` does.
|
|
1133
|
+
* split it into monophonic sub-voices first, as `analyzeArrangement` does. A
|
|
1134
|
+
* note of zero length sounds nothing: it gets no melodic labels and is no other
|
|
1135
|
+
* note's neighbour.
|
|
1111
1136
|
*
|
|
1112
1137
|
* @param voice The monophonic voice, in time order. A note without an `id` is
|
|
1113
1138
|
* identified by its position, so plain note events can be passed straight in.
|
|
@@ -1116,7 +1141,8 @@ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote
|
|
|
1116
1141
|
* covering the whole voice, or a callback giving the key in force at a given
|
|
1117
1142
|
* beat, for music that modulates.
|
|
1118
1143
|
* @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
|
|
1119
|
-
* none, which is the whole story for a solo line
|
|
1144
|
+
* none, which is the whole story for a solo line: a suspension's dissonance
|
|
1145
|
+
* is then judged against the chord.
|
|
1120
1146
|
* @returns One annotation per input note.
|
|
1121
1147
|
* @example
|
|
1122
1148
|
* ```ts
|
|
@@ -1142,7 +1168,11 @@ declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: n
|
|
|
1142
1168
|
* @category Arrangement & Analysis
|
|
1143
1169
|
*/
|
|
1144
1170
|
type ChordTimeline = {
|
|
1145
|
-
/**
|
|
1171
|
+
/**
|
|
1172
|
+
* The chord sounding at a beat, or null when no segment covers it. An
|
|
1173
|
+
* inferred timeline covers a rest shorter than its harmonic rhythm with the
|
|
1174
|
+
* chord around it; see {@link chordTimelineFromNotes}.
|
|
1175
|
+
*/
|
|
1146
1176
|
at: (beat: number) => Chord | null;
|
|
1147
1177
|
segments: ChordSegment[];
|
|
1148
1178
|
};
|
|
@@ -1235,13 +1265,14 @@ type ChordTimelineOptions = {
|
|
|
1235
1265
|
* The first downbeat is beat 0 whatever precedes it, so an upbeat is written
|
|
1236
1266
|
* at negative beats — a one-beat pickup in 4/4 sounds at beat -1. Declaring
|
|
1237
1267
|
* its length rejects a note that starts before the pickup does; leave it
|
|
1238
|
-
* unset to accept any
|
|
1268
|
+
* unset to accept any onset within ±`Number.MAX_SAFE_INTEGER` (2^53 - 1).
|
|
1239
1269
|
*
|
|
1240
1270
|
* @defaultValue no declared pickup
|
|
1241
1271
|
*/
|
|
1242
1272
|
pickupBeats?: number;
|
|
1243
1273
|
/**
|
|
1244
|
-
* Where chord boundaries may fall; defaults to `'dynamic'`.
|
|
1274
|
+
* Where chord boundaries may fall; defaults to `'dynamic'`. Any other value
|
|
1275
|
+
* is an input error.
|
|
1245
1276
|
*
|
|
1246
1277
|
* @defaultValue `'dynamic'`
|
|
1247
1278
|
*/
|
|
@@ -1332,8 +1363,13 @@ type ChordTimelineResult = {
|
|
|
1332
1363
|
* Each settled segment's chord is inferred from a pitch-class weight histogram
|
|
1333
1364
|
* of the notes overlapping it (weight = overlap duration x velocity x
|
|
1334
1365
|
* metric-accent bonus for onsets in the segment). Adjacent segments carrying
|
|
1335
|
-
* the identical chord are merged
|
|
1336
|
-
* so `at(beat)` returns null
|
|
1366
|
+
* the identical chord are merged. Under `'dynamic'` segmentation a silence at
|
|
1367
|
+
* least `harmonicRhythm` long produces no segment, so `at(beat)` returns null
|
|
1368
|
+
* there, while a shorter rest is absorbed into the chord around it — a C triad
|
|
1369
|
+
* for two beats, two beats of rest and a G triad read C over beats 0-4. Under `'grid'` any
|
|
1370
|
+
* window with nothing sounding produces no segment. A window whose notes give
|
|
1371
|
+
* fewer than two tones of any chord produces none either: a lone pitch class
|
|
1372
|
+
* names no chord. Each segment carries a confidence in [0, 1]
|
|
1337
1373
|
* — the fraction of its weight explained by chord tones, reduced when the match
|
|
1338
1374
|
* is inexact, and duration-weighted across anything merged into it.
|
|
1339
1375
|
*
|
|
@@ -1351,7 +1387,8 @@ type ChordTimelineResult = {
|
|
|
1351
1387
|
* @param opts Analysis options; see {@link ChordTimelineOptions}.
|
|
1352
1388
|
* @returns The inferred timeline, the key regions it ran against, the key that
|
|
1353
1389
|
* prevails across them, and per-segment confidences.
|
|
1354
|
-
* @throws If `harmonicRhythm` is not positive
|
|
1390
|
+
* @throws If `harmonicRhythm` is not positive, or `segmentation` names neither
|
|
1391
|
+
* mode.
|
|
1355
1392
|
* @example
|
|
1356
1393
|
* ```ts
|
|
1357
1394
|
* import { chordTimelineFromNotes } from '@libraz/libcantus';
|
|
@@ -1406,10 +1443,9 @@ type CadenceHit = {
|
|
|
1406
1443
|
* @example
|
|
1407
1444
|
* ```ts
|
|
1408
1445
|
* import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
|
|
1409
|
-
* const
|
|
1410
|
-
* { pitch
|
|
1411
|
-
*
|
|
1412
|
-
* ];
|
|
1446
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
1447
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
1448
|
+
* const notes = [...triad([55, 59, 62], 0), ...triad([48, 52, 55], 4)]; // G, then C
|
|
1413
1449
|
* const { timeline, prevailingKey } = chordTimelineFromNotes(notes);
|
|
1414
1450
|
* const cadences = detectCadences(timeline, prevailingKey);
|
|
1415
1451
|
* ```
|
|
@@ -1417,4 +1453,1147 @@ type CadenceHit = {
|
|
|
1417
1453
|
*/
|
|
1418
1454
|
declare function detectCadences(timeline: ChordTimeline, key: KeyContext): CadenceHit[];
|
|
1419
1455
|
|
|
1420
|
-
|
|
1456
|
+
/**
|
|
1457
|
+
* Sections: the largest unit this library reads, recovered from repetition.
|
|
1458
|
+
*
|
|
1459
|
+
* A section is found, not named. What the analysis can see is that a span of
|
|
1460
|
+
* music is a restatement of an earlier one, so the spans are labelled by order
|
|
1461
|
+
* of first appearance — A, B, C — and nothing more is claimed. Calling a
|
|
1462
|
+
* repeated span a chorus would be a guess dressed as a finding: repetition
|
|
1463
|
+
* alone cannot tell a chorus from a second verse, and the label a listener
|
|
1464
|
+
* would give depends on lyrics, arrangement and loudness that the notes do not
|
|
1465
|
+
* carry.
|
|
1466
|
+
*/
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* A span of music labelled by which earlier span it restates.
|
|
1470
|
+
*
|
|
1471
|
+
* @category Arrangement & Analysis
|
|
1472
|
+
*/
|
|
1473
|
+
type FormSection = {
|
|
1474
|
+
/**
|
|
1475
|
+
* The section's letter, assigned in order of first appearance: the opening
|
|
1476
|
+
* material is A, the first material unlike it is B, and a later restatement
|
|
1477
|
+
* of A is A again. Past Z the letters double (AA, AB), the way spreadsheet
|
|
1478
|
+
* columns do. No functional name — verse, chorus, bridge — is ever inferred.
|
|
1479
|
+
*/
|
|
1480
|
+
label: string;
|
|
1481
|
+
/** First beat of the section; negative when it opens with a pickup. */
|
|
1482
|
+
startBeat: number;
|
|
1483
|
+
/** End of the section, exclusive. */
|
|
1484
|
+
endBeat: number;
|
|
1485
|
+
/** The section's length in bars. */
|
|
1486
|
+
bars: number;
|
|
1487
|
+
/**
|
|
1488
|
+
* Index, in the returned array, of the first section carrying this label; the
|
|
1489
|
+
* section's own index when it is that first appearance.
|
|
1490
|
+
*/
|
|
1491
|
+
firstOccurrence: number;
|
|
1492
|
+
/**
|
|
1493
|
+
* How alike this section and its first occurrence are, in [0, 1]; 1 on the
|
|
1494
|
+
* first occurrence itself. The mean over the units the section was merged
|
|
1495
|
+
* from, each scored against the unit it matched.
|
|
1496
|
+
*/
|
|
1497
|
+
similarity: number;
|
|
1498
|
+
/** How the section came to carry its label. */
|
|
1499
|
+
rationale: string;
|
|
1500
|
+
};
|
|
1501
|
+
/**
|
|
1502
|
+
* Options for {@link sectionsFromNotes}.
|
|
1503
|
+
*
|
|
1504
|
+
* @category Arrangement & Analysis
|
|
1505
|
+
*/
|
|
1506
|
+
type FormSectionOptions = {
|
|
1507
|
+
/**
|
|
1508
|
+
* A single time signature held across the whole span, as sugar for a
|
|
1509
|
+
* one-element `meters`; defaults to 4/4. Giving both is an input error.
|
|
1510
|
+
*
|
|
1511
|
+
* @defaultValue `4/4`
|
|
1512
|
+
*/
|
|
1513
|
+
ts?: MeterLike;
|
|
1514
|
+
/**
|
|
1515
|
+
* The meter as it changes over the span, so a piece that changes metre is cut
|
|
1516
|
+
* at its own bar lines.
|
|
1517
|
+
*
|
|
1518
|
+
* @defaultValue 4/4 throughout
|
|
1519
|
+
*/
|
|
1520
|
+
meters?: MeterLike;
|
|
1521
|
+
/**
|
|
1522
|
+
* Length of the unit spans are compared in, in bars; defaults to one hyperbar.
|
|
1523
|
+
*
|
|
1524
|
+
* Sections are found by comparing equal units and merging the neighbours that
|
|
1525
|
+
* match, so this is the resolution of the answer rather than the length of a
|
|
1526
|
+
* section: a section is any whole number of units long.
|
|
1527
|
+
*
|
|
1528
|
+
* @defaultValue the inferred hyperbar, in bars
|
|
1529
|
+
*/
|
|
1530
|
+
unitBars?: number;
|
|
1531
|
+
/**
|
|
1532
|
+
* How alike two units must be before the later one is called a restatement of
|
|
1533
|
+
* the earlier, in [0, 1].
|
|
1534
|
+
*
|
|
1535
|
+
* @defaultValue `0.8`
|
|
1536
|
+
*/
|
|
1537
|
+
threshold?: number;
|
|
1538
|
+
/**
|
|
1539
|
+
* End of the analysed span in beats; defaults to the end of the last note.
|
|
1540
|
+
*
|
|
1541
|
+
* @defaultValue the end of the last note
|
|
1542
|
+
*/
|
|
1543
|
+
totalBeats?: number;
|
|
1544
|
+
/**
|
|
1545
|
+
* Upper bound on the work this call may do.
|
|
1546
|
+
*
|
|
1547
|
+
* @defaultValue {@link DEFAULT_GENERATION_BUDGET}
|
|
1548
|
+
*/
|
|
1549
|
+
budget?: number;
|
|
1550
|
+
};
|
|
1551
|
+
/**
|
|
1552
|
+
* Recover a piece's sections from what it repeats.
|
|
1553
|
+
*
|
|
1554
|
+
* The span is cut into equal units of `unitBars` bars, each unit is compared
|
|
1555
|
+
* with every unit before it, and a unit alike enough to an earlier one takes
|
|
1556
|
+
* that unit's label. Neighbouring units carrying the same label are then merged,
|
|
1557
|
+
* so an eight-bar section built of two like four-bar units is reported as one
|
|
1558
|
+
* section rather than two.
|
|
1559
|
+
*
|
|
1560
|
+
* Likeness is measured on the melodic line and on the harmonic content
|
|
1561
|
+
* together — the same tune under different harmony is a variation, not a
|
|
1562
|
+
* restatement — and the labels are letters in order of first appearance.
|
|
1563
|
+
* Nothing beyond "this is a restatement of that" is asserted: see
|
|
1564
|
+
* {@link FormSection.label}.
|
|
1565
|
+
*
|
|
1566
|
+
* A pickup belongs to the section it leads into, so material before the first
|
|
1567
|
+
* bar is folded into the opening unit rather than made a section of its own.
|
|
1568
|
+
*
|
|
1569
|
+
* @param notes The piece's notes, in any order. Notes that never sound are
|
|
1570
|
+
* dropped.
|
|
1571
|
+
* @param opts Analysis options; see {@link FormSectionOptions}.
|
|
1572
|
+
* @returns The sections, in time order.
|
|
1573
|
+
* @example
|
|
1574
|
+
* ```ts
|
|
1575
|
+
* import { sectionsFromNotes } from '@libraz/libcantus';
|
|
1576
|
+
* const bar = (pitch: number, at: number) => ({ pitch, startBeat: at, durationBeat: 4 });
|
|
1577
|
+
* const notes = [bar(60, 0), bar(64, 4), bar(67, 8), bar(60, 12)];
|
|
1578
|
+
* sectionsFromNotes(notes, { unitBars: 2 }).map((section) => section.label);
|
|
1579
|
+
* ```
|
|
1580
|
+
* @category Arrangement & Analysis
|
|
1581
|
+
*/
|
|
1582
|
+
declare function sectionsFromNotes(notes: readonly NoteEvent[], opts?: FormSectionOptions): FormSection[];
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* The recurring cells a melody is built from.
|
|
1586
|
+
*
|
|
1587
|
+
* A motif is the interval contour plus the rhythmic profile, so every statement
|
|
1588
|
+
* of it carries the same identity however far it has been transposed or however
|
|
1589
|
+
* wide its note values are written. What this module does is find them: every
|
|
1590
|
+
* window of the melody is reduced to that identity, windows that agree are
|
|
1591
|
+
* grouped, and a group stated often enough is a motif.
|
|
1592
|
+
*/
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* One statement of a motif, addressed in the melody it was found in.
|
|
1596
|
+
*
|
|
1597
|
+
* The notes themselves are not repeated here: a statement is the motif's notes
|
|
1598
|
+
* moved by `transpose` and stretched by `timeRatio`, and `noteIndex` addresses
|
|
1599
|
+
* the original array for a caller that wants them exactly.
|
|
1600
|
+
*
|
|
1601
|
+
* @category Arrangement & Analysis
|
|
1602
|
+
*/
|
|
1603
|
+
type MotifOccurrence = {
|
|
1604
|
+
/**
|
|
1605
|
+
* Index of the statement's first note among the sounding notes of the melody,
|
|
1606
|
+
* in time order. Notes that never sound are dropped before the search, so this
|
|
1607
|
+
* is not an index into the caller's array unless the caller's array has none.
|
|
1608
|
+
*/
|
|
1609
|
+
noteIndex: number;
|
|
1610
|
+
/** Onset of the statement's first note. */
|
|
1611
|
+
startBeat: number;
|
|
1612
|
+
/** End of the statement's last note, exclusive. */
|
|
1613
|
+
endBeat: number;
|
|
1614
|
+
/** Semitones this statement stands above the motif's first statement. */
|
|
1615
|
+
transpose: number;
|
|
1616
|
+
/**
|
|
1617
|
+
* How much this statement is stretched against the motif's first: 2 for a
|
|
1618
|
+
* statement in doubled note values, 0.5 for one in halved values, 1 for one in
|
|
1619
|
+
* the same values.
|
|
1620
|
+
*/
|
|
1621
|
+
timeRatio: number;
|
|
1622
|
+
};
|
|
1623
|
+
/**
|
|
1624
|
+
* A recurring melodic cell.
|
|
1625
|
+
*
|
|
1626
|
+
* Identity is the interval sequence plus the rhythmic profile, so every
|
|
1627
|
+
* statement of the motif carries the same `intervals` and `rhythm` however far
|
|
1628
|
+
* it has been transposed or however wide its note values are written. `notes`
|
|
1629
|
+
* holds one statement — the first — exactly as it sounds.
|
|
1630
|
+
*
|
|
1631
|
+
* @category Arrangement & Analysis
|
|
1632
|
+
*/
|
|
1633
|
+
type MotifData = {
|
|
1634
|
+
/** The first statement, exactly as it sounds in the melody. */
|
|
1635
|
+
notes: NoteEvent[];
|
|
1636
|
+
/**
|
|
1637
|
+
* Semitones between consecutive notes. This is the transposition-invariant
|
|
1638
|
+
* identity of the cell: two statements a fifth apart share it.
|
|
1639
|
+
*/
|
|
1640
|
+
intervals: number[];
|
|
1641
|
+
/**
|
|
1642
|
+
* Onset-to-onset gaps as multiples of the first gap, so an even cell reads
|
|
1643
|
+
* `[1, 1, 1]` whatever the tempo or note values it is written in.
|
|
1644
|
+
*/
|
|
1645
|
+
rhythm: number[];
|
|
1646
|
+
/** Every statement found, in time order; the first is `notes`. */
|
|
1647
|
+
occurrences: MotifOccurrence[];
|
|
1648
|
+
/** Why this counts as a motif. */
|
|
1649
|
+
rationale: string;
|
|
1650
|
+
};
|
|
1651
|
+
/**
|
|
1652
|
+
* Options controlling {@link extractMotifs}.
|
|
1653
|
+
*
|
|
1654
|
+
* @category Arrangement & Analysis
|
|
1655
|
+
*/
|
|
1656
|
+
type ExtractMotifsOptions = {
|
|
1657
|
+
/**
|
|
1658
|
+
* Shortest cell to report, in notes.
|
|
1659
|
+
*
|
|
1660
|
+
* @defaultValue 3
|
|
1661
|
+
*/
|
|
1662
|
+
minNotes?: number;
|
|
1663
|
+
/**
|
|
1664
|
+
* Longest cell to report, in notes.
|
|
1665
|
+
*
|
|
1666
|
+
* @defaultValue 8
|
|
1667
|
+
*/
|
|
1668
|
+
maxNotes?: number;
|
|
1669
|
+
/**
|
|
1670
|
+
* How many statements a cell needs before it is reported.
|
|
1671
|
+
*
|
|
1672
|
+
* @defaultValue 2
|
|
1673
|
+
*/
|
|
1674
|
+
minOccurrences?: number;
|
|
1675
|
+
/**
|
|
1676
|
+
* Upper bound on the work this call may do.
|
|
1677
|
+
*
|
|
1678
|
+
* @defaultValue {@link DEFAULT_GENERATION_BUDGET}
|
|
1679
|
+
*/
|
|
1680
|
+
budget?: number;
|
|
1681
|
+
};
|
|
1682
|
+
/**
|
|
1683
|
+
* Find the cells a melody keeps coming back to.
|
|
1684
|
+
*
|
|
1685
|
+
* Every window of `minNotes` to `maxNotes` consecutive notes is reduced to its
|
|
1686
|
+
* interval sequence and its rhythmic profile, and windows sharing both are
|
|
1687
|
+
* statements of one motif — so a restatement a fifth higher, or one written in
|
|
1688
|
+
* doubled note values, is found as the same cell rather than as a new one.
|
|
1689
|
+
* Overlapping statements of a repetitive figure are counted once, and a cell
|
|
1690
|
+
* whose statements all sit inside the statements of a longer cell that recurs at
|
|
1691
|
+
* least as often is dropped, so the answer names the longest thing that recurs
|
|
1692
|
+
* rather than every fragment of it.
|
|
1693
|
+
*
|
|
1694
|
+
* The melody is expected to be monophonic, as {@link analyzeVoice} expects a
|
|
1695
|
+
* voice to be; split polyphonic material into lines first. Notes struck together
|
|
1696
|
+
* are read as one event, the highest of them standing for it, so a chord is
|
|
1697
|
+
* reduced to its top voice rather than read as a line of its own. Onsets are
|
|
1698
|
+
* expected to be quantised, since two statements must agree on their rhythm to
|
|
1699
|
+
* be recognised as one motif.
|
|
1700
|
+
*
|
|
1701
|
+
* @param notes The melody, in time order. Notes that never sound are dropped.
|
|
1702
|
+
* @param opts Cell-length bounds and the recurrence threshold; see
|
|
1703
|
+
* {@link ExtractMotifsOptions}.
|
|
1704
|
+
* @returns The motifs found, longest first, then by how often they recur, then
|
|
1705
|
+
* by where they first appear. Empty when nothing recurs.
|
|
1706
|
+
* @example
|
|
1707
|
+
* ```ts
|
|
1708
|
+
* import { extractMotifs } from '@libraz/libcantus';
|
|
1709
|
+
* const notes = [
|
|
1710
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1711
|
+
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1712
|
+
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1713
|
+
* { pitch: 67, startBeat: 4, durationBeat: 1 },
|
|
1714
|
+
* { pitch: 69, startBeat: 5, durationBeat: 1 },
|
|
1715
|
+
* { pitch: 71, startBeat: 6, durationBeat: 1 },
|
|
1716
|
+
* ];
|
|
1717
|
+
* const motifs = extractMotifs(notes);
|
|
1718
|
+
* motifs[0]?.occurrences.map((o) => o.startBeat); // [0, 4]
|
|
1719
|
+
* ```
|
|
1720
|
+
* @category Arrangement & Analysis
|
|
1721
|
+
*/
|
|
1722
|
+
declare function extractMotifs(notes: readonly NoteEvent[], opts?: ExtractMotifsOptions): MotifData[];
|
|
1723
|
+
/**
|
|
1724
|
+
* Read a run of notes as a single-statement motif.
|
|
1725
|
+
*
|
|
1726
|
+
* {@link extractMotifs} answers with motifs it found recurring; this is how a
|
|
1727
|
+
* caller names one it already has — a subject, an answer, a phrase lifted out of
|
|
1728
|
+
* a score — so that it can be handed to {@link relateMotifs}.
|
|
1729
|
+
*
|
|
1730
|
+
* The cell is expected to be monophonic, as {@link analyzeVoice} expects a voice
|
|
1731
|
+
* to be. Notes struck together are read as one event, the highest of them
|
|
1732
|
+
* standing for it, so a chord is reduced to its top voice rather than read as a
|
|
1733
|
+
* line of its own.
|
|
1734
|
+
*
|
|
1735
|
+
* @param notes The cell, in time order. Notes that never sound are dropped, and
|
|
1736
|
+
* notes sharing an onset are folded to their top voice.
|
|
1737
|
+
* @returns The motif, carrying exactly one occurrence.
|
|
1738
|
+
* @example
|
|
1739
|
+
* ```ts
|
|
1740
|
+
* import { motifFromNotes, relateMotifs } from '@libraz/libcantus';
|
|
1741
|
+
* const subject = motifFromNotes([
|
|
1742
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1743
|
+
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1744
|
+
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1745
|
+
* ]);
|
|
1746
|
+
* const answer = motifFromNotes([
|
|
1747
|
+
* { pitch: 67, startBeat: 3, durationBeat: 1 },
|
|
1748
|
+
* { pitch: 69, startBeat: 4, durationBeat: 1 },
|
|
1749
|
+
* { pitch: 71, startBeat: 5, durationBeat: 1 },
|
|
1750
|
+
* ]);
|
|
1751
|
+
* relateMotifs(subject, answer)?.kind; // 'transposition'
|
|
1752
|
+
* ```
|
|
1753
|
+
* @category Arrangement & Analysis
|
|
1754
|
+
*/
|
|
1755
|
+
declare function motifFromNotes(notes: readonly NoteEvent[]): MotifData;
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* What every reading of a melody has to agree on first.
|
|
1759
|
+
*
|
|
1760
|
+
* A melody is compared, named and classified by three modules here, and all
|
|
1761
|
+
* three start from the same two questions: which notes sound, in what order,
|
|
1762
|
+
* and what the gaps between their onsets are. Answered separately they would
|
|
1763
|
+
* drift — one reading folding a chord to its top voice and another not — so
|
|
1764
|
+
* they are answered once, here.
|
|
1765
|
+
*/
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* Anything that can be read as a melodic line: a motif, or plain note events.
|
|
1769
|
+
*
|
|
1770
|
+
* @category Arrangement & Analysis
|
|
1771
|
+
*/
|
|
1772
|
+
type MelodicPhrase = MotifData | readonly NoteEvent[];
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* The shape of a line: which way it moves, and what that adds up to.
|
|
1776
|
+
*
|
|
1777
|
+
* A contour is the melody with its pitches forgotten — the sequence of ups and
|
|
1778
|
+
* downs alone — which is what lets a phrase be called an arch whatever key it
|
|
1779
|
+
* is in and whatever intervals it is built from.
|
|
1780
|
+
*/
|
|
1781
|
+
|
|
1782
|
+
/**
|
|
1783
|
+
* Step direction between two consecutive notes.
|
|
1784
|
+
*
|
|
1785
|
+
* @category Arrangement & Analysis
|
|
1786
|
+
*/
|
|
1787
|
+
type ContourDirection = 'up' | 'down' | 'same';
|
|
1788
|
+
/**
|
|
1789
|
+
* The overall shapes a line can be read as.
|
|
1790
|
+
*
|
|
1791
|
+
* @category Arrangement & Analysis
|
|
1792
|
+
*/
|
|
1793
|
+
declare const MELODIC_CONTOUR_SHAPES: readonly ["arch", "ascending", "descending", "wave", "static"];
|
|
1794
|
+
/**
|
|
1795
|
+
* The overall shape of a line.
|
|
1796
|
+
*
|
|
1797
|
+
* The first four names are the vocabulary the motif generator's contours are
|
|
1798
|
+
* written in, and mean the same here. `'static'` is the shape a generator never
|
|
1799
|
+
* asks for and an analysis regularly meets: a line that does not move. A line
|
|
1800
|
+
* that dips and comes back — an arch upside down — reads as `'wave'`, since the
|
|
1801
|
+
* shared vocabulary has no name of its own for it.
|
|
1802
|
+
*
|
|
1803
|
+
* A wave has to turn more than once to be heard as one, so a cell the generator
|
|
1804
|
+
* writes for `'wave'` reads back as `'wave'` only from three bars up; the one-
|
|
1805
|
+
* and two-bar cells turn once and read as `'arch'`. The other three shapes come
|
|
1806
|
+
* back under their own name at every length.
|
|
1807
|
+
*
|
|
1808
|
+
* @category Arrangement & Analysis
|
|
1809
|
+
*/
|
|
1810
|
+
type MelodicContourShape = (typeof MELODIC_CONTOUR_SHAPES)[number];
|
|
1811
|
+
/**
|
|
1812
|
+
* The abstract shape of a line: its step directions and what they add up to.
|
|
1813
|
+
*
|
|
1814
|
+
* @category Arrangement & Analysis
|
|
1815
|
+
*/
|
|
1816
|
+
type MelodicContour = {
|
|
1817
|
+
/** One direction per adjacent pair of notes, in time order. */
|
|
1818
|
+
directions: ContourDirection[];
|
|
1819
|
+
/** What the directions add up to. */
|
|
1820
|
+
shape: MelodicContourShape;
|
|
1821
|
+
/** Index of the highest note; ties keep the earliest, and -1 for a line with no notes. */
|
|
1822
|
+
peakIndex: number;
|
|
1823
|
+
/** Index of the lowest note; ties keep the earliest, and -1 for a line with no notes. */
|
|
1824
|
+
troughIndex: number;
|
|
1825
|
+
/** Semitone distance from the lowest note to the highest. */
|
|
1826
|
+
range: number;
|
|
1827
|
+
/** Why the line was given that shape. */
|
|
1828
|
+
rationale: string;
|
|
1829
|
+
};
|
|
1830
|
+
/**
|
|
1831
|
+
* Read the shape of a line.
|
|
1832
|
+
*
|
|
1833
|
+
* The step directions are the literal answer; the shape is what they add up to.
|
|
1834
|
+
* A line is called ascending or descending when most of its motion runs one way,
|
|
1835
|
+
* not only when every step does — a climb with one dip in it is still heard as a
|
|
1836
|
+
* climb — and an arch when it rises to an interior peak and comes back down.
|
|
1837
|
+
* Anything that keeps changing direction is a wave, and a line that does not move
|
|
1838
|
+
* is static.
|
|
1839
|
+
*
|
|
1840
|
+
* The line is expected to be monophonic, as {@link analyzeVoice} expects a voice
|
|
1841
|
+
* to be; split polyphonic material into lines first. Notes struck together are
|
|
1842
|
+
* read as one event, the highest of them standing for it — a chord and the note
|
|
1843
|
+
* after it trace one step, not a climb through the chord — so a piano part read
|
|
1844
|
+
* whole answers for its top voice rather than for a line no one plays.
|
|
1845
|
+
*
|
|
1846
|
+
* @param notes The line: a motif, or plain note events. Notes that never sound
|
|
1847
|
+
* are dropped, and notes sharing an onset are folded to their top voice.
|
|
1848
|
+
* @returns The directions, the shape, the peak and trough, and a rationale. A
|
|
1849
|
+
* line with no sounding notes is static with a range of 0, and its peak and
|
|
1850
|
+
* trough are -1: there is no note for them to point at.
|
|
1851
|
+
* @example
|
|
1852
|
+
* ```ts
|
|
1853
|
+
* import { melodicContour } from '@libraz/libcantus';
|
|
1854
|
+
* const notes = [
|
|
1855
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1856
|
+
* { pitch: 64, startBeat: 1, durationBeat: 1 },
|
|
1857
|
+
* { pitch: 60, startBeat: 2, durationBeat: 1 },
|
|
1858
|
+
* ];
|
|
1859
|
+
* melodicContour(notes).shape; // 'arch'
|
|
1860
|
+
* ```
|
|
1861
|
+
* @category Arrangement & Analysis
|
|
1862
|
+
*/
|
|
1863
|
+
declare function melodicContour(notes: MelodicPhrase): MelodicContour;
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* The transformation between two statements of a motif, named.
|
|
1867
|
+
*
|
|
1868
|
+
* Two cells stand in a relation when one is the other put through a device the
|
|
1869
|
+
* repertoire has a name for — repetition, transposition, inversion, retrograde,
|
|
1870
|
+
* augmentation — and naming it is what turns a pair of similar phrases into an
|
|
1871
|
+
* account of how the second was made from the first.
|
|
1872
|
+
*/
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* The named transformations {@link relateMotifs} can report between two motif
|
|
1876
|
+
* statements.
|
|
1877
|
+
*
|
|
1878
|
+
* @category Arrangement & Analysis
|
|
1879
|
+
*/
|
|
1880
|
+
declare const MOTIF_RELATION_KINDS: readonly ["repetition", "transposition", "tonalTransposition", "inversion", "retrograde", "retrogradeInversion", "augmentation", "diminution"];
|
|
1881
|
+
/**
|
|
1882
|
+
* How one statement of a motif stands to another.
|
|
1883
|
+
*
|
|
1884
|
+
* `'transposition'` is the literal one — every interval preserved, which is what
|
|
1885
|
+
* makes a sequence built on it a *real* sequence. `'tonalTransposition'` shifts
|
|
1886
|
+
* by scale degrees instead and lets the key resize the intervals, which is what
|
|
1887
|
+
* makes a sequence built on it a *tonal* sequence; the two are different devices
|
|
1888
|
+
* and are never reported as one. `'augmentation'` and `'diminution'` name the
|
|
1889
|
+
* relation only when the pitches are untouched, so an inversion in doubled note
|
|
1890
|
+
* values reads as `'inversion'` with a `timeRatio` of 2 rather than losing the
|
|
1891
|
+
* inversion to the stretch.
|
|
1892
|
+
*
|
|
1893
|
+
* @category Arrangement & Analysis
|
|
1894
|
+
*/
|
|
1895
|
+
type MotifRelationKind = (typeof MOTIF_RELATION_KINDS)[number];
|
|
1896
|
+
/**
|
|
1897
|
+
* The transformation that turns one motif into another.
|
|
1898
|
+
*
|
|
1899
|
+
* @category Arrangement & Analysis
|
|
1900
|
+
*/
|
|
1901
|
+
type MotifRelation = {
|
|
1902
|
+
/** The named transformation. */
|
|
1903
|
+
kind: MotifRelationKind;
|
|
1904
|
+
/**
|
|
1905
|
+
* Whether the second statement begins where the first ends, which is what
|
|
1906
|
+
* separates a sequence from a restatement elsewhere in the piece: a
|
|
1907
|
+
* `'transposition'` that is one is a real sequence, a `'tonalTransposition'`
|
|
1908
|
+
* that is one is a tonal sequence.
|
|
1909
|
+
*/
|
|
1910
|
+
sequence: boolean;
|
|
1911
|
+
/** Semitones from the first statement's first note to the second's. */
|
|
1912
|
+
semitones: number;
|
|
1913
|
+
/** The same distance named as a spelled interval. */
|
|
1914
|
+
interval: SpelledInterval;
|
|
1915
|
+
/** Scale degrees the second statement stands above the first, when tonal. */
|
|
1916
|
+
degrees?: number;
|
|
1917
|
+
/**
|
|
1918
|
+
* How much the second statement is stretched against the first: 2 for doubled
|
|
1919
|
+
* note values, 0.5 for halved, 1 for unchanged.
|
|
1920
|
+
*
|
|
1921
|
+
* Measured in the direction the relation names, so a retrograde that kept
|
|
1922
|
+
* every note value reports 1 however uneven those values are: it is compared
|
|
1923
|
+
* against the model played backwards, not against the model as written.
|
|
1924
|
+
*/
|
|
1925
|
+
timeRatio: number;
|
|
1926
|
+
/** Why the pair was given that name. */
|
|
1927
|
+
rationale: string;
|
|
1928
|
+
};
|
|
1929
|
+
/**
|
|
1930
|
+
* The part of a {@link MotifRelation} that names the transformation itself,
|
|
1931
|
+
* without the rationale prose that explains it.
|
|
1932
|
+
*
|
|
1933
|
+
* @category Arrangement & Analysis
|
|
1934
|
+
*/
|
|
1935
|
+
type MotifRelationSummary = Pick<MotifRelation, 'kind' | 'sequence' | 'semitones' | 'degrees' | 'timeRatio'>;
|
|
1936
|
+
/**
|
|
1937
|
+
* Name how two statements of a motif relate.
|
|
1938
|
+
*
|
|
1939
|
+
* The two are compared as shapes, not as pitches: the interval sequences say
|
|
1940
|
+
* which transformations could name the pair and the onset gaps say which of
|
|
1941
|
+
* them does, so a statement a fourth higher in doubled note values is
|
|
1942
|
+
* recognised for what it is, and a cell that reads as both an inversion and a
|
|
1943
|
+
* retrograde is named by the way its rhythm runs rather than by which name came
|
|
1944
|
+
* to hand first. An evenly timed cell runs the same both ways, so there the
|
|
1945
|
+
* reading that needs no transposition wins: a cell played backwards on its own
|
|
1946
|
+
* pitches is a retrograde. A pair whose rhythms correspond in neither direction — the same
|
|
1947
|
+
* way round for a repetition or a transposition, the model played backwards for
|
|
1948
|
+
* a retrograde, note values and all — is not a transformation of the motif but a
|
|
1949
|
+
* different figure, and answers null; {@link melodicSimilarity} is what scores
|
|
1950
|
+
* those.
|
|
1951
|
+
*
|
|
1952
|
+
* With a `key` in hand the search also asks whether the second statement is the
|
|
1953
|
+
* first moved by scale degrees rather than by semitones. That is the tonal
|
|
1954
|
+
* answer to the real one, and the two are kept apart: a real transposition keeps
|
|
1955
|
+
* every interval literally, a tonal one lets the key resize them, so a subject
|
|
1956
|
+
* answered a step higher reads as `'transposition'` when its major second stays
|
|
1957
|
+
* major and as `'tonalTransposition'` when the scale turns it minor.
|
|
1958
|
+
*
|
|
1959
|
+
* @param a The model statement.
|
|
1960
|
+
* @param b The statement to name against it.
|
|
1961
|
+
* @param keyLike Key context for the tonal reading. Without one that reading is
|
|
1962
|
+
* unavailable, and a statement holding only diatonically is left unnamed
|
|
1963
|
+
* unless its interval pattern also fits a member of the retrograde family, in
|
|
1964
|
+
* which case it is named there: a triad restated a degree higher swaps two
|
|
1965
|
+
* adjacent interval sizes, which is what a retrograde inversion does to it as
|
|
1966
|
+
* well, and equal note values read the same way round in both directions, so
|
|
1967
|
+
* the notes alone cannot separate the two. A key separates them.
|
|
1968
|
+
* @returns The relation, or null when the two stand in none.
|
|
1969
|
+
* @example
|
|
1970
|
+
* ```ts
|
|
1971
|
+
* import { majorKey, motifFromNotes, relateMotifs } from '@libraz/libcantus';
|
|
1972
|
+
* const model = motifFromNotes([
|
|
1973
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1974
|
+
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1975
|
+
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1976
|
+
* ]);
|
|
1977
|
+
* const answer = motifFromNotes([
|
|
1978
|
+
* { pitch: 62, startBeat: 3, durationBeat: 1 },
|
|
1979
|
+
* { pitch: 64, startBeat: 4, durationBeat: 1 },
|
|
1980
|
+
* { pitch: 65, startBeat: 5, durationBeat: 1 },
|
|
1981
|
+
* ]);
|
|
1982
|
+
* relateMotifs(model, answer, majorKey(0))?.kind; // 'tonalTransposition'
|
|
1983
|
+
* ```
|
|
1984
|
+
* @category Arrangement & Analysis
|
|
1985
|
+
*/
|
|
1986
|
+
declare function relateMotifs(a: MotifData, b: MotifData, keyLike?: SpelledKeyLike): MotifRelation | null;
|
|
1987
|
+
|
|
1988
|
+
/**
|
|
1989
|
+
* How the statements of a melody's motifs derive from one another.
|
|
1990
|
+
*
|
|
1991
|
+
* {@link extractMotifs} groups statements into motifs; this reads across that
|
|
1992
|
+
* grouping and asks, for every statement after the first, which earlier
|
|
1993
|
+
* statement it most plausibly grew out of. The answer is a forest rather than a
|
|
1994
|
+
* graph proper — every statement has at most one parent — because a derivation
|
|
1995
|
+
* only ever has one source, however many statements it could be heard against.
|
|
1996
|
+
*/
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* Options controlling {@link motifGraph}.
|
|
2000
|
+
*
|
|
2001
|
+
* @category Arrangement & Analysis
|
|
2002
|
+
*/
|
|
2003
|
+
type MotifGraphOptions = {
|
|
2004
|
+
/** Key context for the tonal reading `relateMotifs` can offer. */
|
|
2005
|
+
key?: SpelledKeyLike;
|
|
2006
|
+
/**
|
|
2007
|
+
* Lower bound on `melodicSimilarity` for a pair with no named transformation
|
|
2008
|
+
* to still be drawn as a variation.
|
|
2009
|
+
*
|
|
2010
|
+
* @defaultValue 0.75
|
|
2011
|
+
*/
|
|
2012
|
+
variationThreshold?: number;
|
|
2013
|
+
/**
|
|
2014
|
+
* Upper bound on the work this call may do.
|
|
2015
|
+
*
|
|
2016
|
+
* @defaultValue {@link DEFAULT_GENERATION_BUDGET}
|
|
2017
|
+
*/
|
|
2018
|
+
budget?: number;
|
|
2019
|
+
};
|
|
2020
|
+
/**
|
|
2021
|
+
* One statement of a motif, as a node of {@link MotifGraph}.
|
|
2022
|
+
*
|
|
2023
|
+
* @category Arrangement & Analysis
|
|
2024
|
+
*/
|
|
2025
|
+
type MotifGraphNode = {
|
|
2026
|
+
/** Index into the `motifs` array this statement belongs to. */
|
|
2027
|
+
motif: number;
|
|
2028
|
+
/** Index into that motif's `occurrences`. */
|
|
2029
|
+
occurrence: number;
|
|
2030
|
+
/** Onset of the statement's first note. */
|
|
2031
|
+
startBeat: number;
|
|
2032
|
+
/** End of the statement's last note, exclusive. */
|
|
2033
|
+
endBeat: number;
|
|
2034
|
+
};
|
|
2035
|
+
/**
|
|
2036
|
+
* The derivation drawn from one statement to a later one.
|
|
2037
|
+
*
|
|
2038
|
+
* @category Arrangement & Analysis
|
|
2039
|
+
*/
|
|
2040
|
+
type MotifGraphEdge = {
|
|
2041
|
+
/** Index of the preceding node this statement derives from. */
|
|
2042
|
+
from: number;
|
|
2043
|
+
/** Index of the derived node. */
|
|
2044
|
+
to: number;
|
|
2045
|
+
/** The named transformation, or null when none explains the pair. */
|
|
2046
|
+
relation: MotifRelationSummary | null;
|
|
2047
|
+
/** `melodicSimilarity` between the two statements. */
|
|
2048
|
+
similarity: number;
|
|
2049
|
+
};
|
|
2050
|
+
/**
|
|
2051
|
+
* How a melody's motif statements derive from one another.
|
|
2052
|
+
*
|
|
2053
|
+
* @category Arrangement & Analysis
|
|
2054
|
+
*/
|
|
2055
|
+
type MotifGraph = {
|
|
2056
|
+
/** Every statement of every motif, ordered by `startBeat` then by `motif`. */
|
|
2057
|
+
nodes: MotifGraphNode[];
|
|
2058
|
+
/**
|
|
2059
|
+
* The derivation drawn to each node from an earlier one, ordered by `to`.
|
|
2060
|
+
* Every node has at most one edge into it, so the edges describe a forest.
|
|
2061
|
+
*/
|
|
2062
|
+
edges: MotifGraphEdge[];
|
|
2063
|
+
};
|
|
2064
|
+
/**
|
|
2065
|
+
* Read a melody's motif statements as a derivation forest.
|
|
2066
|
+
*
|
|
2067
|
+
* Every statement after the first is compared against every statement that
|
|
2068
|
+
* precedes it: the pair {@link relateMotifs} can name outranks any it cannot,
|
|
2069
|
+
* the closest-scoring precedent among equals outranks the rest, and a
|
|
2070
|
+
* statement whose best precedent is neither named nor close enough by
|
|
2071
|
+
* `melodicSimilarity` is a root — new material, not a derivation of what came
|
|
2072
|
+
* before it. The derivation traced this way explains a subject restated,
|
|
2073
|
+
* transposed, inverted, augmented, or loosely varied by what it grew out of,
|
|
2074
|
+
* and leaves a truly new idea unattached rather than forcing it onto whatever
|
|
2075
|
+
* came earliest.
|
|
2076
|
+
*
|
|
2077
|
+
* `motifs` is read against `line` rather than against its own prime
|
|
2078
|
+
* statements: an occurrence's notes can differ from the motif's first
|
|
2079
|
+
* statement in duration even where their intervals and onset gaps agree, and a
|
|
2080
|
+
* retrograde reading depends on that duration. So every occurrence is read
|
|
2081
|
+
* back off `line` and named fresh, occurrence to occurrence.
|
|
2082
|
+
*
|
|
2083
|
+
* @param line The melody `motifs` was extracted from. Notes that never sound
|
|
2084
|
+
* are dropped, exactly as {@link extractMotifs} drops them, so the two agree
|
|
2085
|
+
* on which note an occurrence's `noteIndex` addresses.
|
|
2086
|
+
* @param motifs The motifs to graph, as {@link extractMotifs} reports them.
|
|
2087
|
+
* @param opts Key context, the variation threshold, and a work budget; see
|
|
2088
|
+
* {@link MotifGraphOptions}.
|
|
2089
|
+
* @returns The statements as nodes, and the derivation drawn to each from an
|
|
2090
|
+
* earlier one.
|
|
2091
|
+
* @example
|
|
2092
|
+
* ```ts
|
|
2093
|
+
* import { extractMotifs, motifGraph } from '@libraz/libcantus';
|
|
2094
|
+
* const notes = [
|
|
2095
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
2096
|
+
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
2097
|
+
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
2098
|
+
* { pitch: 67, startBeat: 4, durationBeat: 1 },
|
|
2099
|
+
* { pitch: 69, startBeat: 5, durationBeat: 1 },
|
|
2100
|
+
* { pitch: 71, startBeat: 6, durationBeat: 1 },
|
|
2101
|
+
* ];
|
|
2102
|
+
* const graph = motifGraph(notes, extractMotifs(notes));
|
|
2103
|
+
* graph.edges[0]?.relation?.kind; // 'transposition'
|
|
2104
|
+
* ```
|
|
2105
|
+
* @category Arrangement & Analysis
|
|
2106
|
+
*/
|
|
2107
|
+
declare function motifGraph(line: readonly NoteEvent[], motifs: readonly MotifData[], opts?: MotifGraphOptions): MotifGraph;
|
|
2108
|
+
|
|
2109
|
+
/**
|
|
2110
|
+
* Harmonic reduction: which chords carry the progression and which decorate it.
|
|
2111
|
+
*
|
|
2112
|
+
* `analyzeVoice` can say that a note is a passing tone; a timeline is flat, so
|
|
2113
|
+
* nothing could say the same of a chord. This is that answer one level up — the
|
|
2114
|
+
* middle chord of `Cmaj7 - C#dim7 - Dm7` is an embellishment, and the frame
|
|
2115
|
+
* under `I - IV - #ivo7 - V - I` is `I - IV - V - I`.
|
|
2116
|
+
*
|
|
2117
|
+
* **The position taken here.** What counts as structural is a theoretical
|
|
2118
|
+
* choice, not a fact, and this module makes the functional one: *a chord is
|
|
2119
|
+
* structural unless it is demonstrably an embellishment of its neighbours*. The
|
|
2120
|
+
* frame is what carries harmonic function in the prevailing key — the tonic, the
|
|
2121
|
+
* dominant, and the chords that cadence — so those are never reduced away; a
|
|
2122
|
+
* chord that tonicizes something other than the tonic is prolonging, not
|
|
2123
|
+
* framing, however dominant its sonority. Everything else keeps its place unless
|
|
2124
|
+
* its root traces one of two figures against the chords around it, the same two
|
|
2125
|
+
* figures {@link analyzeVoice} matches at the note level: a step through
|
|
2126
|
+
* (passing) and a step away and back (neighbor). Defaulting to structural, and
|
|
2127
|
+
* demoting only on evidence, is what keeps a reduction from quietly deleting
|
|
2128
|
+
* harmony it merely failed to recognize.
|
|
2129
|
+
*
|
|
2130
|
+
* The salience reading — that the frame is whatever lasts longest, regardless of
|
|
2131
|
+
* function — is the main alternative, and it is a live one for repertoire where
|
|
2132
|
+
* function is weak. {@link ReduceProgressionOptions.basis} switches to it: the
|
|
2133
|
+
* figures stay the same, only what protects a chord from them changes.
|
|
2134
|
+
*
|
|
2135
|
+
* Both readings are deterministic: the same timeline and key always reduce the
|
|
2136
|
+
* same way.
|
|
2137
|
+
*/
|
|
2138
|
+
|
|
2139
|
+
/**
|
|
2140
|
+
* The places a chord can hold in a progression.
|
|
2141
|
+
*
|
|
2142
|
+
* @category Arrangement & Analysis
|
|
2143
|
+
*/
|
|
2144
|
+
declare const REDUCTION_LEVELS: readonly ["structural", "passing", "neighbor"];
|
|
2145
|
+
/**
|
|
2146
|
+
* The place a chord holds in a progression.
|
|
2147
|
+
*
|
|
2148
|
+
* `'structural'` is the frame; `'passing'` and `'neighbor'` are the two
|
|
2149
|
+
* embellishing figures, spelled exactly as {@link TheoryLabel} spells them for
|
|
2150
|
+
* a note, so one legend covers both levels of analysis.
|
|
2151
|
+
*
|
|
2152
|
+
* @category Arrangement & Analysis
|
|
2153
|
+
*/
|
|
2154
|
+
type ReductionLevel = (typeof REDUCTION_LEVELS)[number];
|
|
2155
|
+
/**
|
|
2156
|
+
* A chord with the place it holds, the beats it holds it for, and a short
|
|
2157
|
+
* rationale.
|
|
2158
|
+
*
|
|
2159
|
+
* One entry per timeline segment, in the same order. The entry carries the
|
|
2160
|
+
* segment's own beat range, so the chords that survive a filter still say when
|
|
2161
|
+
* they sound: holding the structural chords fixed is what a reharmonizer needs,
|
|
2162
|
+
* and it cannot hold a chord at a place the filter threw away.
|
|
2163
|
+
*
|
|
2164
|
+
* @category Arrangement & Analysis
|
|
2165
|
+
*/
|
|
2166
|
+
type ReducedChord = {
|
|
2167
|
+
chord: Chord;
|
|
2168
|
+
startBeat: number;
|
|
2169
|
+
endBeat: number;
|
|
2170
|
+
level: ReductionLevel;
|
|
2171
|
+
rationale: string;
|
|
2172
|
+
};
|
|
2173
|
+
/**
|
|
2174
|
+
* What a reduction treats as the frame.
|
|
2175
|
+
*
|
|
2176
|
+
* `'function'` is the functional reading: the tonic, the dominant and the
|
|
2177
|
+
* chords that cadence are the frame. `'duration'` is the salience reading: a
|
|
2178
|
+
* chord is the frame when it outlasts the chords around it — its length beats
|
|
2179
|
+
* the median of the other chords' — whatever function it carries. Nothing
|
|
2180
|
+
* outlasts anything in an even harmonic rhythm, so a loop or a vamp protects no
|
|
2181
|
+
* chord by length and the two figures decide, as they do under either reading.
|
|
2182
|
+
*
|
|
2183
|
+
* @category Arrangement & Analysis
|
|
2184
|
+
*/
|
|
2185
|
+
type ReductionBasis = 'function' | 'duration';
|
|
2186
|
+
/**
|
|
2187
|
+
* Options for {@link reduceProgression}.
|
|
2188
|
+
*
|
|
2189
|
+
* @category Arrangement & Analysis
|
|
2190
|
+
*/
|
|
2191
|
+
type ReduceProgressionOptions = {
|
|
2192
|
+
/**
|
|
2193
|
+
* Which reading decides what the frame is; see {@link ReductionBasis}.
|
|
2194
|
+
*
|
|
2195
|
+
* @defaultValue `'function'`
|
|
2196
|
+
*/
|
|
2197
|
+
basis?: ReductionBasis;
|
|
2198
|
+
};
|
|
2199
|
+
/**
|
|
2200
|
+
* Reduce a chord progression to its frame, labelling every chord.
|
|
2201
|
+
*
|
|
2202
|
+
* One entry comes back per timeline segment, in the same order, so the two read
|
|
2203
|
+
* in step. Filtering the result to `'structural'` is the skeleton view — the
|
|
2204
|
+
* `I - IV - V - I` under a progression that spells far more chords than that —
|
|
2205
|
+
* and holding those chords fixed is what a reharmonizer needs in order to
|
|
2206
|
+
* rewrite the surface without moving the harmony. Each entry carries its own
|
|
2207
|
+
* `startBeat` and `endBeat`, so a filtered chord still knows where it sounds
|
|
2208
|
+
* once the segments it was read against are gone.
|
|
2209
|
+
*
|
|
2210
|
+
* A chord is structural unless one of two figures demotes it: its root stepping
|
|
2211
|
+
* in from the previous chord and on to the next in the same direction (passing),
|
|
2212
|
+
* or stepping away from a harmony that returns (neighbor). Chords carrying the
|
|
2213
|
+
* frame are never demoted, and what carries the frame is the module's stated
|
|
2214
|
+
* theoretical position — see {@link ReductionBasis} for the reading this takes
|
|
2215
|
+
* by default and the one it can be switched to.
|
|
2216
|
+
*
|
|
2217
|
+
* Chords separated by a rest form no figure across it, the way
|
|
2218
|
+
* {@link detectCadences} pairs no chords across one.
|
|
2219
|
+
*
|
|
2220
|
+
* @param timeline The chord timeline to reduce.
|
|
2221
|
+
* @param key The prevailing key, or the key in force at a given beat, for music
|
|
2222
|
+
* that modulates.
|
|
2223
|
+
* @param opts Which reading decides the frame; see
|
|
2224
|
+
* {@link ReduceProgressionOptions}.
|
|
2225
|
+
* @returns One labelled chord per segment, in time order.
|
|
2226
|
+
* @throws If `opts.basis` is not one of the readings {@link ReductionBasis}
|
|
2227
|
+
* names.
|
|
2228
|
+
* @example
|
|
2229
|
+
* ```ts
|
|
2230
|
+
* import { chordTimelineFromChords, majorKey, reduceProgression } from '@libraz/libcantus';
|
|
2231
|
+
* const timeline = chordTimelineFromChords(
|
|
2232
|
+
* [
|
|
2233
|
+
* { rootPc: 0, quality: 'maj7', startBeat: 0 },
|
|
2234
|
+
* { rootPc: 1, quality: 'dim7', startBeat: 4 },
|
|
2235
|
+
* { rootPc: 2, quality: 'min7', startBeat: 8 },
|
|
2236
|
+
* ],
|
|
2237
|
+
* 12,
|
|
2238
|
+
* );
|
|
2239
|
+
* reduceProgression(timeline, majorKey(0)).map((entry) => entry.level);
|
|
2240
|
+
* // ['structural', 'passing', 'structural']
|
|
2241
|
+
* ```
|
|
2242
|
+
* @category Arrangement & Analysis
|
|
2243
|
+
*/
|
|
2244
|
+
declare function reduceProgression(timeline: ChordTimeline, key: KeyContext, opts?: ReduceProgressionOptions): ReducedChord[];
|
|
2245
|
+
|
|
2246
|
+
/**
|
|
2247
|
+
* Where a line places its onsets against the meter: how dense they are, which
|
|
2248
|
+
* metric positions they favour, and how far they push against the beat.
|
|
2249
|
+
*
|
|
2250
|
+
* A phrase and a whole piece are read the same way — a run of onsets over a
|
|
2251
|
+
* span of beats — so a single reading serves both a melodic line and a
|
|
2252
|
+
* harmonic-rhythm reading of chord changes turned into onsets of their own.
|
|
2253
|
+
*/
|
|
2254
|
+
|
|
2255
|
+
/**
|
|
2256
|
+
* Options for {@link analyzeRhythm}.
|
|
2257
|
+
*
|
|
2258
|
+
* @category Arrangement & Analysis
|
|
2259
|
+
*/
|
|
2260
|
+
type RhythmAnalysisOptions = {
|
|
2261
|
+
/**
|
|
2262
|
+
* A single time signature held across the whole span, as sugar for a
|
|
2263
|
+
* one-element `meters`; defaults to 4/4. Giving both is an input error.
|
|
2264
|
+
*
|
|
2265
|
+
* @defaultValue `4/4`
|
|
2266
|
+
*/
|
|
2267
|
+
ts?: MeterLike;
|
|
2268
|
+
/**
|
|
2269
|
+
* The meter as it changes over the span, so a piece that changes metre is
|
|
2270
|
+
* read against its own bar lines throughout.
|
|
2271
|
+
*
|
|
2272
|
+
* @defaultValue 4/4 throughout
|
|
2273
|
+
*/
|
|
2274
|
+
meters?: MeterLike;
|
|
2275
|
+
/**
|
|
2276
|
+
* End of the analysed span in beats; defaults to the end of the last note.
|
|
2277
|
+
*
|
|
2278
|
+
* @defaultValue the end of the last note
|
|
2279
|
+
*/
|
|
2280
|
+
totalBeats?: number;
|
|
2281
|
+
/**
|
|
2282
|
+
* Upper bound on the work this call may do.
|
|
2283
|
+
*
|
|
2284
|
+
* @defaultValue {@link DEFAULT_GENERATION_BUDGET}
|
|
2285
|
+
*/
|
|
2286
|
+
budget?: number;
|
|
2287
|
+
};
|
|
2288
|
+
/**
|
|
2289
|
+
* How a line's onsets sit against one time signature: which slot of its bar
|
|
2290
|
+
* they favour, mass-normalized.
|
|
2291
|
+
*
|
|
2292
|
+
* @category Arrangement & Analysis
|
|
2293
|
+
*/
|
|
2294
|
+
type BarPositionProfile = {
|
|
2295
|
+
/** The time signature this profile's bar is read in. */
|
|
2296
|
+
ts: TimeSignature;
|
|
2297
|
+
/** Slots in one bar of `ts`, at one twelfth of a quarter-note beat each. */
|
|
2298
|
+
slotsPerBar: number;
|
|
2299
|
+
/** Onset mass per slot, in bar order starting at the downbeat; sums to 1. */
|
|
2300
|
+
shares: number[];
|
|
2301
|
+
/** Onsets read under this signature. */
|
|
2302
|
+
onsets: number;
|
|
2303
|
+
};
|
|
2304
|
+
/**
|
|
2305
|
+
* How a line places its onsets against the meter.
|
|
2306
|
+
*
|
|
2307
|
+
* @category Arrangement & Analysis
|
|
2308
|
+
*/
|
|
2309
|
+
type RhythmAnalysis = {
|
|
2310
|
+
/** First beat of the analysed span. */
|
|
2311
|
+
startBeat: number;
|
|
2312
|
+
/** End of the analysed span, exclusive. */
|
|
2313
|
+
endBeat: number;
|
|
2314
|
+
/** The span's length in bars, counted across any meter changes it crosses. */
|
|
2315
|
+
bars: number;
|
|
2316
|
+
/** Onsets read, folding onsets within 0.05 beat of one another into one. */
|
|
2317
|
+
onsets: number;
|
|
2318
|
+
/** Onsets per bar; 0 for a span with no bars. */
|
|
2319
|
+
onsetDensity: number;
|
|
2320
|
+
/** Onsets per bar of the span, one entry per bar including a part bar at either end. */
|
|
2321
|
+
barOnsets: number[];
|
|
2322
|
+
/**
|
|
2323
|
+
* Onset mass by rhythmic level, indexed 0–5: 0 off the first pulse
|
|
2324
|
+
* subdivision, 1 on it, 2–5 on a main pulse (`metricWeight` plus 2). Sums to 1.
|
|
2325
|
+
*/
|
|
2326
|
+
onsetLevels: number[];
|
|
2327
|
+
/** Onset placement within the bar, one profile per time signature the span reads under. */
|
|
2328
|
+
barPositions: BarPositionProfile[];
|
|
2329
|
+
/** Inter-onset-interval mass, binned to {@link RHYTHM_IOI_BINS}; sums to 1. */
|
|
2330
|
+
interOnsetShares: number[];
|
|
2331
|
+
/** Share of the span during which nothing sounds. */
|
|
2332
|
+
restRatio: number;
|
|
2333
|
+
/** Longuet-Higgins & Lee syncopation, in [0, 1]. */
|
|
2334
|
+
syncopation: number;
|
|
2335
|
+
/** Share of onsets further than 1/32 beat from the nearest grid slot. */
|
|
2336
|
+
offGridRatio: number;
|
|
2337
|
+
/** Why the line reads as it does. */
|
|
2338
|
+
rationale: string;
|
|
2339
|
+
};
|
|
2340
|
+
/**
|
|
2341
|
+
* Inter-onset intervals, in quarter-note beats, at half-octave steps from a
|
|
2342
|
+
* sixteenth of a beat to sixteen beats.
|
|
2343
|
+
*
|
|
2344
|
+
* @category Arrangement & Analysis
|
|
2345
|
+
*/
|
|
2346
|
+
declare const RHYTHM_IOI_BINS: readonly number[];
|
|
2347
|
+
/**
|
|
2348
|
+
* How a line places its onsets against the meter: density, metric placement,
|
|
2349
|
+
* interval spacing, rest, and Longuet-Higgins & Lee syncopation.
|
|
2350
|
+
*
|
|
2351
|
+
* Onsets within 0.05 beat of one another read as one, and an
|
|
2352
|
+
* onset further than 1/32 beat from the nearest twelfth-of-a-beat slot is read
|
|
2353
|
+
* as off grid; every onset is moved to its nearest slot for everything
|
|
2354
|
+
* downstream of it, so an unquantized line reads a real but reduced
|
|
2355
|
+
* syncopation rather than a spurious one from a grid it never intended to sit
|
|
2356
|
+
* on.
|
|
2357
|
+
*
|
|
2358
|
+
* @param notes The line's notes, in any order. Notes that never sound
|
|
2359
|
+
* (`durationBeat <= 0`) are dropped.
|
|
2360
|
+
* @param opts Analysis options; see {@link RhythmAnalysisOptions}.
|
|
2361
|
+
* @returns The rhythm reading; see {@link RhythmAnalysis}.
|
|
2362
|
+
* @example
|
|
2363
|
+
* ```ts
|
|
2364
|
+
* import { analyzeRhythm } from '@libraz/libcantus';
|
|
2365
|
+
* const rhythm = analyzeRhythm(
|
|
2366
|
+
* [
|
|
2367
|
+
* { pitch: 60, startBeat: 0, durationBeat: 1.5 },
|
|
2368
|
+
* { pitch: 62, startBeat: 1.5, durationBeat: 2.5 },
|
|
2369
|
+
* ],
|
|
2370
|
+
* { ts: '4/4' },
|
|
2371
|
+
* );
|
|
2372
|
+
* rhythm.onsets; // 2
|
|
2373
|
+
* rhythm.syncopation > 0; // true
|
|
2374
|
+
* ```
|
|
2375
|
+
* @category Arrangement & Analysis
|
|
2376
|
+
*/
|
|
2377
|
+
declare function analyzeRhythm(notes: readonly NoteEvent[], opts?: RhythmAnalysisOptions): RhythmAnalysis;
|
|
2378
|
+
|
|
2379
|
+
/**
|
|
2380
|
+
* A structural fingerprint of a piece: form, phrase shape, harmonic function
|
|
2381
|
+
* and rhythm, and how its motifs derive from one another — everything
|
|
2382
|
+
* `compareReferences` needs to measure two pieces against each other,
|
|
2383
|
+
* dimension by dimension, without carrying the note sequence itself.
|
|
2384
|
+
*
|
|
2385
|
+
* A profile is plain data, JSON-serializable end to end: nothing in it is a
|
|
2386
|
+
* class instance or a function. `profileVersion` gates the shape this file
|
|
2387
|
+
* declares, not the analysis behind it — a schema change bumps it, a reading
|
|
2388
|
+
* that changes only because the analyzer improved does not.
|
|
2389
|
+
*/
|
|
2390
|
+
|
|
2391
|
+
/**
|
|
2392
|
+
* Schema version {@link ReferenceProfile} is written at.
|
|
2393
|
+
*
|
|
2394
|
+
* {@link assertReferenceProfile} rejects any other value outright rather than
|
|
2395
|
+
* guess at an older shape a caller's stored JSON might be in.
|
|
2396
|
+
*
|
|
2397
|
+
* @category Arrangement & Analysis
|
|
2398
|
+
*/
|
|
2399
|
+
declare const REFERENCE_PROFILE_VERSION = 1;
|
|
2400
|
+
/**
|
|
2401
|
+
* A structural fingerprint of a piece, recovered from its notes and held
|
|
2402
|
+
* without the notes themselves.
|
|
2403
|
+
*
|
|
2404
|
+
* @category Arrangement & Analysis
|
|
2405
|
+
*/
|
|
2406
|
+
type ReferenceProfile = {
|
|
2407
|
+
/** Schema version this record is written at; see {@link REFERENCE_PROFILE_VERSION}. */
|
|
2408
|
+
profileVersion: number;
|
|
2409
|
+
/** The analysed span. */
|
|
2410
|
+
span: {
|
|
2411
|
+
startBeat: number;
|
|
2412
|
+
endBeat: number;
|
|
2413
|
+
bars: number;
|
|
2414
|
+
};
|
|
2415
|
+
/** The piece's meter across the analysed span. */
|
|
2416
|
+
meters: MeterMap;
|
|
2417
|
+
/** Sections, phrases and the hypermetric grouping they sit in. */
|
|
2418
|
+
form: ReferenceForm;
|
|
2419
|
+
/** Keys, structural harmony, and harmonic rhythm. */
|
|
2420
|
+
harmony: ReferenceHarmony;
|
|
2421
|
+
/** Register, contour, motifs and their derivations, and melodic rhythm. */
|
|
2422
|
+
melody: ReferenceMelody;
|
|
2423
|
+
};
|
|
2424
|
+
/**
|
|
2425
|
+
* The formal reading of a profiled piece: its hypermetric grouping, the
|
|
2426
|
+
* sections that grouping and repetition reveal, and the phrases within them.
|
|
2427
|
+
*
|
|
2428
|
+
* @category Arrangement & Analysis
|
|
2429
|
+
*/
|
|
2430
|
+
type ReferenceForm = {
|
|
2431
|
+
/** The hyperbar grouping the phrase and section boundaries are read against. */
|
|
2432
|
+
hypermeter: {
|
|
2433
|
+
groupBars: number;
|
|
2434
|
+
confidence: number;
|
|
2435
|
+
};
|
|
2436
|
+
/** Sections, labelled by which earlier section they restate. */
|
|
2437
|
+
sections: FormSection[];
|
|
2438
|
+
/** Phrases, in time order. */
|
|
2439
|
+
phrases: ReferencePhrase[];
|
|
2440
|
+
};
|
|
2441
|
+
/**
|
|
2442
|
+
* One phrase: its span, the section it opens in, how it cadences, and the
|
|
2443
|
+
* melodic and motivic content within it.
|
|
2444
|
+
*
|
|
2445
|
+
* @category Arrangement & Analysis
|
|
2446
|
+
*/
|
|
2447
|
+
type ReferencePhrase = {
|
|
2448
|
+
/** First beat of the phrase. */
|
|
2449
|
+
startBeat: number;
|
|
2450
|
+
/** End of the phrase, exclusive. */
|
|
2451
|
+
endBeat: number;
|
|
2452
|
+
/** The phrase's length in bars. */
|
|
2453
|
+
bars: number;
|
|
2454
|
+
/** Index into `form.sections` of the section this phrase's start beat falls in. */
|
|
2455
|
+
section: number | null;
|
|
2456
|
+
/** Confidence of the phrase boundary, in [0, 1]. */
|
|
2457
|
+
confidence: number;
|
|
2458
|
+
/** The cadence the phrase ends on, or null when it closes on none. */
|
|
2459
|
+
cadence: ReferenceCadence | null;
|
|
2460
|
+
/** The phrase's melodic descriptors, or null when fewer than two notes sound within it. */
|
|
2461
|
+
melody: ReferencePhraseMelody | null;
|
|
2462
|
+
/** Indices into `melody.graph.nodes` of every motif statement that starts within this phrase. */
|
|
2463
|
+
motifNodes: number[];
|
|
2464
|
+
};
|
|
2465
|
+
/**
|
|
2466
|
+
* How a melodic line reads within one phrase.
|
|
2467
|
+
*
|
|
2468
|
+
* @category Arrangement & Analysis
|
|
2469
|
+
*/
|
|
2470
|
+
type ReferencePhraseMelody = {
|
|
2471
|
+
/** The phrase's overall shape. */
|
|
2472
|
+
shape: MelodicContourShape;
|
|
2473
|
+
/** Lowest pitch sounding in the phrase. */
|
|
2474
|
+
low: number;
|
|
2475
|
+
/** Highest pitch sounding in the phrase. */
|
|
2476
|
+
high: number;
|
|
2477
|
+
/** Duration-weighted mean pitch, truncated to the phrase's span. */
|
|
2478
|
+
mean: number;
|
|
2479
|
+
/** Pitch of the phrase's highest note (the first, when several tie). */
|
|
2480
|
+
peakPitch: number;
|
|
2481
|
+
/** Onset of the phrase's peak note, as a fraction of the phrase's length, in [0, 1]. */
|
|
2482
|
+
peakPosition: number;
|
|
2483
|
+
/**
|
|
2484
|
+
* Eight sampled points across the phrase, each the sounding pitch at that
|
|
2485
|
+
* point minus the piece's overall melodic mean, in semitones.
|
|
2486
|
+
*/
|
|
2487
|
+
outline: number[];
|
|
2488
|
+
/** Onsets per bar within the phrase; see {@link RhythmAnalysis.onsetDensity}. */
|
|
2489
|
+
onsetDensity: number;
|
|
2490
|
+
/** Share of the phrase during which nothing sounds. */
|
|
2491
|
+
restRatio: number;
|
|
2492
|
+
};
|
|
2493
|
+
/**
|
|
2494
|
+
* A cadence read at a structural boundary.
|
|
2495
|
+
*
|
|
2496
|
+
* @category Arrangement & Analysis
|
|
2497
|
+
*/
|
|
2498
|
+
type ReferenceCadence = {
|
|
2499
|
+
/** Beat the cadence arrives at. */
|
|
2500
|
+
atBeat: number;
|
|
2501
|
+
/** The cadence type. */
|
|
2502
|
+
type: CadenceType;
|
|
2503
|
+
/** How strongly the cadence closes the boundary it sits at, in [0, 1]. */
|
|
2504
|
+
weight: number;
|
|
2505
|
+
};
|
|
2506
|
+
/**
|
|
2507
|
+
* The harmonic reading of a profiled piece: its key regions, its structural
|
|
2508
|
+
* chords, and how densely those chords change.
|
|
2509
|
+
*
|
|
2510
|
+
* @category Arrangement & Analysis
|
|
2511
|
+
*/
|
|
2512
|
+
type ReferenceHarmony = {
|
|
2513
|
+
/** Key regions across the analysed span. */
|
|
2514
|
+
keys: ReferenceKeyRegion[];
|
|
2515
|
+
/** Chords reduced from the piece's harmony. */
|
|
2516
|
+
chords: ReferenceChord[];
|
|
2517
|
+
/** Onset placement of chord changes, read the way a melodic line's onsets are. */
|
|
2518
|
+
rhythm: RhythmAnalysis;
|
|
2519
|
+
};
|
|
2520
|
+
/**
|
|
2521
|
+
* One span of the piece read in a single key.
|
|
2522
|
+
*
|
|
2523
|
+
* @category Arrangement & Analysis
|
|
2524
|
+
*/
|
|
2525
|
+
type ReferenceKeyRegion = {
|
|
2526
|
+
/** First beat of the region. */
|
|
2527
|
+
startBeat: number;
|
|
2528
|
+
/** End of the region, exclusive. */
|
|
2529
|
+
endBeat: number;
|
|
2530
|
+
/** The key the region is read in. */
|
|
2531
|
+
key: ResolvedKey;
|
|
2532
|
+
/** Confidence of the key reading, in [0, 1]. */
|
|
2533
|
+
confidence: number;
|
|
2534
|
+
};
|
|
2535
|
+
/**
|
|
2536
|
+
* One reduced chord, named against the key region it falls in.
|
|
2537
|
+
*
|
|
2538
|
+
* @category Arrangement & Analysis
|
|
2539
|
+
*/
|
|
2540
|
+
type ReferenceChord = {
|
|
2541
|
+
/** First beat of the chord. */
|
|
2542
|
+
startBeat: number;
|
|
2543
|
+
/** End of the chord, exclusive. */
|
|
2544
|
+
endBeat: number;
|
|
2545
|
+
/** Index into `harmony.keys` of the region this chord is read against. */
|
|
2546
|
+
key: number;
|
|
2547
|
+
/** The chord, as a Roman numeral in the region's key. */
|
|
2548
|
+
roman: string;
|
|
2549
|
+
/** The chord's harmonic function. */
|
|
2550
|
+
function: HarmonicFunction;
|
|
2551
|
+
/** How the chord survived harmonic reduction. */
|
|
2552
|
+
level: ReductionLevel;
|
|
2553
|
+
};
|
|
2554
|
+
/**
|
|
2555
|
+
* The melodic reading of a profiled piece: register and contour, its motifs
|
|
2556
|
+
* and how they derive from one another, and its rhythm.
|
|
2557
|
+
*
|
|
2558
|
+
* @category Arrangement & Analysis
|
|
2559
|
+
*/
|
|
2560
|
+
type ReferenceMelody = {
|
|
2561
|
+
/** The melodic line's pitch range and mean, or null when no note sounds. */
|
|
2562
|
+
register: {
|
|
2563
|
+
low: number;
|
|
2564
|
+
high: number;
|
|
2565
|
+
mean: number;
|
|
2566
|
+
} | null;
|
|
2567
|
+
/** The melodic line's overall shape, or null when fewer than two notes sound. */
|
|
2568
|
+
shape: MelodicContourShape | null;
|
|
2569
|
+
/** Motifs found in the melodic line. */
|
|
2570
|
+
motifs: ReferenceMotif[];
|
|
2571
|
+
/** How the motifs' statements derive from one another. */
|
|
2572
|
+
graph: MotifGraph;
|
|
2573
|
+
/** Onset placement of the melodic line. */
|
|
2574
|
+
rhythm: RhythmAnalysis;
|
|
2575
|
+
};
|
|
2576
|
+
/**
|
|
2577
|
+
* One motif, held as the interval and rhythm ratios it repeats under
|
|
2578
|
+
* transformation rather than as the notes of any one statement.
|
|
2579
|
+
*
|
|
2580
|
+
* @category Arrangement & Analysis
|
|
2581
|
+
*/
|
|
2582
|
+
type ReferenceMotif = {
|
|
2583
|
+
/** Semitones between consecutive notes of the first statement (`MotifData.intervals`). */
|
|
2584
|
+
intervals: number[];
|
|
2585
|
+
/** Onset-to-onset ratios of the first statement (`MotifData.rhythm`), each gap over the first. */
|
|
2586
|
+
rhythm: number[];
|
|
2587
|
+
/**
|
|
2588
|
+
* Onset gap between the first two notes of the first statement, in beats:
|
|
2589
|
+
* the unit `rhythm` is measured in. 0 for a one-note motif.
|
|
2590
|
+
*/
|
|
2591
|
+
unitBeats: number;
|
|
2592
|
+
/** Length of the first statement, in beats. */
|
|
2593
|
+
spanBeats: number;
|
|
2594
|
+
/** How many statements of this motif were found. */
|
|
2595
|
+
occurrences: number;
|
|
2596
|
+
};
|
|
2597
|
+
|
|
2598
|
+
export { CADENCE_TYPES as C, HARMONIC_FUNCTIONS as H, MELODIC_CONTOUR_SHAPES as M, REDUCTION_LEVELS as R, REFERENCE_PROFILE_VERSION as S, RHYTHM_IOI_BINS as T, reduceProgression as aA, relateMotifs as aB, romanToChord as aC, secondaryDominant as aD, secondaryDominantOf as aE, sectionsFromNotes as aF, toVoiceNotes as aG, analyzeChord as ad, analyzeRhythm as ae, analyzeVoice as af, assertChordTimeline as ag, borrowedSource as ah, chordTimelineFromChords as ai, chordTimelineFromNotes as aj, chordToRoman as ak, detectCadence as al, detectCadences as am, detectModulations as an, explainRoman as ao, extractMotifs as ap, functionOf as aq, isBorrowedChord as ar, isDiatonic as as, isMinorKey as at, keyTimelineFromNotes as au, melodicContour as av, motifFromNotes as aw, motifGraph as ax, pivotChords as ay, prevailingKeyOf as az, MOTIF_RELATION_KINDS as t };
|
|
2599
|
+
export type { ReferenceHarmony as $, AnalyzeChordOptions as A, BarPositionProfile as B, DetectCadenceOptions as D, ExplainRomanOptions as E, FormSection as F, MotifGraphNode as G, IdentifiedVoiceNote as I, MotifGraphOptions as J, KeyContext as K, MotifOccurrence as L, MotifRelation as N, MotifRelationKind as O, MotifRelationSummary as P, PivotChord as Q, ReduceProgressionOptions as U, ReducedChord as V, ReductionBasis as W, ReductionLevel as X, ReferenceCadence as Y, ReferenceChord as Z, ReferenceForm as _, AnalyzedNote as a, ReferenceKeyRegion as a0, ReferenceMelody as a1, ReferenceMotif as a2, ReferencePhrase as a3, ReferencePhraseMelody as a4, ReferenceProfile as a5, RejectedCandidate as a6, RhythmAnalysis as a7, RhythmAnalysisOptions as a8, RomanExplanation as a9, SuspensionFigure as aa, TheoryLabel as ab, VoiceNote as ac, BorrowedSource as b, CadenceHit as c, CadenceResult as d, CadenceType as e, ChordAnalysis as f, ChordSegmentation as g, ChordTimeline as h, ChordTimelineOptions as i, ChordTimelineResult as j, ChordToRomanOptions as k, ContourDirection as l, ExtractMotifsOptions as m, FormSectionOptions as n, HarmonicFunction as o, KeyProfileName as p, KeyProfilePair as q, KeyRegion as r, KeyTimelineOptions as s, MelodicContour as u, MelodicContourShape as v, MelodicPhrase as w, MotifData as x, MotifGraph as y, MotifGraphEdge as z };
|