@libraz/libcantus 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +4 -0
  2. package/README_ja.md +4 -0
  3. package/dist/analyze/index.cjs +31 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +155 -9
  6. package/dist/analyze/index.d.ts +155 -9
  7. package/dist/analyze/index.js +37 -13
  8. package/dist/{chunk-TT6PKMPR.cjs → chunk-35NBV2FT.cjs} +16 -16
  9. package/dist/{chunk-TT6PKMPR.cjs.map → chunk-35NBV2FT.cjs.map} +1 -1
  10. package/dist/{chunk-YPB3A3EY.cjs → chunk-3R3QVKY5.cjs} +314 -285
  11. package/dist/chunk-3R3QVKY5.cjs.map +1 -0
  12. package/dist/{chunk-GDKE6RQW.cjs → chunk-4R2LWV7E.cjs} +691 -413
  13. package/dist/chunk-4R2LWV7E.cjs.map +1 -0
  14. package/dist/{chunk-RVS4G4MR.cjs → chunk-76ZBTRG2.cjs} +198 -198
  15. package/dist/{chunk-RVS4G4MR.cjs.map → chunk-76ZBTRG2.cjs.map} +1 -1
  16. package/dist/{chunk-X6RJAHII.js → chunk-7KG3RTYS.js} +2 -2
  17. package/dist/{chunk-PQL7E5F6.js → chunk-AGOVW7U6.js} +1 -1
  18. package/dist/{chunk-PQL7E5F6.js.map → chunk-AGOVW7U6.js.map} +1 -1
  19. package/dist/{chunk-5KW7DNCJ.cjs → chunk-B2PW6EHN.cjs} +2423 -453
  20. package/dist/chunk-B2PW6EHN.cjs.map +1 -0
  21. package/dist/{chunk-J772DL3B.cjs → chunk-CGBADIO5.cjs} +53 -53
  22. package/dist/{chunk-J772DL3B.cjs.map → chunk-CGBADIO5.cjs.map} +1 -1
  23. package/dist/{chunk-R5VUXQMP.js → chunk-CPMB7P75.js} +1978 -8
  24. package/dist/chunk-CPMB7P75.js.map +1 -0
  25. package/dist/{chunk-VLE5R7PF.js → chunk-GNVN4PPJ.js} +1874 -8
  26. package/dist/chunk-GNVN4PPJ.js.map +1 -0
  27. package/dist/{chunk-Q6R3QAZD.cjs → chunk-IFZDWIKM.cjs} +2199 -333
  28. package/dist/chunk-IFZDWIKM.cjs.map +1 -0
  29. package/dist/{chunk-DKICR7AN.js → chunk-IPEF6NWB.js} +296 -18
  30. package/dist/chunk-IPEF6NWB.js.map +1 -0
  31. package/dist/{chunk-AWEMD747.js → chunk-M5RYMAFI.js} +3 -3
  32. package/dist/{chunk-HSY2VQXZ.js → chunk-MLBPXA4Z.js} +330 -728
  33. package/dist/chunk-MLBPXA4Z.js.map +1 -0
  34. package/dist/{chunk-H66ARFU5.js → chunk-NB25P3EH.js} +3 -3
  35. package/dist/{chunk-5JJ6EHEZ.cjs → chunk-NM7UAMC3.cjs} +62 -62
  36. package/dist/{chunk-5JJ6EHEZ.cjs.map → chunk-NM7UAMC3.cjs.map} +1 -1
  37. package/dist/{chunk-R7B5N2JK.cjs → chunk-PM4XJE2Y.cjs} +47 -47
  38. package/dist/{chunk-R7B5N2JK.cjs.map → chunk-PM4XJE2Y.cjs.map} +1 -1
  39. package/dist/{chunk-7GOKQJTQ.js → chunk-PUKJP2Z5.js} +2 -2
  40. package/dist/{chunk-QCXYKSZS.cjs → chunk-PWC4CRWT.cjs} +19 -19
  41. package/dist/{chunk-QCXYKSZS.cjs.map → chunk-PWC4CRWT.cjs.map} +1 -1
  42. package/dist/{chunk-FPBZFNCD.js → chunk-RFIBF2RR.js} +47 -18
  43. package/dist/{chunk-FPBZFNCD.js.map → chunk-RFIBF2RR.js.map} +1 -1
  44. package/dist/{chunk-XKSO2V5I.cjs → chunk-S5BKZZGM.cjs} +1 -1
  45. package/dist/{chunk-XKSO2V5I.cjs.map → chunk-S5BKZZGM.cjs.map} +1 -1
  46. package/dist/{chunk-HOHFYGKW.js → chunk-UMWZ4ZET.js} +2 -2
  47. package/dist/{chunk-XVMJO3QF.cjs → chunk-YFA7X2F6.cjs} +489 -887
  48. package/dist/chunk-YFA7X2F6.cjs.map +1 -0
  49. package/dist/{chunk-57WB2EDF.js → chunk-ZBUROTJQ.js} +3 -3
  50. package/dist/{coerce-wwdELNYS.d.ts → coerce-DP0vYu-x.d.ts} +77 -1
  51. package/dist/{coerce-CJlgULrQ.d.cts → coerce-DRERzdve.d.cts} +77 -1
  52. package/dist/core/index.cjs +5 -5
  53. package/dist/core/index.d.cts +2 -2
  54. package/dist/core/index.d.ts +2 -2
  55. package/dist/core/index.js +4 -4
  56. package/dist/generate/index.cjs +20 -8
  57. package/dist/generate/index.cjs.map +1 -1
  58. package/dist/generate/index.d.cts +87 -9
  59. package/dist/generate/index.d.ts +87 -9
  60. package/dist/generate/index.js +19 -7
  61. package/dist/{index-BaoKVVhE.d.cts → index-B9QLziDe.d.cts} +2 -2
  62. package/dist/{index-CnHQgqNh.d.ts → index-BHIUyjhZ.d.ts} +2 -2
  63. package/dist/{index-CBCP84rv.d.cts → index-BL5N2Oyz.d.cts} +1 -1
  64. package/dist/{index-D_hzxD8w.d.cts → index-BMweUlm5.d.cts} +3 -0
  65. package/dist/{index-Ci3xyows.d.ts → index-CL4b4gUQ.d.ts} +3 -0
  66. package/dist/{index-BzLNkmA7.d.cts → index-CY-3NVfy.d.cts} +486 -5
  67. package/dist/{index-CnIdP4EZ.d.ts → index-Dw2DP7vm.d.ts} +1 -1
  68. package/dist/{index-DpC-mDlV.d.ts → index-zaZdmnU4.d.ts} +486 -5
  69. package/dist/index.cjs +49 -13
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +10 -10
  72. package/dist/index.d.ts +10 -10
  73. package/dist/index.js +55 -19
  74. package/dist/{masks-O73Rhj5d.d.ts → masks-Cc7-MzzY.d.ts} +1 -1
  75. package/dist/{masks-C8TWLFIi.d.cts → masks-pADfTOiD.d.cts} +1 -1
  76. package/dist/model/index.cjs +12 -12
  77. package/dist/model/index.d.cts +213 -16
  78. package/dist/model/index.d.ts +213 -16
  79. package/dist/model/index.js +11 -11
  80. package/dist/{index-DTsKaPaC.d.cts → profile-CQMEqdH9.d.cts} +88 -601
  81. package/dist/{index-BUtIf2go.d.ts → profile-DLdlIpS9.d.ts} +88 -601
  82. package/dist/theory/index.cjs +5 -5
  83. package/dist/theory/index.d.cts +7 -7
  84. package/dist/theory/index.d.ts +7 -7
  85. package/dist/theory/index.js +4 -4
  86. package/dist/{index-BpWOhkkE.d.ts → types-D-XgxGvp.d.cts} +1170 -14
  87. package/dist/{index-B3WCj_Hm.d.cts → types-Dh7C064C.d.ts} +1170 -14
  88. package/package.json +6 -5
  89. package/dist/chunk-5KW7DNCJ.cjs.map +0 -1
  90. package/dist/chunk-DKICR7AN.js.map +0 -1
  91. package/dist/chunk-GDKE6RQW.cjs.map +0 -1
  92. package/dist/chunk-HSY2VQXZ.js.map +0 -1
  93. package/dist/chunk-Q6R3QAZD.cjs.map +0 -1
  94. package/dist/chunk-R5VUXQMP.js.map +0 -1
  95. package/dist/chunk-VLE5R7PF.js.map +0 -1
  96. package/dist/chunk-XVMJO3QF.cjs.map +0 -1
  97. package/dist/chunk-YPB3A3EY.cjs.map +0 -1
  98. /package/dist/{chunk-X6RJAHII.js.map → chunk-7KG3RTYS.js.map} +0 -0
  99. /package/dist/{chunk-AWEMD747.js.map → chunk-M5RYMAFI.js.map} +0 -0
  100. /package/dist/{chunk-H66ARFU5.js.map → chunk-NB25P3EH.js.map} +0 -0
  101. /package/dist/{chunk-7GOKQJTQ.js.map → chunk-PUKJP2Z5.js.map} +0 -0
  102. /package/dist/{chunk-HOHFYGKW.js.map → chunk-UMWZ4ZET.js.map} +0 -0
  103. /package/dist/{chunk-57WB2EDF.js.map → chunk-ZBUROTJQ.js.map} +0 -0
@@ -1,8 +1,8 @@
1
- import { e as KeyScale, h as NoteEvent, i as NoteLike, g as Note, S as SpelledInterval } from './index-Cy15oTvh.js';
2
- import { d as ChordQuality, k as KeyVariant, b as Chord, q as SpelledKeyLike, c as ChordLike, K as KeyLike, n as ResolvedKey, N as NoteSafety, S as SafetyProfile } from './coerce-wwdELNYS.js';
3
- import { r as ScaleName } from './masks-O73Rhj5d.js';
4
- import { i as KeyProfileName, j as KeyProfilePair, R as RejectedCandidate, k as KeyRegion, e as ChordTimeline, C as CadenceHit, a as AnalyzedNote, T as TheoryLabel, K as KeyContext, b as CadenceResult } from './index-BpWOhkkE.js';
5
- import { b as MeterLike } from './index-Ci3xyows.js';
1
+ import { e as KeyScale, h as NoteEvent, i as NoteLike, g as Note } from './index-Cy15oTvh.js';
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-DP0vYu-x.js';
3
+ import { r as ScaleName } from './masks-Cc7-MzzY.js';
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-Dh7C064C.js';
5
+ import { b as MeterLike } from './index-CL4b4gUQ.js';
6
6
 
7
7
  /**
8
8
  * Church-mode candidates for {@link detectKey}.
@@ -603,6 +603,21 @@ type TrackAnalysis = {
603
603
  /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */
604
604
  notes: AnalyzedNote[];
605
605
  };
606
+ /**
607
+ * A note sounding with a {@link Conflict} that raises a pairwise flag against it.
608
+ *
609
+ * @category Arrangement & Analysis
610
+ */
611
+ type ConflictPartner = {
612
+ /** The partner's track index in the caller's input array. */
613
+ trackIndex: number;
614
+ /** The partner note's index in that track's own note array. */
615
+ originalIndex?: number;
616
+ /** The id the partner note carries in {@link TrackAnalysis.notes}. */
617
+ noteId: number;
618
+ /** The pairwise flags this note raises, as {@link SafetyPartner.reasons}. */
619
+ reasons: number;
620
+ };
606
621
  /**
607
622
  * A note that clashes with the harmony sounding beneath it.
608
623
  *
@@ -632,6 +647,11 @@ type Conflict = {
632
647
  resolveTo?: number;
633
648
  /** Nearby fully safe replacement pitches, nearest first. */
634
649
  suggestions?: number[];
650
+ /**
651
+ * The notes behind the pairwise flags in `reasons` — a parallel fifth, a
652
+ * crossing, a vertical clash — when any are set.
653
+ */
654
+ partners?: ConflictPartner[];
635
655
  rationale?: string;
636
656
  };
637
657
  /**
@@ -1367,56 +1387,24 @@ declare function phrasesFromTimeline(timeline: ChordTimeline, notes: readonly No
1367
1387
  declare function structuralCadences(phrases: readonly Phrase[]): StructuralCadence[];
1368
1388
 
1369
1389
  /**
1370
- * Sections: the largest unit this library reads, recovered from repetition.
1371
- *
1372
- * A section is found, not named. What the analysis can see is that a span of
1373
- * music is a restatement of an earlier one, so the spans are labelled by order
1374
- * of first appearance — A, B, C — and nothing more is claimed. Calling a
1375
- * repeated span a chorus would be a guess dressed as a finding: repetition
1376
- * alone cannot tell a chorus from a second verse, and the label a listener
1377
- * would give depends on lyrics, arrangement and loudness that the notes do not
1378
- * carry.
1379
- */
1380
-
1381
- /**
1382
- * A span of music labelled by which earlier span it restates.
1390
+ * Reading a piece into a {@link ReferenceProfile}.
1383
1391
  *
1384
- * @category Arrangement & Analysis
1392
+ * The profile is assembled from analyses the library already runs — chord and
1393
+ * key inference, hypermeter, phrases, sections, reduction, motifs and rhythm —
1394
+ * each run exactly once and all read against one span: every analysis is handed
1395
+ * the same `totalBeats`, so the phrases, sections and both rhythm readings
1396
+ * describe the same stretch of music. The harmonic reading enters as a bundle
1397
+ * of its own, so a caller already holding one (a score's kept chord analysis,
1398
+ * an arrangement's session) builds the profile from it without inferring the
1399
+ * harmony a second time.
1385
1400
  */
1386
- type FormSection = {
1387
- /**
1388
- * The section's letter, assigned in order of first appearance: the opening
1389
- * material is A, the first material unlike it is B, and a later restatement
1390
- * of A is A again. Past Z the letters double (AA, AB), the way spreadsheet
1391
- * columns do. No functional name — verse, chorus, bridge — is ever inferred.
1392
- */
1393
- label: string;
1394
- /** First beat of the section; negative when it opens with a pickup. */
1395
- startBeat: number;
1396
- /** End of the section, exclusive. */
1397
- endBeat: number;
1398
- /** The section's length in bars. */
1399
- bars: number;
1400
- /**
1401
- * Index, in the returned array, of the first section carrying this label; the
1402
- * section's own index when it is that first appearance.
1403
- */
1404
- firstOccurrence: number;
1405
- /**
1406
- * How alike this section and its first occurrence are, in [0, 1]; 1 on the
1407
- * first occurrence itself. The mean over the units the section was merged
1408
- * from, each scored against the unit it matched.
1409
- */
1410
- similarity: number;
1411
- /** How the section came to carry its label. */
1412
- rationale: string;
1413
- };
1401
+
1414
1402
  /**
1415
- * Options for {@link sectionsFromNotes}.
1403
+ * Options for {@link analyzeReference}.
1416
1404
  *
1417
1405
  * @category Arrangement & Analysis
1418
1406
  */
1419
- type FormSectionOptions = {
1407
+ type ReferenceProfileOptions = {
1420
1408
  /**
1421
1409
  * A single time signature held across the whole span, as sugar for a
1422
1410
  * one-element `meters`; defaults to 4/4. Giving both is an input error.
@@ -1425,586 +1413,85 @@ type FormSectionOptions = {
1425
1413
  */
1426
1414
  ts?: MeterLike;
1427
1415
  /**
1428
- * The meter as it changes over the span, so a piece that changes metre is cut
1429
- * at its own bar lines.
1416
+ * The meter as it changes over the span.
1430
1417
  *
1431
1418
  * @defaultValue 4/4 throughout
1432
1419
  */
1433
1420
  meters?: MeterLike;
1434
1421
  /**
1435
- * Length of the unit spans are compared in, in bars; defaults to one hyperbar.
1422
+ * The key, when it is already known: read as one key across the whole span,
1423
+ * as {@link chordTimelineFromNotes} reads it. Omit it to have the keys
1424
+ * searched for over time.
1436
1425
  *
1437
- * Sections are found by comparing equal units and merging the neighbours that
1438
- * match, so this is the resolution of the answer rather than the length of a
1439
- * section: a section is any whole number of units long.
1440
- *
1441
- * @defaultValue the inferred hyperbar, in bars
1442
- */
1443
- unitBars?: number;
1444
- /**
1445
- * How alike two units must be before the later one is called a restatement of
1446
- * the earlier, in [0, 1].
1447
- *
1448
- * @defaultValue `0.8`
1449
- */
1450
- threshold?: number;
1451
- /**
1452
- * End of the analysed span in beats; defaults to the end of the last note.
1453
- *
1454
- * @defaultValue the end of the last note
1455
- */
1456
- totalBeats?: number;
1457
- /**
1458
- * Upper bound on the work this call may do.
1459
- *
1460
- * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
1461
- */
1462
- budget?: number;
1463
- };
1464
- /**
1465
- * Recover a piece's sections from what it repeats.
1466
- *
1467
- * The span is cut into equal units of `unitBars` bars, each unit is compared
1468
- * with every unit before it, and a unit alike enough to an earlier one takes
1469
- * that unit's label. Neighbouring units carrying the same label are then merged,
1470
- * so an eight-bar section built of two like four-bar units is reported as one
1471
- * section rather than two.
1472
- *
1473
- * Likeness is measured on the melodic line and on the harmonic content
1474
- * together — the same tune under different harmony is a variation, not a
1475
- * restatement — and the labels are letters in order of first appearance.
1476
- * Nothing beyond "this is a restatement of that" is asserted: see
1477
- * {@link FormSection.label}.
1478
- *
1479
- * A pickup belongs to the section it leads into, so material before the first
1480
- * bar is folded into the opening unit rather than made a section of its own.
1481
- *
1482
- * @param notes The piece's notes, in any order. Notes that never sound are
1483
- * dropped.
1484
- * @param opts Analysis options; see {@link FormSectionOptions}.
1485
- * @returns The sections, in time order.
1486
- * @example
1487
- * ```ts
1488
- * import { sectionsFromNotes } from '@libraz/libcantus';
1489
- * const bar = (pitch: number, at: number) => ({ pitch, startBeat: at, durationBeat: 4 });
1490
- * const notes = [bar(60, 0), bar(64, 4), bar(67, 8), bar(60, 12)];
1491
- * sectionsFromNotes(notes, { unitBars: 2 }).map((section) => section.label);
1492
- * ```
1493
- * @category Arrangement & Analysis
1494
- */
1495
- declare function sectionsFromNotes(notes: readonly NoteEvent[], opts?: FormSectionOptions): FormSection[];
1496
-
1497
- /**
1498
- * The recurring cells a melody is built from.
1499
- *
1500
- * A motif is the interval contour plus the rhythmic profile, so every statement
1501
- * of it carries the same identity however far it has been transposed or however
1502
- * wide its note values are written. What this module does is find them: every
1503
- * window of the melody is reduced to that identity, windows that agree are
1504
- * grouped, and a group stated often enough is a motif.
1505
- */
1506
-
1507
- /**
1508
- * One statement of a motif, addressed in the melody it was found in.
1509
- *
1510
- * The notes themselves are not repeated here: a statement is the motif's notes
1511
- * moved by `transpose` and stretched by `timeRatio`, and `noteIndex` addresses
1512
- * the original array for a caller that wants them exactly.
1513
- *
1514
- * @category Arrangement & Analysis
1515
- */
1516
- type MotifOccurrence = {
1517
- /**
1518
- * Index of the statement's first note among the sounding notes of the melody,
1519
- * in time order. Notes that never sound are dropped before the search, so this
1520
- * is not an index into the caller's array unless the caller's array has none.
1521
- */
1522
- noteIndex: number;
1523
- /** Onset of the statement's first note. */
1524
- startBeat: number;
1525
- /** End of the statement's last note, exclusive. */
1526
- endBeat: number;
1527
- /** Semitones this statement stands above the motif's first statement. */
1528
- transpose: number;
1529
- /**
1530
- * How much this statement is stretched against the motif's first: 2 for a
1531
- * statement in doubled note values, 0.5 for one in halved values, 1 for one in
1532
- * the same values.
1533
- */
1534
- timeRatio: number;
1535
- };
1536
- /**
1537
- * A recurring melodic cell.
1538
- *
1539
- * Identity is the interval sequence plus the rhythmic profile, so every
1540
- * statement of the motif carries the same `intervals` and `rhythm` however far
1541
- * it has been transposed or however wide its note values are written. `notes`
1542
- * holds one statement — the first — exactly as it sounds.
1543
- *
1544
- * @category Arrangement & Analysis
1545
- */
1546
- type MotifData = {
1547
- /** The first statement, exactly as it sounds in the melody. */
1548
- notes: NoteEvent[];
1549
- /**
1550
- * Semitones between consecutive notes. This is the transposition-invariant
1551
- * identity of the cell: two statements a fifth apart share it.
1426
+ * @defaultValue the keys inferred from the notes over time
1552
1427
  */
1553
- intervals: number[];
1554
- /**
1555
- * Onset-to-onset gaps as multiples of the first gap, so an even cell reads
1556
- * `[1, 1, 1]` whatever the tempo or note values it is written in.
1557
- */
1558
- rhythm: number[];
1559
- /** Every statement found, in time order; the first is `notes`. */
1560
- occurrences: MotifOccurrence[];
1561
- /** Why this counts as a motif. */
1562
- rationale: string;
1563
- };
1564
- /**
1565
- * Options controlling {@link extractMotifs}.
1566
- *
1567
- * @category Arrangement & Analysis
1568
- */
1569
- type ExtractMotifsOptions = {
1428
+ key?: KeyLike;
1570
1429
  /**
1571
- * Shortest cell to report, in notes.
1430
+ * The harmony, given rather than inferred — the shape an arrangement's
1431
+ * `timeline` option takes. The key regions are still read from `key` or,
1432
+ * without one, from the notes.
1572
1433
  *
1573
- * @defaultValue 3
1434
+ * @defaultValue inferred from the notes
1574
1435
  */
1575
- minNotes?: number;
1436
+ timeline?: ChordTimeline;
1576
1437
  /**
1577
- * Longest cell to report, in notes.
1438
+ * The melodic line. Omit it to read the top line of `notes`, where notes
1439
+ * struck together are one event and the highest of them stands for it.
1578
1440
  *
1579
- * @defaultValue 8
1441
+ * @defaultValue the top line of the notes
1580
1442
  */
1581
- maxNotes?: number;
1443
+ melody?: readonly NoteEvent[];
1582
1444
  /**
1583
- * How many statements a cell needs before it is reported.
1445
+ * End of the analysed span in beats.
1584
1446
  *
1585
- * @defaultValue 2
1447
+ * @defaultValue the end of the last note or chord segment
1586
1448
  */
1587
- minOccurrences?: number;
1449
+ totalBeats?: number;
1588
1450
  /**
1589
- * Upper bound on the work this call may do.
1451
+ * Upper bound on the work each analysis may do.
1590
1452
  *
1591
1453
  * @defaultValue {@link DEFAULT_GENERATION_BUDGET}
1592
1454
  */
1593
1455
  budget?: number;
1594
1456
  };
1595
1457
  /**
1596
- * Find the cells a melody keeps coming back to.
1597
- *
1598
- * Every window of `minNotes` to `maxNotes` consecutive notes is reduced to its
1599
- * interval sequence and its rhythmic profile, and windows sharing both are
1600
- * statements of one motif — so a restatement a fifth higher, or one written in
1601
- * doubled note values, is found as the same cell rather than as a new one.
1602
- * Overlapping statements of a repetitive figure are counted once, and a cell
1603
- * whose statements all sit inside the statements of a longer cell that recurs at
1604
- * least as often is dropped, so the answer names the longest thing that recurs
1605
- * rather than every fragment of it.
1606
- *
1607
- * The melody is expected to be monophonic, as {@link analyzeVoice} expects a
1608
- * voice to be; split polyphonic material into lines first. Notes struck together
1609
- * are read as one event, the highest of them standing for it, so a chord is
1610
- * reduced to its top voice rather than read as a line of its own. Onsets are
1611
- * expected to be quantised, since two statements must agree on their rhythm to
1612
- * be recognised as one motif.
1613
- *
1614
- * @param notes The melody, in time order. Notes that never sound are dropped.
1615
- * @param opts Cell-length bounds and the recurrence threshold; see
1616
- * {@link ExtractMotifsOptions}.
1617
- * @returns The motifs found, longest first, then by how often they recur, then
1618
- * by where they first appear. Empty when nothing recurs.
1619
- * @example
1620
- * ```ts
1621
- * import { extractMotifs } from '@libraz/libcantus';
1622
- * const notes = [
1623
- * { pitch: 60, startBeat: 0, durationBeat: 1 },
1624
- * { pitch: 62, startBeat: 1, durationBeat: 1 },
1625
- * { pitch: 64, startBeat: 2, durationBeat: 1 },
1626
- * { pitch: 67, startBeat: 4, durationBeat: 1 },
1627
- * { pitch: 69, startBeat: 5, durationBeat: 1 },
1628
- * { pitch: 71, startBeat: 6, durationBeat: 1 },
1629
- * ];
1630
- * const motifs = extractMotifs(notes);
1631
- * motifs[0]?.occurrences.map((o) => o.startBeat); // [0, 4]
1632
- * ```
1633
- * @category Arrangement & Analysis
1634
- */
1635
- declare function extractMotifs(notes: readonly NoteEvent[], opts?: ExtractMotifsOptions): MotifData[];
1636
- /**
1637
- * Read a run of notes as a single-statement motif.
1638
- *
1639
- * {@link extractMotifs} answers with motifs it found recurring; this is how a
1640
- * caller names one it already has — a subject, an answer, a phrase lifted out of
1641
- * a score — so that it can be handed to {@link relateMotifs}.
1642
- *
1643
- * The cell is expected to be monophonic, as {@link analyzeVoice} expects a voice
1644
- * to be. Notes struck together are read as one event, the highest of them
1645
- * standing for it, so a chord is reduced to its top voice rather than read as a
1646
- * line of its own.
1647
- *
1648
- * @param notes The cell, in time order. Notes that never sound are dropped, and
1649
- * notes sharing an onset are folded to their top voice.
1650
- * @returns The motif, carrying exactly one occurrence.
1458
+ * Read a piece's structure into a {@link ReferenceProfile}: its form, phrase
1459
+ * shapes, harmonic function and rhythm, motif derivations and melodic rhythm,
1460
+ * without the notes themselves.
1461
+ *
1462
+ * The melody is the `melody` option when given, and otherwise the top line of
1463
+ * the notes — notes struck together read as one event, the highest standing
1464
+ * for it — so a piano part read whole answers for its upper voice. The harmony
1465
+ * is the `timeline` option when given, and otherwise inferred from all the
1466
+ * notes. The motif search expects quantized onsets.
1467
+ *
1468
+ * The sections are cut on the hyperbar grouping the phrase reading settles
1469
+ * on, which weighs where the harmony cadences; `sectionsFromNotes` alone
1470
+ * groups without the cadences, so the two can differ.
1471
+ *
1472
+ * @param notes The piece's notes, in any order. Notes that never sound are dropped.
1473
+ * @param opts The meter, key, harmony, melody, span end and budget; see
1474
+ * {@link ReferenceProfileOptions}.
1475
+ * @returns The profile.
1476
+ * @throws {InvalidInputError} If the notes or an option cannot be read.
1477
+ * @throws {BudgetExceededError} If an analysis would exceed the budget.
1651
1478
  * @example
1652
1479
  * ```ts
1653
- * import { motifFromNotes, relateMotifs } from '@libraz/libcantus';
1654
- * const subject = motifFromNotes([
1655
- * { pitch: 60, startBeat: 0, durationBeat: 1 },
1656
- * { pitch: 62, startBeat: 1, durationBeat: 1 },
1657
- * { pitch: 64, startBeat: 2, durationBeat: 1 },
1658
- * ]);
1659
- * const answer = motifFromNotes([
1660
- * { pitch: 67, startBeat: 3, durationBeat: 1 },
1661
- * { pitch: 69, startBeat: 4, durationBeat: 1 },
1662
- * { pitch: 71, startBeat: 5, durationBeat: 1 },
1663
- * ]);
1664
- * relateMotifs(subject, answer)?.kind; // 'transposition'
1665
- * ```
1666
- * @category Arrangement & Analysis
1667
- */
1668
- declare function motifFromNotes(notes: readonly NoteEvent[]): MotifData;
1669
-
1670
- /**
1671
- * What every reading of a melody has to agree on first.
1672
- *
1673
- * A melody is compared, named and classified by three modules here, and all
1674
- * three start from the same two questions: which notes sound, in what order,
1675
- * and what the gaps between their onsets are. Answered separately they would
1676
- * drift — one reading folding a chord to its top voice and another not — so
1677
- * they are answered once, here.
1678
- */
1679
-
1680
- /**
1681
- * Anything that can be read as a melodic line: a motif, or plain note events.
1682
- *
1683
- * @category Arrangement & Analysis
1684
- */
1685
- type MelodicPhrase = MotifData | readonly NoteEvent[];
1686
-
1687
- /**
1688
- * The shape of a line: which way it moves, and what that adds up to.
1689
- *
1690
- * A contour is the melody with its pitches forgotten — the sequence of ups and
1691
- * downs alone — which is what lets a phrase be called an arch whatever key it
1692
- * is in and whatever intervals it is built from.
1693
- */
1694
-
1695
- /**
1696
- * Step direction between two consecutive notes.
1697
- *
1698
- * @category Arrangement & Analysis
1699
- */
1700
- type ContourDirection = 'up' | 'down' | 'same';
1701
- /**
1702
- * The overall shape of a line.
1703
- *
1704
- * The first four names are the vocabulary the motif generator's contours are
1705
- * written in, and mean the same here. `'static'` is the shape a generator never
1706
- * asks for and an analysis regularly meets: a line that does not move. A line
1707
- * that dips and comes back — an arch upside down — reads as `'wave'`, since the
1708
- * shared vocabulary has no name of its own for it.
1709
- *
1710
- * A wave has to turn more than once to be heard as one, so a cell the generator
1711
- * writes for `'wave'` reads back as `'wave'` only from three bars up; the one-
1712
- * and two-bar cells turn once and read as `'arch'`. The other three shapes come
1713
- * back under their own name at every length.
1714
- *
1715
- * @category Arrangement & Analysis
1716
- */
1717
- type MelodicContourShape = 'arch' | 'ascending' | 'descending' | 'wave' | 'static';
1718
- /**
1719
- * The abstract shape of a line: its step directions and what they add up to.
1720
- *
1721
- * @category Arrangement & Analysis
1722
- */
1723
- type MelodicContour = {
1724
- /** One direction per adjacent pair of notes, in time order. */
1725
- directions: ContourDirection[];
1726
- /** What the directions add up to. */
1727
- shape: MelodicContourShape;
1728
- /** Index of the highest note; ties keep the earliest, and -1 for a line with no notes. */
1729
- peakIndex: number;
1730
- /** Index of the lowest note; ties keep the earliest, and -1 for a line with no notes. */
1731
- troughIndex: number;
1732
- /** Semitone distance from the lowest note to the highest. */
1733
- range: number;
1734
- /** Why the line was given that shape. */
1735
- rationale: string;
1736
- };
1737
- /**
1738
- * Read the shape of a line.
1739
- *
1740
- * The step directions are the literal answer; the shape is what they add up to.
1741
- * A line is called ascending or descending when most of its motion runs one way,
1742
- * not only when every step does — a climb with one dip in it is still heard as a
1743
- * climb — and an arch when it rises to an interior peak and comes back down.
1744
- * Anything that keeps changing direction is a wave, and a line that does not move
1745
- * is static.
1746
- *
1747
- * The line is expected to be monophonic, as {@link analyzeVoice} expects a voice
1748
- * to be; split polyphonic material into lines first. Notes struck together are
1749
- * read as one event, the highest of them standing for it — a chord and the note
1750
- * after it trace one step, not a climb through the chord — so a piano part read
1751
- * whole answers for its top voice rather than for a line no one plays.
1752
- *
1753
- * @param notes The line: a motif, or plain note events. Notes that never sound
1754
- * are dropped, and notes sharing an onset are folded to their top voice.
1755
- * @returns The directions, the shape, the peak and trough, and a rationale. A
1756
- * line with no sounding notes is static with a range of 0, and its peak and
1757
- * trough are -1: there is no note for them to point at.
1758
- * @example
1759
- * ```ts
1760
- * import { melodicContour } from '@libraz/libcantus';
1480
+ * import { analyzeReference } from '@libraz/libcantus';
1481
+ * const triad = (pitches: number[], startBeat: number) =>
1482
+ * pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
1761
1483
  * const notes = [
1762
- * { pitch: 60, startBeat: 0, durationBeat: 1 },
1763
- * { pitch: 64, startBeat: 1, durationBeat: 1 },
1764
- * { pitch: 60, startBeat: 2, durationBeat: 1 },
1484
+ * ...triad([60, 64, 67], 0),
1485
+ * ...triad([65, 69, 72], 4),
1486
+ * ...triad([67, 71, 74], 8),
1487
+ * ...triad([60, 64, 67], 12),
1765
1488
  * ];
1766
- * melodicContour(notes).shape; // 'arch'
1767
- * ```
1768
- * @category Arrangement & Analysis
1769
- */
1770
- declare function melodicContour(notes: MelodicPhrase): MelodicContour;
1771
-
1772
- /**
1773
- * The transformation between two statements of a motif, named.
1774
- *
1775
- * Two cells stand in a relation when one is the other put through a device the
1776
- * repertoire has a name for — repetition, transposition, inversion, retrograde,
1777
- * augmentation — and naming it is what turns a pair of similar phrases into an
1778
- * account of how the second was made from the first.
1779
- */
1780
-
1781
- /**
1782
- * How one statement of a motif stands to another.
1783
- *
1784
- * `'transposition'` is the literal one — every interval preserved, which is what
1785
- * makes a sequence built on it a *real* sequence. `'tonalTransposition'` shifts
1786
- * by scale degrees instead and lets the key resize the intervals, which is what
1787
- * makes a sequence built on it a *tonal* sequence; the two are different devices
1788
- * and are never reported as one. `'augmentation'` and `'diminution'` name the
1789
- * relation only when the pitches are untouched, so an inversion in doubled note
1790
- * values reads as `'inversion'` with a `timeRatio` of 2 rather than losing the
1791
- * inversion to the stretch.
1792
- *
1793
- * @category Arrangement & Analysis
1794
- */
1795
- type MotifRelationKind = 'repetition' | 'transposition' | 'tonalTransposition' | 'inversion' | 'retrograde' | 'retrogradeInversion' | 'augmentation' | 'diminution';
1796
- /**
1797
- * The transformation that turns one motif into another.
1798
- *
1799
- * @category Arrangement & Analysis
1800
- */
1801
- type MotifRelation = {
1802
- /** The named transformation. */
1803
- kind: MotifRelationKind;
1804
- /**
1805
- * Whether the second statement begins where the first ends, which is what
1806
- * separates a sequence from a restatement elsewhere in the piece: a
1807
- * `'transposition'` that is one is a real sequence, a `'tonalTransposition'`
1808
- * that is one is a tonal sequence.
1809
- */
1810
- sequence: boolean;
1811
- /** Semitones from the first statement's first note to the second's. */
1812
- semitones: number;
1813
- /** The same distance named as a spelled interval. */
1814
- interval: SpelledInterval;
1815
- /** Scale degrees the second statement stands above the first, when tonal. */
1816
- degrees?: number;
1817
- /**
1818
- * How much the second statement is stretched against the first: 2 for doubled
1819
- * note values, 0.5 for halved, 1 for unchanged.
1820
- *
1821
- * Measured in the direction the relation names, so a retrograde that kept
1822
- * every note value reports 1 however uneven those values are: it is compared
1823
- * against the model played backwards, not against the model as written.
1824
- */
1825
- timeRatio: number;
1826
- /** Why the pair was given that name. */
1827
- rationale: string;
1828
- };
1829
- /**
1830
- * Name how two statements of a motif relate.
1831
- *
1832
- * The two are compared as shapes, not as pitches: the interval sequences say
1833
- * which transformations could name the pair and the onset gaps say which of
1834
- * them does, so a statement a fourth higher in doubled note values is
1835
- * recognised for what it is, and a cell that reads as both an inversion and a
1836
- * retrograde is named by the way its rhythm runs rather than by which name came
1837
- * to hand first. A pair whose rhythms correspond in neither direction — the same
1838
- * way round for a repetition or a transposition, the model played backwards for
1839
- * a retrograde, note values and all — is not a transformation of the motif but a
1840
- * different figure, and answers null; {@link melodicSimilarity} is what scores
1841
- * those.
1842
- *
1843
- * With a `key` in hand the search also asks whether the second statement is the
1844
- * first moved by scale degrees rather than by semitones. That is the tonal
1845
- * answer to the real one, and the two are kept apart: a real transposition keeps
1846
- * every interval literally, a tonal one lets the key resize them, so a subject
1847
- * answered a step higher reads as `'transposition'` when its major second stays
1848
- * major and as `'tonalTransposition'` when the scale turns it minor.
1849
- *
1850
- * @param a The model statement.
1851
- * @param b The statement to name against it.
1852
- * @param keyLike Key context for the tonal reading. Without one that reading is
1853
- * unavailable, and a statement holding only diatonically is left unnamed
1854
- * unless its interval pattern also fits a member of the retrograde family, in
1855
- * which case it is named there: a triad restated a degree higher swaps two
1856
- * adjacent interval sizes, which is what a retrograde inversion does to it as
1857
- * well, and equal note values read the same way round in both directions, so
1858
- * the notes alone cannot separate the two. A key separates them.
1859
- * @returns The relation, or null when the two stand in none.
1860
- * @example
1861
- * ```ts
1862
- * import { majorKey, motifFromNotes, relateMotifs } from '@libraz/libcantus';
1863
- * const model = motifFromNotes([
1864
- * { pitch: 60, startBeat: 0, durationBeat: 1 },
1865
- * { pitch: 62, startBeat: 1, durationBeat: 1 },
1866
- * { pitch: 64, startBeat: 2, durationBeat: 1 },
1867
- * ]);
1868
- * const answer = motifFromNotes([
1869
- * { pitch: 62, startBeat: 3, durationBeat: 1 },
1870
- * { pitch: 64, startBeat: 4, durationBeat: 1 },
1871
- * { pitch: 65, startBeat: 5, durationBeat: 1 },
1872
- * ]);
1873
- * relateMotifs(model, answer, majorKey(0))?.kind; // 'tonalTransposition'
1874
- * ```
1875
- * @category Arrangement & Analysis
1876
- */
1877
- declare function relateMotifs(a: MotifData, b: MotifData, keyLike?: SpelledKeyLike): MotifRelation | null;
1878
-
1879
- /**
1880
- * Harmonic reduction: which chords carry the progression and which decorate it.
1881
- *
1882
- * `analyzeVoice` can say that a note is a passing tone; a timeline is flat, so
1883
- * nothing could say the same of a chord. This is that answer one level up — the
1884
- * middle chord of `Cmaj7 - C#dim7 - Dm7` is an embellishment, and the frame
1885
- * under `I - IV - #ivo7 - V - I` is `I - IV - V - I`.
1886
- *
1887
- * **The position taken here.** What counts as structural is a theoretical
1888
- * choice, not a fact, and this module makes the functional one: *a chord is
1889
- * structural unless it is demonstrably an embellishment of its neighbours*. The
1890
- * frame is what carries harmonic function in the prevailing key — the tonic, the
1891
- * dominant, and the chords that cadence — so those are never reduced away; a
1892
- * chord that tonicizes something other than the tonic is prolonging, not
1893
- * framing, however dominant its sonority. Everything else keeps its place unless
1894
- * its root traces one of two figures against the chords around it, the same two
1895
- * figures {@link analyzeVoice} matches at the note level: a step through
1896
- * (passing) and a step away and back (neighbor). Defaulting to structural, and
1897
- * demoting only on evidence, is what keeps a reduction from quietly deleting
1898
- * harmony it merely failed to recognize.
1899
- *
1900
- * The salience reading — that the frame is whatever lasts longest, regardless of
1901
- * function — is the main alternative, and it is a live one for repertoire where
1902
- * function is weak. {@link ReduceProgressionOptions.basis} switches to it: the
1903
- * figures stay the same, only what protects a chord from them changes.
1904
- *
1905
- * Both readings are deterministic: the same timeline and key always reduce the
1906
- * same way.
1907
- */
1908
-
1909
- /**
1910
- * The place a chord holds in a progression.
1911
- *
1912
- * `'structural'` is the frame; `'passing'` and `'neighbor'` are the two
1913
- * embellishing figures, spelled exactly as {@link TheoryLabel} spells them for
1914
- * a note, so one legend covers both levels of analysis.
1915
- *
1916
- * @category Arrangement & Analysis
1917
- */
1918
- type ReductionLevel = 'structural' | 'passing' | 'neighbor';
1919
- /**
1920
- * A chord with the place it holds, the beats it holds it for, and a short
1921
- * rationale.
1922
- *
1923
- * One entry per timeline segment, in the same order. The entry carries the
1924
- * segment's own beat range, so the chords that survive a filter still say when
1925
- * they sound: holding the structural chords fixed is what a reharmonizer needs,
1926
- * and it cannot hold a chord at a place the filter threw away.
1927
- *
1928
- * @category Arrangement & Analysis
1929
- */
1930
- type ReducedChord = {
1931
- chord: Chord;
1932
- startBeat: number;
1933
- endBeat: number;
1934
- level: ReductionLevel;
1935
- rationale: string;
1936
- };
1937
- /**
1938
- * What a reduction treats as the frame.
1939
- *
1940
- * `'function'` is the functional reading: the tonic, the dominant and the
1941
- * chords that cadence are the frame. `'duration'` is the salience reading: a
1942
- * chord is the frame when it outlasts the chords around it — its length beats
1943
- * the median of the other chords' — whatever function it carries. Nothing
1944
- * outlasts anything in an even harmonic rhythm, so a loop or a vamp protects no
1945
- * chord by length and the two figures decide, as they do under either reading.
1946
- *
1947
- * @category Arrangement & Analysis
1948
- */
1949
- type ReductionBasis = 'function' | 'duration';
1950
- /**
1951
- * Options for {@link reduceProgression}.
1952
- *
1953
- * @category Arrangement & Analysis
1954
- */
1955
- type ReduceProgressionOptions = {
1956
- /**
1957
- * Which reading decides what the frame is; see {@link ReductionBasis}.
1958
- *
1959
- * @defaultValue `'function'`
1960
- */
1961
- basis?: ReductionBasis;
1962
- };
1963
- /**
1964
- * Reduce a chord progression to its frame, labelling every chord.
1965
- *
1966
- * One entry comes back per timeline segment, in the same order, so the two read
1967
- * in step. Filtering the result to `'structural'` is the skeleton view — the
1968
- * `I - IV - V - I` under a progression that spells far more chords than that —
1969
- * and holding those chords fixed is what a reharmonizer needs in order to
1970
- * rewrite the surface without moving the harmony. Each entry carries its own
1971
- * `startBeat` and `endBeat`, so a filtered chord still knows where it sounds
1972
- * once the segments it was read against are gone.
1973
- *
1974
- * A chord is structural unless one of two figures demotes it: its root stepping
1975
- * in from the previous chord and on to the next in the same direction (passing),
1976
- * or stepping away from a harmony that returns (neighbor). Chords carrying the
1977
- * frame are never demoted, and what carries the frame is the module's stated
1978
- * theoretical position — see {@link ReductionBasis} for the reading this takes
1979
- * by default and the one it can be switched to.
1980
- *
1981
- * Chords separated by a rest form no figure across it, the way
1982
- * {@link detectCadences} pairs no chords across one.
1983
- *
1984
- * @param timeline The chord timeline to reduce.
1985
- * @param key The prevailing key, or the key in force at a given beat, for music
1986
- * that modulates.
1987
- * @param opts Which reading decides the frame; see
1988
- * {@link ReduceProgressionOptions}.
1989
- * @returns One labelled chord per segment, in time order.
1990
- * @throws If `opts.basis` is not one of the readings {@link ReductionBasis}
1991
- * names.
1992
- * @example
1993
- * ```ts
1994
- * import { chordTimelineFromChords, majorKey, reduceProgression } from '@libraz/libcantus';
1995
- * const timeline = chordTimelineFromChords(
1996
- * [
1997
- * { rootPc: 0, quality: 'maj7', startBeat: 0 },
1998
- * { rootPc: 1, quality: 'dim7', startBeat: 4 },
1999
- * { rootPc: 2, quality: 'min7', startBeat: 8 },
2000
- * ],
2001
- * 12,
2002
- * );
2003
- * reduceProgression(timeline, majorKey(0)).map((entry) => entry.level);
2004
- * // ['structural', 'passing', 'structural']
1489
+ * const profile = analyzeReference(notes, { key: 'C major' });
1490
+ * profile.harmony.chords.map((chord) => chord.roman); // ['I', 'IV', 'V', 'I']
1491
+ * profile.span.bars; // 4
2005
1492
  * ```
2006
1493
  * @category Arrangement & Analysis
2007
1494
  */
2008
- declare function reduceProgression(timeline: ChordTimeline, key: KeyContext, opts?: ReduceProgressionOptions): ReducedChord[];
1495
+ declare function analyzeReference(notes: readonly NoteEvent[], opts?: ReferenceProfileOptions): ReferenceProfile;
2009
1496
 
2010
- export { phrasesFromTimeline as $, type ArrangementAnalysis as A, analyzeArrangement as B, type ChordMatch as C, type DetectChordOptions as D, type ExtractMotifsOptions as E, type FormSection as F, analyzePolyphony as G, type Hypermeter as H, augmentedSixthChord as I, augmentedSixthFromPitchClasses as J, type KeyMatch as K, augmentedSixthKind as L, MODAL_SCALE_NAMES as M, createArrangementSession as N, detectChord as O, type Phrase as P, detectChordBest as Q, type ReduceProgressionOptions as R, type StructuralCadence as S, type TensionPoint as T, detectKey as U, detectKeyBest as V, detectKeyFromNotes as W, extractMotifs as X, hypermeter as Y, melodicContour as Z, motifFromNotes as _, type ArrangementOptions as a, reduceProgression as a0, relateMotifs as a1, sectionsFromNotes as a2, spellAugmentedSixth as a3, structuralCadences as a4, tensionCurve as a5, tensionCurveFrom as a6, type ArrangementSession as b, type ArrangementTrack as c, type AugmentedSixthKind as d, type Conflict as e, type ContourDirection as f, type DetectKeyOptions as g, type FormSectionOptions as h, type HypermeterOptions as i, type MelodicContour as j, type MelodicContourShape as k, type MelodicPhrase as l, type ModalCandidate as m, type ModalScaleName as n, type MotifData as o, type MotifOccurrence as p, type MotifRelation as q, type MotifRelationKind as r, type PhraseOptions as s, type PhraseSignal as t, type ReducedChord as u, type ReductionBasis as v, type ReductionLevel as w, type TrackAnalysis as x, type TrackEdit as y, type TrackRole as z };
1497
+ export { type ArrangementAnalysis as A, detectKeyFromNotes as B, type ChordMatch as C, type DetectChordOptions as D, hypermeter as E, phrasesFromTimeline as F, spellAugmentedSixth as G, type Hypermeter as H, structuralCadences as I, tensionCurve as J, type KeyMatch as K, tensionCurveFrom as L, MODAL_SCALE_NAMES as M, type Phrase as P, type ReferenceProfileOptions as R, type StructuralCadence as S, type TensionPoint as T, type ArrangementOptions as a, type ArrangementSession as b, type ArrangementTrack as c, type AugmentedSixthKind as d, type Conflict as e, type ConflictPartner as f, type DetectKeyOptions as g, type HypermeterOptions as h, type ModalCandidate as i, type ModalScaleName as j, type PhraseOptions as k, type PhraseSignal as l, type TrackAnalysis as m, type TrackEdit as n, type TrackRole as o, 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 };