@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,8 +1,8 @@
|
|
|
1
|
-
import { e as KeyScale, h as NoteEvent, i as NoteLike, g as Note
|
|
2
|
-
import { d as ChordQuality, k as KeyVariant, b as Chord, r as SpelledKeyLike, c as ChordLike, K as KeyLike, n as ResolvedKey, N as NoteSafety, o as SafetyProfile } from './coerce-
|
|
3
|
-
import { r as ScaleName } from './masks-
|
|
4
|
-
import {
|
|
5
|
-
import { b as MeterLike } from './index-
|
|
1
|
+
import { e as KeyScale, h as NoteEvent, i as NoteLike, g as Note } from './index-C9MYbuKS.cjs';
|
|
2
|
+
import { d as ChordQuality, k as KeyVariant, b as Chord, r as SpelledKeyLike, c as ChordLike, K as KeyLike, n as ResolvedKey, N as NoteSafety, o as SafetyProfile } from './coerce-Ytt2Eiq9.cjs';
|
|
3
|
+
import { r as ScaleName } from './masks-BnzXnBj0.cjs';
|
|
4
|
+
import { p as KeyProfileName, q as KeyProfilePair, a6 as RejectedCandidate, r as KeyRegion, h as ChordTimeline, c as CadenceHit, a as AnalyzedNote, ab as TheoryLabel, K as KeyContext, d as CadenceResult, a5 as ReferenceProfile } from './types-_ofpb2be.cjs';
|
|
5
|
+
import { b as MeterLike } from './index-5VJgKz_Q.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Church-mode candidates for {@link detectKey}.
|
|
@@ -243,7 +243,8 @@ type DetectKeyOptions = {
|
|
|
243
243
|
* Every input pitch class is tried as a root against every known chord quality.
|
|
244
244
|
* A match is reported when all of the chord's tones are present in the input, or
|
|
245
245
|
* when the only absent tone is the perfect fifth (a common omission in shell
|
|
246
|
-
* voicings)
|
|
246
|
+
* voicings) and at least two of the chord's tones still sound, so a single
|
|
247
|
+
* pitch class matches no chord; matches are ranked best-first by fewest extra notes, then fewest
|
|
247
248
|
* missing notes, then root position over an inversion, then most specific
|
|
248
249
|
* (largest) chord. An exact match (no extras, no missing) is flagged and ranked
|
|
249
250
|
* first.
|
|
@@ -291,11 +292,11 @@ declare function detectChordBest(pitches: readonly number[], opts?: DetectChordO
|
|
|
291
292
|
*
|
|
292
293
|
* Choosing the minor variant is deliberately not part of the ranking. A minor
|
|
293
294
|
* candidate is ranked once, on the minor profile, which already expects some
|
|
294
|
-
* weight on the raised
|
|
295
|
-
* the natural, harmonic and melodic masks
|
|
296
|
-
* `fit` is measured against that mask. That
|
|
297
|
-
* cadence ranks its own tonic first without the
|
|
298
|
-
* a scoring contest against the natural one.
|
|
295
|
+
* weight on the raised seventh and treats the raised sixth as chromatic; only
|
|
296
|
+
* then does it report whichever of the natural, harmonic and melodic masks
|
|
297
|
+
* covers the most input weight, and `fit` is measured against that mask. That
|
|
298
|
+
* split is why a harmonic-minor cadence ranks its own tonic first without the
|
|
299
|
+
* harmonic variant having to win a scoring contest against the natural one.
|
|
299
300
|
*
|
|
300
301
|
* {@link DetectKeyOptions.modes} adds the church modes to the same contest, one
|
|
301
302
|
* candidate per mode per tonic, each ranked on a profile derived from its
|
|
@@ -737,7 +738,8 @@ type ArrangementOptions = {
|
|
|
737
738
|
*
|
|
738
739
|
* The first downbeat is beat 0, so an upbeat is written at negative beats.
|
|
739
740
|
* Declaring its length rejects a note that starts before the pickup does;
|
|
740
|
-
* leave it unset to accept any
|
|
741
|
+
* leave it unset to accept any onset within ±`Number.MAX_SAFE_INTEGER`
|
|
742
|
+
* (2^53 - 1).
|
|
741
743
|
*
|
|
742
744
|
* @defaultValue no declared pickup
|
|
743
745
|
*/
|
|
@@ -1307,8 +1309,8 @@ type StructuralCadence = {
|
|
|
1307
1309
|
*
|
|
1308
1310
|
* Three kinds of evidence are gathered independently and then reconciled. The
|
|
1309
1311
|
* harmony contributes its cadences, each closing at the end of the chord it
|
|
1310
|
-
* arrives on, and at
|
|
1311
|
-
*
|
|
1312
|
+
* arrives on, and at every bar line that chord is held across — a tonic still
|
|
1313
|
+
* sounding into the next phrase closed this one at the bar line. The
|
|
1312
1314
|
* cadence reported for a phrase is always one that arrived inside it. The
|
|
1313
1315
|
* melody contributes the rests it breathes at and the notes it
|
|
1314
1316
|
* holds long enough to read as arrivals. Repetition contributes the beats where
|
|
@@ -1387,56 +1389,24 @@ declare function phrasesFromTimeline(timeline: ChordTimeline, notes: readonly No
|
|
|
1387
1389
|
declare function structuralCadences(phrases: readonly Phrase[]): StructuralCadence[];
|
|
1388
1390
|
|
|
1389
1391
|
/**
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1392
|
-
* A section is found, not named. What the analysis can see is that a span of
|
|
1393
|
-
* music is a restatement of an earlier one, so the spans are labelled by order
|
|
1394
|
-
* of first appearance — A, B, C — and nothing more is claimed. Calling a
|
|
1395
|
-
* repeated span a chorus would be a guess dressed as a finding: repetition
|
|
1396
|
-
* alone cannot tell a chorus from a second verse, and the label a listener
|
|
1397
|
-
* would give depends on lyrics, arrangement and loudness that the notes do not
|
|
1398
|
-
* carry.
|
|
1399
|
-
*/
|
|
1400
|
-
|
|
1401
|
-
/**
|
|
1402
|
-
* A span of music labelled by which earlier span it restates.
|
|
1392
|
+
* Reading a piece into a {@link ReferenceProfile}.
|
|
1403
1393
|
*
|
|
1404
|
-
*
|
|
1394
|
+
* The profile is assembled from analyses the library already runs — chord and
|
|
1395
|
+
* key inference, hypermeter, phrases, sections, reduction, motifs and rhythm —
|
|
1396
|
+
* each run exactly once and all read against one span: every analysis is handed
|
|
1397
|
+
* the same `totalBeats`, so the phrases, sections and both rhythm readings
|
|
1398
|
+
* describe the same stretch of music. The harmonic reading enters as a bundle
|
|
1399
|
+
* of its own, so a caller already holding one (a score's kept chord analysis,
|
|
1400
|
+
* an arrangement's session) builds the profile from it without inferring the
|
|
1401
|
+
* harmony a second time.
|
|
1405
1402
|
*/
|
|
1406
|
-
|
|
1407
|
-
/**
|
|
1408
|
-
* The section's letter, assigned in order of first appearance: the opening
|
|
1409
|
-
* material is A, the first material unlike it is B, and a later restatement
|
|
1410
|
-
* of A is A again. Past Z the letters double (AA, AB), the way spreadsheet
|
|
1411
|
-
* columns do. No functional name — verse, chorus, bridge — is ever inferred.
|
|
1412
|
-
*/
|
|
1413
|
-
label: string;
|
|
1414
|
-
/** First beat of the section; negative when it opens with a pickup. */
|
|
1415
|
-
startBeat: number;
|
|
1416
|
-
/** End of the section, exclusive. */
|
|
1417
|
-
endBeat: number;
|
|
1418
|
-
/** The section's length in bars. */
|
|
1419
|
-
bars: number;
|
|
1420
|
-
/**
|
|
1421
|
-
* Index, in the returned array, of the first section carrying this label; the
|
|
1422
|
-
* section's own index when it is that first appearance.
|
|
1423
|
-
*/
|
|
1424
|
-
firstOccurrence: number;
|
|
1425
|
-
/**
|
|
1426
|
-
* How alike this section and its first occurrence are, in [0, 1]; 1 on the
|
|
1427
|
-
* first occurrence itself. The mean over the units the section was merged
|
|
1428
|
-
* from, each scored against the unit it matched.
|
|
1429
|
-
*/
|
|
1430
|
-
similarity: number;
|
|
1431
|
-
/** How the section came to carry its label. */
|
|
1432
|
-
rationale: string;
|
|
1433
|
-
};
|
|
1403
|
+
|
|
1434
1404
|
/**
|
|
1435
|
-
* Options for {@link
|
|
1405
|
+
* Options for {@link analyzeReference}.
|
|
1436
1406
|
*
|
|
1437
1407
|
* @category Arrangement & Analysis
|
|
1438
1408
|
*/
|
|
1439
|
-
type
|
|
1409
|
+
type ReferenceProfileOptions = {
|
|
1440
1410
|
/**
|
|
1441
1411
|
* A single time signature held across the whole span, as sugar for a
|
|
1442
1412
|
* one-element `meters`; defaults to 4/4. Giving both is an input error.
|
|
@@ -1445,586 +1415,91 @@ type FormSectionOptions = {
|
|
|
1445
1415
|
*/
|
|
1446
1416
|
ts?: MeterLike;
|
|
1447
1417
|
/**
|
|
1448
|
-
* The meter as it changes over the span
|
|
1449
|
-
* at its own bar lines.
|
|
1418
|
+
* The meter as it changes over the span.
|
|
1450
1419
|
*
|
|
1451
1420
|
* @defaultValue 4/4 throughout
|
|
1452
1421
|
*/
|
|
1453
1422
|
meters?: MeterLike;
|
|
1454
1423
|
/**
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
* match, so this is the resolution of the answer rather than the length of a
|
|
1459
|
-
* section: a section is any whole number of units long.
|
|
1460
|
-
*
|
|
1461
|
-
* @defaultValue the inferred hyperbar, in bars
|
|
1462
|
-
*/
|
|
1463
|
-
unitBars?: number;
|
|
1464
|
-
/**
|
|
1465
|
-
* How alike two units must be before the later one is called a restatement of
|
|
1466
|
-
* the earlier, in [0, 1].
|
|
1424
|
+
* The key, when it is already known: read as one key across the whole span,
|
|
1425
|
+
* as {@link chordTimelineFromNotes} reads it. Omit it to have the keys
|
|
1426
|
+
* searched for over time.
|
|
1467
1427
|
*
|
|
1468
|
-
* @defaultValue
|
|
1469
|
-
*/
|
|
1470
|
-
threshold?: number;
|
|
1471
|
-
/**
|
|
1472
|
-
* End of the analysed span in beats; defaults to the end of the last note.
|
|
1473
|
-
*
|
|
1474
|
-
* @defaultValue the end of the last note
|
|
1475
|
-
*/
|
|
1476
|
-
totalBeats?: number;
|
|
1477
|
-
/**
|
|
1478
|
-
* Upper bound on the work this call may do.
|
|
1479
|
-
*
|
|
1480
|
-
* @defaultValue {@link DEFAULT_GENERATION_BUDGET}
|
|
1481
|
-
*/
|
|
1482
|
-
budget?: number;
|
|
1483
|
-
};
|
|
1484
|
-
/**
|
|
1485
|
-
* Recover a piece's sections from what it repeats.
|
|
1486
|
-
*
|
|
1487
|
-
* The span is cut into equal units of `unitBars` bars, each unit is compared
|
|
1488
|
-
* with every unit before it, and a unit alike enough to an earlier one takes
|
|
1489
|
-
* that unit's label. Neighbouring units carrying the same label are then merged,
|
|
1490
|
-
* so an eight-bar section built of two like four-bar units is reported as one
|
|
1491
|
-
* section rather than two.
|
|
1492
|
-
*
|
|
1493
|
-
* Likeness is measured on the melodic line and on the harmonic content
|
|
1494
|
-
* together — the same tune under different harmony is a variation, not a
|
|
1495
|
-
* restatement — and the labels are letters in order of first appearance.
|
|
1496
|
-
* Nothing beyond "this is a restatement of that" is asserted: see
|
|
1497
|
-
* {@link FormSection.label}.
|
|
1498
|
-
*
|
|
1499
|
-
* A pickup belongs to the section it leads into, so material before the first
|
|
1500
|
-
* bar is folded into the opening unit rather than made a section of its own.
|
|
1501
|
-
*
|
|
1502
|
-
* @param notes The piece's notes, in any order. Notes that never sound are
|
|
1503
|
-
* dropped.
|
|
1504
|
-
* @param opts Analysis options; see {@link FormSectionOptions}.
|
|
1505
|
-
* @returns The sections, in time order.
|
|
1506
|
-
* @example
|
|
1507
|
-
* ```ts
|
|
1508
|
-
* import { sectionsFromNotes } from '@libraz/libcantus';
|
|
1509
|
-
* const bar = (pitch: number, at: number) => ({ pitch, startBeat: at, durationBeat: 4 });
|
|
1510
|
-
* const notes = [bar(60, 0), bar(64, 4), bar(67, 8), bar(60, 12)];
|
|
1511
|
-
* sectionsFromNotes(notes, { unitBars: 2 }).map((section) => section.label);
|
|
1512
|
-
* ```
|
|
1513
|
-
* @category Arrangement & Analysis
|
|
1514
|
-
*/
|
|
1515
|
-
declare function sectionsFromNotes(notes: readonly NoteEvent[], opts?: FormSectionOptions): FormSection[];
|
|
1516
|
-
|
|
1517
|
-
/**
|
|
1518
|
-
* The recurring cells a melody is built from.
|
|
1519
|
-
*
|
|
1520
|
-
* A motif is the interval contour plus the rhythmic profile, so every statement
|
|
1521
|
-
* of it carries the same identity however far it has been transposed or however
|
|
1522
|
-
* wide its note values are written. What this module does is find them: every
|
|
1523
|
-
* window of the melody is reduced to that identity, windows that agree are
|
|
1524
|
-
* grouped, and a group stated often enough is a motif.
|
|
1525
|
-
*/
|
|
1526
|
-
|
|
1527
|
-
/**
|
|
1528
|
-
* One statement of a motif, addressed in the melody it was found in.
|
|
1529
|
-
*
|
|
1530
|
-
* The notes themselves are not repeated here: a statement is the motif's notes
|
|
1531
|
-
* moved by `transpose` and stretched by `timeRatio`, and `noteIndex` addresses
|
|
1532
|
-
* the original array for a caller that wants them exactly.
|
|
1533
|
-
*
|
|
1534
|
-
* @category Arrangement & Analysis
|
|
1535
|
-
*/
|
|
1536
|
-
type MotifOccurrence = {
|
|
1537
|
-
/**
|
|
1538
|
-
* Index of the statement's first note among the sounding notes of the melody,
|
|
1539
|
-
* in time order. Notes that never sound are dropped before the search, so this
|
|
1540
|
-
* is not an index into the caller's array unless the caller's array has none.
|
|
1428
|
+
* @defaultValue the keys inferred from the notes over time
|
|
1541
1429
|
*/
|
|
1542
|
-
|
|
1543
|
-
/** Onset of the statement's first note. */
|
|
1544
|
-
startBeat: number;
|
|
1545
|
-
/** End of the statement's last note, exclusive. */
|
|
1546
|
-
endBeat: number;
|
|
1547
|
-
/** Semitones this statement stands above the motif's first statement. */
|
|
1548
|
-
transpose: number;
|
|
1549
|
-
/**
|
|
1550
|
-
* How much this statement is stretched against the motif's first: 2 for a
|
|
1551
|
-
* statement in doubled note values, 0.5 for one in halved values, 1 for one in
|
|
1552
|
-
* the same values.
|
|
1553
|
-
*/
|
|
1554
|
-
timeRatio: number;
|
|
1555
|
-
};
|
|
1556
|
-
/**
|
|
1557
|
-
* A recurring melodic cell.
|
|
1558
|
-
*
|
|
1559
|
-
* Identity is the interval sequence plus the rhythmic profile, so every
|
|
1560
|
-
* statement of the motif carries the same `intervals` and `rhythm` however far
|
|
1561
|
-
* it has been transposed or however wide its note values are written. `notes`
|
|
1562
|
-
* holds one statement — the first — exactly as it sounds.
|
|
1563
|
-
*
|
|
1564
|
-
* @category Arrangement & Analysis
|
|
1565
|
-
*/
|
|
1566
|
-
type MotifData = {
|
|
1567
|
-
/** The first statement, exactly as it sounds in the melody. */
|
|
1568
|
-
notes: NoteEvent[];
|
|
1569
|
-
/**
|
|
1570
|
-
* Semitones between consecutive notes. This is the transposition-invariant
|
|
1571
|
-
* identity of the cell: two statements a fifth apart share it.
|
|
1572
|
-
*/
|
|
1573
|
-
intervals: number[];
|
|
1574
|
-
/**
|
|
1575
|
-
* Onset-to-onset gaps as multiples of the first gap, so an even cell reads
|
|
1576
|
-
* `[1, 1, 1]` whatever the tempo or note values it is written in.
|
|
1577
|
-
*/
|
|
1578
|
-
rhythm: number[];
|
|
1579
|
-
/** Every statement found, in time order; the first is `notes`. */
|
|
1580
|
-
occurrences: MotifOccurrence[];
|
|
1581
|
-
/** Why this counts as a motif. */
|
|
1582
|
-
rationale: string;
|
|
1583
|
-
};
|
|
1584
|
-
/**
|
|
1585
|
-
* Options controlling {@link extractMotifs}.
|
|
1586
|
-
*
|
|
1587
|
-
* @category Arrangement & Analysis
|
|
1588
|
-
*/
|
|
1589
|
-
type ExtractMotifsOptions = {
|
|
1430
|
+
key?: KeyLike;
|
|
1590
1431
|
/**
|
|
1591
|
-
*
|
|
1432
|
+
* The harmony, given rather than inferred — the shape an arrangement's
|
|
1433
|
+
* `timeline` option takes. The key regions are still read from `key` or,
|
|
1434
|
+
* without one, from the notes.
|
|
1592
1435
|
*
|
|
1593
|
-
* @defaultValue
|
|
1436
|
+
* @defaultValue inferred from the notes
|
|
1594
1437
|
*/
|
|
1595
|
-
|
|
1438
|
+
timeline?: ChordTimeline;
|
|
1596
1439
|
/**
|
|
1597
|
-
*
|
|
1440
|
+
* The melodic line. Omit it to read the top line of `notes`, where notes
|
|
1441
|
+
* struck together are one event and the highest of them stands for it. Only
|
|
1442
|
+
* notes struck together are collapsed: a lower note struck while a higher
|
|
1443
|
+
* one still sounds — a broken-chord or Alberti accompaniment under a held
|
|
1444
|
+
* melody note — joins the line, so pass the melody of such a texture.
|
|
1598
1445
|
*
|
|
1599
|
-
* @defaultValue
|
|
1446
|
+
* @defaultValue the top line of the notes
|
|
1600
1447
|
*/
|
|
1601
|
-
|
|
1448
|
+
melody?: readonly NoteEvent[];
|
|
1602
1449
|
/**
|
|
1603
|
-
*
|
|
1450
|
+
* End of the analysed span in beats.
|
|
1604
1451
|
*
|
|
1605
|
-
* @defaultValue
|
|
1452
|
+
* @defaultValue the end of the last note or chord segment
|
|
1606
1453
|
*/
|
|
1607
|
-
|
|
1454
|
+
totalBeats?: number;
|
|
1608
1455
|
/**
|
|
1609
|
-
* Upper bound on the work
|
|
1456
|
+
* Upper bound on the work each analysis may do.
|
|
1610
1457
|
*
|
|
1611
1458
|
* @defaultValue {@link DEFAULT_GENERATION_BUDGET}
|
|
1612
1459
|
*/
|
|
1613
1460
|
budget?: number;
|
|
1614
1461
|
};
|
|
1615
1462
|
/**
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1634
|
-
*
|
|
1635
|
-
* @
|
|
1636
|
-
*
|
|
1637
|
-
* @
|
|
1638
|
-
* by where they first appear. Empty when nothing recurs.
|
|
1639
|
-
* @example
|
|
1640
|
-
* ```ts
|
|
1641
|
-
* import { extractMotifs } from '@libraz/libcantus';
|
|
1642
|
-
* const notes = [
|
|
1643
|
-
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1644
|
-
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1645
|
-
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1646
|
-
* { pitch: 67, startBeat: 4, durationBeat: 1 },
|
|
1647
|
-
* { pitch: 69, startBeat: 5, durationBeat: 1 },
|
|
1648
|
-
* { pitch: 71, startBeat: 6, durationBeat: 1 },
|
|
1649
|
-
* ];
|
|
1650
|
-
* const motifs = extractMotifs(notes);
|
|
1651
|
-
* motifs[0]?.occurrences.map((o) => o.startBeat); // [0, 4]
|
|
1652
|
-
* ```
|
|
1653
|
-
* @category Arrangement & Analysis
|
|
1654
|
-
*/
|
|
1655
|
-
declare function extractMotifs(notes: readonly NoteEvent[], opts?: ExtractMotifsOptions): MotifData[];
|
|
1656
|
-
/**
|
|
1657
|
-
* Read a run of notes as a single-statement motif.
|
|
1658
|
-
*
|
|
1659
|
-
* {@link extractMotifs} answers with motifs it found recurring; this is how a
|
|
1660
|
-
* caller names one it already has — a subject, an answer, a phrase lifted out of
|
|
1661
|
-
* a score — so that it can be handed to {@link relateMotifs}.
|
|
1662
|
-
*
|
|
1663
|
-
* The cell is expected to be monophonic, as {@link analyzeVoice} expects a voice
|
|
1664
|
-
* to be. Notes struck together are read as one event, the highest of them
|
|
1665
|
-
* standing for it, so a chord is reduced to its top voice rather than read as a
|
|
1666
|
-
* line of its own.
|
|
1667
|
-
*
|
|
1668
|
-
* @param notes The cell, in time order. Notes that never sound are dropped, and
|
|
1669
|
-
* notes sharing an onset are folded to their top voice.
|
|
1670
|
-
* @returns The motif, carrying exactly one occurrence.
|
|
1671
|
-
* @example
|
|
1672
|
-
* ```ts
|
|
1673
|
-
* import { motifFromNotes, relateMotifs } from '@libraz/libcantus';
|
|
1674
|
-
* const subject = motifFromNotes([
|
|
1675
|
-
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1676
|
-
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1677
|
-
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1678
|
-
* ]);
|
|
1679
|
-
* const answer = motifFromNotes([
|
|
1680
|
-
* { pitch: 67, startBeat: 3, durationBeat: 1 },
|
|
1681
|
-
* { pitch: 69, startBeat: 4, durationBeat: 1 },
|
|
1682
|
-
* { pitch: 71, startBeat: 5, durationBeat: 1 },
|
|
1683
|
-
* ]);
|
|
1684
|
-
* relateMotifs(subject, answer)?.kind; // 'transposition'
|
|
1685
|
-
* ```
|
|
1686
|
-
* @category Arrangement & Analysis
|
|
1687
|
-
*/
|
|
1688
|
-
declare function motifFromNotes(notes: readonly NoteEvent[]): MotifData;
|
|
1689
|
-
|
|
1690
|
-
/**
|
|
1691
|
-
* What every reading of a melody has to agree on first.
|
|
1692
|
-
*
|
|
1693
|
-
* A melody is compared, named and classified by three modules here, and all
|
|
1694
|
-
* three start from the same two questions: which notes sound, in what order,
|
|
1695
|
-
* and what the gaps between their onsets are. Answered separately they would
|
|
1696
|
-
* drift — one reading folding a chord to its top voice and another not — so
|
|
1697
|
-
* they are answered once, here.
|
|
1698
|
-
*/
|
|
1699
|
-
|
|
1700
|
-
/**
|
|
1701
|
-
* Anything that can be read as a melodic line: a motif, or plain note events.
|
|
1702
|
-
*
|
|
1703
|
-
* @category Arrangement & Analysis
|
|
1704
|
-
*/
|
|
1705
|
-
type MelodicPhrase = MotifData | readonly NoteEvent[];
|
|
1706
|
-
|
|
1707
|
-
/**
|
|
1708
|
-
* The shape of a line: which way it moves, and what that adds up to.
|
|
1709
|
-
*
|
|
1710
|
-
* A contour is the melody with its pitches forgotten — the sequence of ups and
|
|
1711
|
-
* downs alone — which is what lets a phrase be called an arch whatever key it
|
|
1712
|
-
* is in and whatever intervals it is built from.
|
|
1713
|
-
*/
|
|
1714
|
-
|
|
1715
|
-
/**
|
|
1716
|
-
* Step direction between two consecutive notes.
|
|
1717
|
-
*
|
|
1718
|
-
* @category Arrangement & Analysis
|
|
1719
|
-
*/
|
|
1720
|
-
type ContourDirection = 'up' | 'down' | 'same';
|
|
1721
|
-
/**
|
|
1722
|
-
* The overall shape of a line.
|
|
1723
|
-
*
|
|
1724
|
-
* The first four names are the vocabulary the motif generator's contours are
|
|
1725
|
-
* written in, and mean the same here. `'static'` is the shape a generator never
|
|
1726
|
-
* asks for and an analysis regularly meets: a line that does not move. A line
|
|
1727
|
-
* that dips and comes back — an arch upside down — reads as `'wave'`, since the
|
|
1728
|
-
* shared vocabulary has no name of its own for it.
|
|
1729
|
-
*
|
|
1730
|
-
* A wave has to turn more than once to be heard as one, so a cell the generator
|
|
1731
|
-
* writes for `'wave'` reads back as `'wave'` only from three bars up; the one-
|
|
1732
|
-
* and two-bar cells turn once and read as `'arch'`. The other three shapes come
|
|
1733
|
-
* back under their own name at every length.
|
|
1734
|
-
*
|
|
1735
|
-
* @category Arrangement & Analysis
|
|
1736
|
-
*/
|
|
1737
|
-
type MelodicContourShape = 'arch' | 'ascending' | 'descending' | 'wave' | 'static';
|
|
1738
|
-
/**
|
|
1739
|
-
* The abstract shape of a line: its step directions and what they add up to.
|
|
1740
|
-
*
|
|
1741
|
-
* @category Arrangement & Analysis
|
|
1742
|
-
*/
|
|
1743
|
-
type MelodicContour = {
|
|
1744
|
-
/** One direction per adjacent pair of notes, in time order. */
|
|
1745
|
-
directions: ContourDirection[];
|
|
1746
|
-
/** What the directions add up to. */
|
|
1747
|
-
shape: MelodicContourShape;
|
|
1748
|
-
/** Index of the highest note; ties keep the earliest, and -1 for a line with no notes. */
|
|
1749
|
-
peakIndex: number;
|
|
1750
|
-
/** Index of the lowest note; ties keep the earliest, and -1 for a line with no notes. */
|
|
1751
|
-
troughIndex: number;
|
|
1752
|
-
/** Semitone distance from the lowest note to the highest. */
|
|
1753
|
-
range: number;
|
|
1754
|
-
/** Why the line was given that shape. */
|
|
1755
|
-
rationale: string;
|
|
1756
|
-
};
|
|
1757
|
-
/**
|
|
1758
|
-
* Read the shape of a line.
|
|
1759
|
-
*
|
|
1760
|
-
* The step directions are the literal answer; the shape is what they add up to.
|
|
1761
|
-
* A line is called ascending or descending when most of its motion runs one way,
|
|
1762
|
-
* not only when every step does — a climb with one dip in it is still heard as a
|
|
1763
|
-
* climb — and an arch when it rises to an interior peak and comes back down.
|
|
1764
|
-
* Anything that keeps changing direction is a wave, and a line that does not move
|
|
1765
|
-
* is static.
|
|
1766
|
-
*
|
|
1767
|
-
* The line is expected to be monophonic, as {@link analyzeVoice} expects a voice
|
|
1768
|
-
* to be; split polyphonic material into lines first. Notes struck together are
|
|
1769
|
-
* read as one event, the highest of them standing for it — a chord and the note
|
|
1770
|
-
* after it trace one step, not a climb through the chord — so a piano part read
|
|
1771
|
-
* whole answers for its top voice rather than for a line no one plays.
|
|
1772
|
-
*
|
|
1773
|
-
* @param notes The line: a motif, or plain note events. Notes that never sound
|
|
1774
|
-
* are dropped, and notes sharing an onset are folded to their top voice.
|
|
1775
|
-
* @returns The directions, the shape, the peak and trough, and a rationale. A
|
|
1776
|
-
* line with no sounding notes is static with a range of 0, and its peak and
|
|
1777
|
-
* trough are -1: there is no note for them to point at.
|
|
1463
|
+
* Read a piece's structure into a {@link ReferenceProfile}: its form, phrase
|
|
1464
|
+
* shapes, harmonic function and rhythm, motif derivations and melodic rhythm,
|
|
1465
|
+
* without the notes themselves.
|
|
1466
|
+
*
|
|
1467
|
+
* The melody is the `melody` option when given, and otherwise the top line of
|
|
1468
|
+
* the notes — notes struck together read as one event, the highest standing
|
|
1469
|
+
* for it. A lower note struck while a higher one still sounds stays in that
|
|
1470
|
+
* line, so a part whose accompaniment moves under a held melody note is read
|
|
1471
|
+
* as its melody only when the melody is passed. The harmony
|
|
1472
|
+
* is the `timeline` option when given, and otherwise inferred from all the
|
|
1473
|
+
* notes. The motif search expects quantized onsets.
|
|
1474
|
+
*
|
|
1475
|
+
* The sections are cut on the hyperbar grouping the phrase reading settles
|
|
1476
|
+
* on, which weighs where the harmony cadences; `sectionsFromNotes` alone
|
|
1477
|
+
* groups without the cadences, so the two can differ.
|
|
1478
|
+
*
|
|
1479
|
+
* @param notes The piece's notes, in any order. Notes that never sound are dropped.
|
|
1480
|
+
* @param opts The meter, key, harmony, melody, span end and budget; see
|
|
1481
|
+
* {@link ReferenceProfileOptions}.
|
|
1482
|
+
* @returns The profile.
|
|
1483
|
+
* @throws {InvalidInputError} If the notes or an option cannot be read.
|
|
1484
|
+
* @throws {BudgetExceededError} If an analysis would exceed the budget.
|
|
1778
1485
|
* @example
|
|
1779
1486
|
* ```ts
|
|
1780
|
-
* import {
|
|
1487
|
+
* import { analyzeReference } from '@libraz/libcantus';
|
|
1488
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
1489
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
1781
1490
|
* const notes = [
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1491
|
+
* ...triad([60, 64, 67], 0),
|
|
1492
|
+
* ...triad([65, 69, 72], 4),
|
|
1493
|
+
* ...triad([67, 71, 74], 8),
|
|
1494
|
+
* ...triad([60, 64, 67], 12),
|
|
1785
1495
|
* ];
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1789
|
-
*/
|
|
1790
|
-
declare function melodicContour(notes: MelodicPhrase): MelodicContour;
|
|
1791
|
-
|
|
1792
|
-
/**
|
|
1793
|
-
* The transformation between two statements of a motif, named.
|
|
1794
|
-
*
|
|
1795
|
-
* Two cells stand in a relation when one is the other put through a device the
|
|
1796
|
-
* repertoire has a name for — repetition, transposition, inversion, retrograde,
|
|
1797
|
-
* augmentation — and naming it is what turns a pair of similar phrases into an
|
|
1798
|
-
* account of how the second was made from the first.
|
|
1799
|
-
*/
|
|
1800
|
-
|
|
1801
|
-
/**
|
|
1802
|
-
* How one statement of a motif stands to another.
|
|
1803
|
-
*
|
|
1804
|
-
* `'transposition'` is the literal one — every interval preserved, which is what
|
|
1805
|
-
* makes a sequence built on it a *real* sequence. `'tonalTransposition'` shifts
|
|
1806
|
-
* by scale degrees instead and lets the key resize the intervals, which is what
|
|
1807
|
-
* makes a sequence built on it a *tonal* sequence; the two are different devices
|
|
1808
|
-
* and are never reported as one. `'augmentation'` and `'diminution'` name the
|
|
1809
|
-
* relation only when the pitches are untouched, so an inversion in doubled note
|
|
1810
|
-
* values reads as `'inversion'` with a `timeRatio` of 2 rather than losing the
|
|
1811
|
-
* inversion to the stretch.
|
|
1812
|
-
*
|
|
1813
|
-
* @category Arrangement & Analysis
|
|
1814
|
-
*/
|
|
1815
|
-
type MotifRelationKind = 'repetition' | 'transposition' | 'tonalTransposition' | 'inversion' | 'retrograde' | 'retrogradeInversion' | 'augmentation' | 'diminution';
|
|
1816
|
-
/**
|
|
1817
|
-
* The transformation that turns one motif into another.
|
|
1818
|
-
*
|
|
1819
|
-
* @category Arrangement & Analysis
|
|
1820
|
-
*/
|
|
1821
|
-
type MotifRelation = {
|
|
1822
|
-
/** The named transformation. */
|
|
1823
|
-
kind: MotifRelationKind;
|
|
1824
|
-
/**
|
|
1825
|
-
* Whether the second statement begins where the first ends, which is what
|
|
1826
|
-
* separates a sequence from a restatement elsewhere in the piece: a
|
|
1827
|
-
* `'transposition'` that is one is a real sequence, a `'tonalTransposition'`
|
|
1828
|
-
* that is one is a tonal sequence.
|
|
1829
|
-
*/
|
|
1830
|
-
sequence: boolean;
|
|
1831
|
-
/** Semitones from the first statement's first note to the second's. */
|
|
1832
|
-
semitones: number;
|
|
1833
|
-
/** The same distance named as a spelled interval. */
|
|
1834
|
-
interval: SpelledInterval;
|
|
1835
|
-
/** Scale degrees the second statement stands above the first, when tonal. */
|
|
1836
|
-
degrees?: number;
|
|
1837
|
-
/**
|
|
1838
|
-
* How much the second statement is stretched against the first: 2 for doubled
|
|
1839
|
-
* note values, 0.5 for halved, 1 for unchanged.
|
|
1840
|
-
*
|
|
1841
|
-
* Measured in the direction the relation names, so a retrograde that kept
|
|
1842
|
-
* every note value reports 1 however uneven those values are: it is compared
|
|
1843
|
-
* against the model played backwards, not against the model as written.
|
|
1844
|
-
*/
|
|
1845
|
-
timeRatio: number;
|
|
1846
|
-
/** Why the pair was given that name. */
|
|
1847
|
-
rationale: string;
|
|
1848
|
-
};
|
|
1849
|
-
/**
|
|
1850
|
-
* Name how two statements of a motif relate.
|
|
1851
|
-
*
|
|
1852
|
-
* The two are compared as shapes, not as pitches: the interval sequences say
|
|
1853
|
-
* which transformations could name the pair and the onset gaps say which of
|
|
1854
|
-
* them does, so a statement a fourth higher in doubled note values is
|
|
1855
|
-
* recognised for what it is, and a cell that reads as both an inversion and a
|
|
1856
|
-
* retrograde is named by the way its rhythm runs rather than by which name came
|
|
1857
|
-
* to hand first. A pair whose rhythms correspond in neither direction — the same
|
|
1858
|
-
* way round for a repetition or a transposition, the model played backwards for
|
|
1859
|
-
* a retrograde, note values and all — is not a transformation of the motif but a
|
|
1860
|
-
* different figure, and answers null; {@link melodicSimilarity} is what scores
|
|
1861
|
-
* those.
|
|
1862
|
-
*
|
|
1863
|
-
* With a `key` in hand the search also asks whether the second statement is the
|
|
1864
|
-
* first moved by scale degrees rather than by semitones. That is the tonal
|
|
1865
|
-
* answer to the real one, and the two are kept apart: a real transposition keeps
|
|
1866
|
-
* every interval literally, a tonal one lets the key resize them, so a subject
|
|
1867
|
-
* answered a step higher reads as `'transposition'` when its major second stays
|
|
1868
|
-
* major and as `'tonalTransposition'` when the scale turns it minor.
|
|
1869
|
-
*
|
|
1870
|
-
* @param a The model statement.
|
|
1871
|
-
* @param b The statement to name against it.
|
|
1872
|
-
* @param keyLike Key context for the tonal reading. Without one that reading is
|
|
1873
|
-
* unavailable, and a statement holding only diatonically is left unnamed
|
|
1874
|
-
* unless its interval pattern also fits a member of the retrograde family, in
|
|
1875
|
-
* which case it is named there: a triad restated a degree higher swaps two
|
|
1876
|
-
* adjacent interval sizes, which is what a retrograde inversion does to it as
|
|
1877
|
-
* well, and equal note values read the same way round in both directions, so
|
|
1878
|
-
* the notes alone cannot separate the two. A key separates them.
|
|
1879
|
-
* @returns The relation, or null when the two stand in none.
|
|
1880
|
-
* @example
|
|
1881
|
-
* ```ts
|
|
1882
|
-
* import { majorKey, motifFromNotes, relateMotifs } from '@libraz/libcantus';
|
|
1883
|
-
* const model = motifFromNotes([
|
|
1884
|
-
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1885
|
-
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1886
|
-
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1887
|
-
* ]);
|
|
1888
|
-
* const answer = motifFromNotes([
|
|
1889
|
-
* { pitch: 62, startBeat: 3, durationBeat: 1 },
|
|
1890
|
-
* { pitch: 64, startBeat: 4, durationBeat: 1 },
|
|
1891
|
-
* { pitch: 65, startBeat: 5, durationBeat: 1 },
|
|
1892
|
-
* ]);
|
|
1893
|
-
* relateMotifs(model, answer, majorKey(0))?.kind; // 'tonalTransposition'
|
|
1894
|
-
* ```
|
|
1895
|
-
* @category Arrangement & Analysis
|
|
1896
|
-
*/
|
|
1897
|
-
declare function relateMotifs(a: MotifData, b: MotifData, keyLike?: SpelledKeyLike): MotifRelation | null;
|
|
1898
|
-
|
|
1899
|
-
/**
|
|
1900
|
-
* Harmonic reduction: which chords carry the progression and which decorate it.
|
|
1901
|
-
*
|
|
1902
|
-
* `analyzeVoice` can say that a note is a passing tone; a timeline is flat, so
|
|
1903
|
-
* nothing could say the same of a chord. This is that answer one level up — the
|
|
1904
|
-
* middle chord of `Cmaj7 - C#dim7 - Dm7` is an embellishment, and the frame
|
|
1905
|
-
* under `I - IV - #ivo7 - V - I` is `I - IV - V - I`.
|
|
1906
|
-
*
|
|
1907
|
-
* **The position taken here.** What counts as structural is a theoretical
|
|
1908
|
-
* choice, not a fact, and this module makes the functional one: *a chord is
|
|
1909
|
-
* structural unless it is demonstrably an embellishment of its neighbours*. The
|
|
1910
|
-
* frame is what carries harmonic function in the prevailing key — the tonic, the
|
|
1911
|
-
* dominant, and the chords that cadence — so those are never reduced away; a
|
|
1912
|
-
* chord that tonicizes something other than the tonic is prolonging, not
|
|
1913
|
-
* framing, however dominant its sonority. Everything else keeps its place unless
|
|
1914
|
-
* its root traces one of two figures against the chords around it, the same two
|
|
1915
|
-
* figures {@link analyzeVoice} matches at the note level: a step through
|
|
1916
|
-
* (passing) and a step away and back (neighbor). Defaulting to structural, and
|
|
1917
|
-
* demoting only on evidence, is what keeps a reduction from quietly deleting
|
|
1918
|
-
* harmony it merely failed to recognize.
|
|
1919
|
-
*
|
|
1920
|
-
* The salience reading — that the frame is whatever lasts longest, regardless of
|
|
1921
|
-
* function — is the main alternative, and it is a live one for repertoire where
|
|
1922
|
-
* function is weak. {@link ReduceProgressionOptions.basis} switches to it: the
|
|
1923
|
-
* figures stay the same, only what protects a chord from them changes.
|
|
1924
|
-
*
|
|
1925
|
-
* Both readings are deterministic: the same timeline and key always reduce the
|
|
1926
|
-
* same way.
|
|
1927
|
-
*/
|
|
1928
|
-
|
|
1929
|
-
/**
|
|
1930
|
-
* The place a chord holds in a progression.
|
|
1931
|
-
*
|
|
1932
|
-
* `'structural'` is the frame; `'passing'` and `'neighbor'` are the two
|
|
1933
|
-
* embellishing figures, spelled exactly as {@link TheoryLabel} spells them for
|
|
1934
|
-
* a note, so one legend covers both levels of analysis.
|
|
1935
|
-
*
|
|
1936
|
-
* @category Arrangement & Analysis
|
|
1937
|
-
*/
|
|
1938
|
-
type ReductionLevel = 'structural' | 'passing' | 'neighbor';
|
|
1939
|
-
/**
|
|
1940
|
-
* A chord with the place it holds, the beats it holds it for, and a short
|
|
1941
|
-
* rationale.
|
|
1942
|
-
*
|
|
1943
|
-
* One entry per timeline segment, in the same order. The entry carries the
|
|
1944
|
-
* segment's own beat range, so the chords that survive a filter still say when
|
|
1945
|
-
* they sound: holding the structural chords fixed is what a reharmonizer needs,
|
|
1946
|
-
* and it cannot hold a chord at a place the filter threw away.
|
|
1947
|
-
*
|
|
1948
|
-
* @category Arrangement & Analysis
|
|
1949
|
-
*/
|
|
1950
|
-
type ReducedChord = {
|
|
1951
|
-
chord: Chord;
|
|
1952
|
-
startBeat: number;
|
|
1953
|
-
endBeat: number;
|
|
1954
|
-
level: ReductionLevel;
|
|
1955
|
-
rationale: string;
|
|
1956
|
-
};
|
|
1957
|
-
/**
|
|
1958
|
-
* What a reduction treats as the frame.
|
|
1959
|
-
*
|
|
1960
|
-
* `'function'` is the functional reading: the tonic, the dominant and the
|
|
1961
|
-
* chords that cadence are the frame. `'duration'` is the salience reading: a
|
|
1962
|
-
* chord is the frame when it outlasts the chords around it — its length beats
|
|
1963
|
-
* the median of the other chords' — whatever function it carries. Nothing
|
|
1964
|
-
* outlasts anything in an even harmonic rhythm, so a loop or a vamp protects no
|
|
1965
|
-
* chord by length and the two figures decide, as they do under either reading.
|
|
1966
|
-
*
|
|
1967
|
-
* @category Arrangement & Analysis
|
|
1968
|
-
*/
|
|
1969
|
-
type ReductionBasis = 'function' | 'duration';
|
|
1970
|
-
/**
|
|
1971
|
-
* Options for {@link reduceProgression}.
|
|
1972
|
-
*
|
|
1973
|
-
* @category Arrangement & Analysis
|
|
1974
|
-
*/
|
|
1975
|
-
type ReduceProgressionOptions = {
|
|
1976
|
-
/**
|
|
1977
|
-
* Which reading decides what the frame is; see {@link ReductionBasis}.
|
|
1978
|
-
*
|
|
1979
|
-
* @defaultValue `'function'`
|
|
1980
|
-
*/
|
|
1981
|
-
basis?: ReductionBasis;
|
|
1982
|
-
};
|
|
1983
|
-
/**
|
|
1984
|
-
* Reduce a chord progression to its frame, labelling every chord.
|
|
1985
|
-
*
|
|
1986
|
-
* One entry comes back per timeline segment, in the same order, so the two read
|
|
1987
|
-
* in step. Filtering the result to `'structural'` is the skeleton view — the
|
|
1988
|
-
* `I - IV - V - I` under a progression that spells far more chords than that —
|
|
1989
|
-
* and holding those chords fixed is what a reharmonizer needs in order to
|
|
1990
|
-
* rewrite the surface without moving the harmony. Each entry carries its own
|
|
1991
|
-
* `startBeat` and `endBeat`, so a filtered chord still knows where it sounds
|
|
1992
|
-
* once the segments it was read against are gone.
|
|
1993
|
-
*
|
|
1994
|
-
* A chord is structural unless one of two figures demotes it: its root stepping
|
|
1995
|
-
* in from the previous chord and on to the next in the same direction (passing),
|
|
1996
|
-
* or stepping away from a harmony that returns (neighbor). Chords carrying the
|
|
1997
|
-
* frame are never demoted, and what carries the frame is the module's stated
|
|
1998
|
-
* theoretical position — see {@link ReductionBasis} for the reading this takes
|
|
1999
|
-
* by default and the one it can be switched to.
|
|
2000
|
-
*
|
|
2001
|
-
* Chords separated by a rest form no figure across it, the way
|
|
2002
|
-
* {@link detectCadences} pairs no chords across one.
|
|
2003
|
-
*
|
|
2004
|
-
* @param timeline The chord timeline to reduce.
|
|
2005
|
-
* @param key The prevailing key, or the key in force at a given beat, for music
|
|
2006
|
-
* that modulates.
|
|
2007
|
-
* @param opts Which reading decides the frame; see
|
|
2008
|
-
* {@link ReduceProgressionOptions}.
|
|
2009
|
-
* @returns One labelled chord per segment, in time order.
|
|
2010
|
-
* @throws If `opts.basis` is not one of the readings {@link ReductionBasis}
|
|
2011
|
-
* names.
|
|
2012
|
-
* @example
|
|
2013
|
-
* ```ts
|
|
2014
|
-
* import { chordTimelineFromChords, majorKey, reduceProgression } from '@libraz/libcantus';
|
|
2015
|
-
* const timeline = chordTimelineFromChords(
|
|
2016
|
-
* [
|
|
2017
|
-
* { rootPc: 0, quality: 'maj7', startBeat: 0 },
|
|
2018
|
-
* { rootPc: 1, quality: 'dim7', startBeat: 4 },
|
|
2019
|
-
* { rootPc: 2, quality: 'min7', startBeat: 8 },
|
|
2020
|
-
* ],
|
|
2021
|
-
* 12,
|
|
2022
|
-
* );
|
|
2023
|
-
* reduceProgression(timeline, majorKey(0)).map((entry) => entry.level);
|
|
2024
|
-
* // ['structural', 'passing', 'structural']
|
|
1496
|
+
* const profile = analyzeReference(notes, { key: 'C major' });
|
|
1497
|
+
* profile.harmony.chords.map((chord) => chord.roman); // ['I', 'IV', 'V', 'I']
|
|
1498
|
+
* profile.span.bars; // 4
|
|
2025
1499
|
* ```
|
|
2026
1500
|
* @category Arrangement & Analysis
|
|
2027
1501
|
*/
|
|
2028
|
-
declare function
|
|
1502
|
+
declare function analyzeReference(notes: readonly NoteEvent[], opts?: ReferenceProfileOptions): ReferenceProfile;
|
|
2029
1503
|
|
|
2030
|
-
export {
|
|
1504
|
+
export { detectKeyFromNotes as B, hypermeter as E, phrasesFromTimeline as F, spellAugmentedSixth as G, structuralCadences as I, tensionCurve as J, tensionCurveFrom as L, MODAL_SCALE_NAMES as M, analyzeArrangement as p, analyzePolyphony as q, analyzeReference as r, augmentedSixthChord as s, augmentedSixthFromPitchClasses as t, augmentedSixthKind as u, createArrangementSession as v, detectChord as w, detectChordBest as x, detectKey as y, detectKeyBest as z };
|
|
1505
|
+
export type { ArrangementAnalysis as A, ChordMatch as C, DetectChordOptions as D, Hypermeter as H, KeyMatch as K, Phrase as P, ReferenceProfileOptions as R, StructuralCadence as S, TensionPoint as T, ArrangementOptions as a, ArrangementSession as b, ArrangementTrack as c, AugmentedSixthKind as d, Conflict as e, ConflictPartner as f, DetectKeyOptions as g, HypermeterOptions as h, ModalCandidate as i, ModalScaleName as j, PhraseOptions as k, PhraseSignal as l, TrackAnalysis as m, TrackEdit as n, TrackRole as o };
|