@libraz/libcantus 1.3.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 +3 -1
- package/README_ja.md +13 -11
- package/dist/analyze/index.cjs +7 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +14 -12
- package/dist/analyze/index.d.ts +14 -12
- package/dist/analyze/index.js +6 -6
- 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-YFA7X2F6.cjs → chunk-3MDOEFIQ.cjs} +411 -327
- package/dist/chunk-3MDOEFIQ.cjs.map +1 -0
- package/dist/{chunk-IPEF6NWB.js → chunk-5MWRBYAT.js} +278 -154
- package/dist/chunk-5MWRBYAT.js.map +1 -0
- package/dist/{chunk-B2PW6EHN.cjs → chunk-6TTBEON3.cjs} +1253 -889
- 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-PWC4CRWT.cjs → chunk-E3QO3UET.cjs} +31 -23
- package/dist/chunk-E3QO3UET.cjs.map +1 -0
- package/dist/{chunk-GNVN4PPJ.js → chunk-FJFULJZE.js} +531 -213
- package/dist/chunk-FJFULJZE.js.map +1 -0
- package/dist/{chunk-CPMB7P75.js → chunk-FK3DGAIZ.js} +608 -244
- 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-4R2LWV7E.cjs → chunk-HYSVSFNI.cjs} +664 -540
- 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-IFZDWIKM.cjs → chunk-LQZQDBCD.cjs} +1048 -730
- 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-MLBPXA4Z.js → chunk-UTE5A36R.js} +198 -114
- package/dist/chunk-UTE5A36R.js.map +1 -0
- package/dist/{chunk-ZBUROTJQ.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 +8 -8
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +28 -18
- package/dist/generate/index.d.ts +28 -18
- package/dist/generate/index.js +7 -7
- package/dist/{index-BMweUlm5.d.cts → index-5VJgKz_Q.d.cts} +6 -2
- package/dist/{index-BHIUyjhZ.d.ts → index-BLuDesVG.d.ts} +20 -13
- package/dist/{index-B9QLziDe.d.cts → index-BV6APVa6.d.cts} +20 -13
- package/dist/{index-CL4b4gUQ.d.ts → 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-CY-3NVfy.d.cts → index-DVInzCDS.d.cts} +53 -29
- package/dist/{index-9eoQs2C1.d.ts → index-Dg-SAfhz.d.ts} +6 -4
- package/dist/{index-zaZdmnU4.d.ts → index-jgeug9EA.d.ts} +53 -29
- package/dist/{index-cHvbYgJI.d.cts → index-pQihL4z7.d.cts} +6 -4
- package/dist/index.cjs +13 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +12 -12
- 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 +63 -48
- package/dist/model/index.d.ts +63 -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/{profile-DLdlIpS9.d.ts → profile-B4WqcM4H.d.ts} +25 -17
- package/dist/{profile-CQMEqdH9.d.cts → profile-BJd1XkPB.d.cts} +25 -17
- 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/{types-D-XgxGvp.d.cts → types-7mGFwvMm.d.ts} +45 -22
- package/dist/{types-Dh7C064C.d.ts → types-_ofpb2be.d.cts} +45 -22
- package/package.json +13 -13
- package/dist/chunk-35NBV2FT.cjs.map +0 -1
- package/dist/chunk-3R3QVKY5.cjs.map +0 -1
- package/dist/chunk-4R2LWV7E.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-B2PW6EHN.cjs.map +0 -1
- package/dist/chunk-CGBADIO5.cjs.map +0 -1
- package/dist/chunk-CPMB7P75.js.map +0 -1
- package/dist/chunk-GNVN4PPJ.js.map +0 -1
- package/dist/chunk-IFZDWIKM.cjs.map +0 -1
- package/dist/chunk-IPEF6NWB.js.map +0 -1
- package/dist/chunk-M5RYMAFI.js.map +0 -1
- package/dist/chunk-MLBPXA4Z.js.map +0 -1
- package/dist/chunk-NB25P3EH.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-PWC4CRWT.cjs.map +0 -1
- package/dist/chunk-RFIBF2RR.js.map +0 -1
- package/dist/chunk-S5BKZZGM.cjs.map +0 -1
- package/dist/chunk-UMWZ4ZET.js.map +0 -1
- package/dist/chunk-YFA7X2F6.cjs.map +0 -1
- package/dist/chunk-ZBUROTJQ.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { b as MeterLike, T as TimeSignature, c as MeterMap } from './index-
|
|
2
|
-
import { c as ChordLike, K as KeyLike, i as ChordToneRole, r as SpelledKeyLike, b as Chord, n as ResolvedKey, j as KeyRelation, e as ChordSegment, V as VoiceSnapshot, g as ChordSpan } from './coerce-
|
|
3
|
-
import { h as NoteEvent, e as KeyScale, S as SpelledInterval } from './index-
|
|
1
|
+
import { b as MeterLike, T as TimeSignature, c as MeterMap } from './index-Bf0Sv8a9.js';
|
|
2
|
+
import { c as ChordLike, K as KeyLike, i as ChordToneRole, r as SpelledKeyLike, b as Chord, n as ResolvedKey, j as KeyRelation, e as ChordSegment, V as VoiceSnapshot, g as ChordSpan } from './coerce-CUhHc3Tu.js';
|
|
3
|
+
import { h as NoteEvent, e as KeyScale, S as SpelledInterval } from './index-C9MYbuKS.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The vocabulary the analyze layer explains itself in.
|
|
@@ -346,6 +346,8 @@ declare function detectCadence(from: ChordLike, to: ChordLike, key: KeyLike, opt
|
|
|
346
346
|
* @param text The Roman numeral.
|
|
347
347
|
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
348
348
|
* @returns The chord.
|
|
349
|
+
* @throws If `key` names no key, `text` is not a string, or it is no numeral
|
|
350
|
+
* this reads.
|
|
349
351
|
* @example
|
|
350
352
|
* ```ts
|
|
351
353
|
* import { romanToChord, majorKey } from '@libraz/libcantus';
|
|
@@ -368,7 +370,7 @@ type ChordToRomanOptions = {
|
|
|
368
370
|
* Off by default: naming the root is always a correct spelling, whereas
|
|
369
371
|
* whether a chromatic dominant is genuinely applied is a reading only the
|
|
370
372
|
* caller can make. Turning it on names the chords {@link secondaryDominant}
|
|
371
|
-
* builds as the applied numerals they are, except in
|
|
373
|
+
* builds as the applied numerals they are, except in four cases, where the
|
|
372
374
|
* numeral names the root against the home key as it does with this off:
|
|
373
375
|
*
|
|
374
376
|
* - The target degree carries no perfect fifth: nothing tonicizes a
|
|
@@ -379,6 +381,9 @@ type ChordToRomanOptions = {
|
|
|
379
381
|
* - The chord is diatonic to the key, which keeps the function of its degree
|
|
380
382
|
* rather than tonicizing another. `G7` is `VII7` in A minor and `I7` in C
|
|
381
383
|
* mixolydian, not `V7/III` and `V7/IV`.
|
|
384
|
+
* - The chord is borrowed from the parallel mode, which sounds its own degree:
|
|
385
|
+
* the Picardy tonic and the major IV of a minor key are `I` and `IV`, not
|
|
386
|
+
* `V/iv` and `V/VII`.
|
|
382
387
|
*/
|
|
383
388
|
applied?: boolean;
|
|
384
389
|
/**
|
|
@@ -659,13 +664,14 @@ declare function analyzeChord(chord: ChordLike, key: KeyLike, opts?: AnalyzeChor
|
|
|
659
664
|
* @param targetDegree 1-based scale degree to tonicize: 5 is the dominant, so
|
|
660
665
|
* `secondaryDominant(5, majorKey(0))` is the V of V.
|
|
661
666
|
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
662
|
-
* @returns A dominant-seventh chord a fifth above the target's root
|
|
667
|
+
* @returns A dominant-seventh chord a fifth above the target's root, spelled the
|
|
668
|
+
* way the key writes it (in Ab major the V of IV is Eb7, not D#7).
|
|
663
669
|
* @throws If `targetDegree` is not an integer naming a degree in `key`.
|
|
664
670
|
* @see {@link secondaryDominantOf} to tonicize a chord that has no degree in
|
|
665
671
|
* the key, or when no key is at hand.
|
|
666
672
|
* @category Functional Harmony
|
|
667
673
|
*/
|
|
668
|
-
declare function secondaryDominant(targetDegree: number, key:
|
|
674
|
+
declare function secondaryDominant(targetDegree: number, key: SpelledKeyLike): Chord;
|
|
669
675
|
/**
|
|
670
676
|
* The secondary dominant (V7) that tonicizes a chord.
|
|
671
677
|
*
|
|
@@ -733,7 +739,8 @@ type PivotChord = {
|
|
|
733
739
|
* so it may be any scale; its numerals then follow the {@link chordToRoman}
|
|
734
740
|
* fallback of naming roots against the parallel major.
|
|
735
741
|
*
|
|
736
|
-
* @param from The key being left, as a key name, a key/scale, or a `Key
|
|
742
|
+
* @param from The key being left, as a key name, a key/scale, or a `Key`; the
|
|
743
|
+
* shared chords are spelled the way it writes them.
|
|
737
744
|
* @param to The key being entered, in any of the same forms.
|
|
738
745
|
* @returns The shared triads, ascending by their degree in `from`.
|
|
739
746
|
* @example
|
|
@@ -744,7 +751,7 @@ type PivotChord = {
|
|
|
744
751
|
* ```
|
|
745
752
|
* @category Functional Harmony
|
|
746
753
|
*/
|
|
747
|
-
declare function pivotChords(from:
|
|
754
|
+
declare function pivotChords(from: SpelledKeyLike, to: SpelledKeyLike): PivotChord[];
|
|
748
755
|
|
|
749
756
|
/**
|
|
750
757
|
* The key as it changes over time.
|
|
@@ -1123,7 +1130,9 @@ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote
|
|
|
1123
1130
|
* onset are treated as simultaneous cluster members, not melodic neighbors, so
|
|
1124
1131
|
* they receive no melodic labels (suspension, passing, neighbor, anticipation,
|
|
1125
1132
|
* escape) — only harmonic ones. Callers with truly polyphonic material should
|
|
1126
|
-
* split it into monophonic sub-voices first, as `analyzeArrangement` does.
|
|
1133
|
+
* split it into monophonic sub-voices first, as `analyzeArrangement` does. A
|
|
1134
|
+
* note of zero length sounds nothing: it gets no melodic labels and is no other
|
|
1135
|
+
* note's neighbour.
|
|
1127
1136
|
*
|
|
1128
1137
|
* @param voice The monophonic voice, in time order. A note without an `id` is
|
|
1129
1138
|
* identified by its position, so plain note events can be passed straight in.
|
|
@@ -1132,7 +1141,8 @@ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote
|
|
|
1132
1141
|
* covering the whole voice, or a callback giving the key in force at a given
|
|
1133
1142
|
* beat, for music that modulates.
|
|
1134
1143
|
* @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
|
|
1135
|
-
* none, which is the whole story for a solo line
|
|
1144
|
+
* none, which is the whole story for a solo line: a suspension's dissonance
|
|
1145
|
+
* is then judged against the chord.
|
|
1136
1146
|
* @returns One annotation per input note.
|
|
1137
1147
|
* @example
|
|
1138
1148
|
* ```ts
|
|
@@ -1158,7 +1168,11 @@ declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: n
|
|
|
1158
1168
|
* @category Arrangement & Analysis
|
|
1159
1169
|
*/
|
|
1160
1170
|
type ChordTimeline = {
|
|
1161
|
-
/**
|
|
1171
|
+
/**
|
|
1172
|
+
* The chord sounding at a beat, or null when no segment covers it. An
|
|
1173
|
+
* inferred timeline covers a rest shorter than its harmonic rhythm with the
|
|
1174
|
+
* chord around it; see {@link chordTimelineFromNotes}.
|
|
1175
|
+
*/
|
|
1162
1176
|
at: (beat: number) => Chord | null;
|
|
1163
1177
|
segments: ChordSegment[];
|
|
1164
1178
|
};
|
|
@@ -1251,13 +1265,14 @@ type ChordTimelineOptions = {
|
|
|
1251
1265
|
* The first downbeat is beat 0 whatever precedes it, so an upbeat is written
|
|
1252
1266
|
* at negative beats — a one-beat pickup in 4/4 sounds at beat -1. Declaring
|
|
1253
1267
|
* its length rejects a note that starts before the pickup does; leave it
|
|
1254
|
-
* unset to accept any
|
|
1268
|
+
* unset to accept any onset within ±`Number.MAX_SAFE_INTEGER` (2^53 - 1).
|
|
1255
1269
|
*
|
|
1256
1270
|
* @defaultValue no declared pickup
|
|
1257
1271
|
*/
|
|
1258
1272
|
pickupBeats?: number;
|
|
1259
1273
|
/**
|
|
1260
|
-
* Where chord boundaries may fall; defaults to `'dynamic'`.
|
|
1274
|
+
* Where chord boundaries may fall; defaults to `'dynamic'`. Any other value
|
|
1275
|
+
* is an input error.
|
|
1261
1276
|
*
|
|
1262
1277
|
* @defaultValue `'dynamic'`
|
|
1263
1278
|
*/
|
|
@@ -1348,8 +1363,13 @@ type ChordTimelineResult = {
|
|
|
1348
1363
|
* Each settled segment's chord is inferred from a pitch-class weight histogram
|
|
1349
1364
|
* of the notes overlapping it (weight = overlap duration x velocity x
|
|
1350
1365
|
* metric-accent bonus for onsets in the segment). Adjacent segments carrying
|
|
1351
|
-
* the identical chord are merged
|
|
1352
|
-
* so `at(beat)` returns null
|
|
1366
|
+
* the identical chord are merged. Under `'dynamic'` segmentation a silence at
|
|
1367
|
+
* least `harmonicRhythm` long produces no segment, so `at(beat)` returns null
|
|
1368
|
+
* there, while a shorter rest is absorbed into the chord around it — a C triad
|
|
1369
|
+
* for two beats, two beats of rest and a G triad read C over beats 0-4. Under `'grid'` any
|
|
1370
|
+
* window with nothing sounding produces no segment. A window whose notes give
|
|
1371
|
+
* fewer than two tones of any chord produces none either: a lone pitch class
|
|
1372
|
+
* names no chord. Each segment carries a confidence in [0, 1]
|
|
1353
1373
|
* — the fraction of its weight explained by chord tones, reduced when the match
|
|
1354
1374
|
* is inexact, and duration-weighted across anything merged into it.
|
|
1355
1375
|
*
|
|
@@ -1367,7 +1387,8 @@ type ChordTimelineResult = {
|
|
|
1367
1387
|
* @param opts Analysis options; see {@link ChordTimelineOptions}.
|
|
1368
1388
|
* @returns The inferred timeline, the key regions it ran against, the key that
|
|
1369
1389
|
* prevails across them, and per-segment confidences.
|
|
1370
|
-
* @throws If `harmonicRhythm` is not positive
|
|
1390
|
+
* @throws If `harmonicRhythm` is not positive, or `segmentation` names neither
|
|
1391
|
+
* mode.
|
|
1371
1392
|
* @example
|
|
1372
1393
|
* ```ts
|
|
1373
1394
|
* import { chordTimelineFromNotes } from '@libraz/libcantus';
|
|
@@ -1422,10 +1443,9 @@ type CadenceHit = {
|
|
|
1422
1443
|
* @example
|
|
1423
1444
|
* ```ts
|
|
1424
1445
|
* import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
|
|
1425
|
-
* const
|
|
1426
|
-
* { pitch
|
|
1427
|
-
*
|
|
1428
|
-
* ];
|
|
1446
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
1447
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
1448
|
+
* const notes = [...triad([55, 59, 62], 0), ...triad([48, 52, 55], 4)]; // G, then C
|
|
1429
1449
|
* const { timeline, prevailingKey } = chordTimelineFromNotes(notes);
|
|
1430
1450
|
* const cadences = detectCadences(timeline, prevailingKey);
|
|
1431
1451
|
* ```
|
|
@@ -1921,7 +1941,9 @@ type MotifRelationSummary = Pick<MotifRelation, 'kind' | 'sequence' | 'semitones
|
|
|
1921
1941
|
* them does, so a statement a fourth higher in doubled note values is
|
|
1922
1942
|
* recognised for what it is, and a cell that reads as both an inversion and a
|
|
1923
1943
|
* retrograde is named by the way its rhythm runs rather than by which name came
|
|
1924
|
-
* to hand first.
|
|
1944
|
+
* to hand first. An evenly timed cell runs the same both ways, so there the
|
|
1945
|
+
* reading that needs no transposition wins: a cell played backwards on its own
|
|
1946
|
+
* pitches is a retrograde. A pair whose rhythms correspond in neither direction — the same
|
|
1925
1947
|
* way round for a repetition or a transposition, the model played backwards for
|
|
1926
1948
|
* a retrograde, note values and all — is not a transformation of the motif but a
|
|
1927
1949
|
* different figure, and answers null; {@link melodicSimilarity} is what scores
|
|
@@ -2573,4 +2595,5 @@ type ReferenceMotif = {
|
|
|
2573
2595
|
occurrences: number;
|
|
2574
2596
|
};
|
|
2575
2597
|
|
|
2576
|
-
export {
|
|
2598
|
+
export { CADENCE_TYPES as C, HARMONIC_FUNCTIONS as H, MELODIC_CONTOUR_SHAPES as M, REDUCTION_LEVELS as R, REFERENCE_PROFILE_VERSION as S, RHYTHM_IOI_BINS as T, reduceProgression as aA, relateMotifs as aB, romanToChord as aC, secondaryDominant as aD, secondaryDominantOf as aE, sectionsFromNotes as aF, toVoiceNotes as aG, analyzeChord as ad, analyzeRhythm as ae, analyzeVoice as af, assertChordTimeline as ag, borrowedSource as ah, chordTimelineFromChords as ai, chordTimelineFromNotes as aj, chordToRoman as ak, detectCadence as al, detectCadences as am, detectModulations as an, explainRoman as ao, extractMotifs as ap, functionOf as aq, isBorrowedChord as ar, isDiatonic as as, isMinorKey as at, keyTimelineFromNotes as au, melodicContour as av, motifFromNotes as aw, motifGraph as ax, pivotChords as ay, prevailingKeyOf as az, MOTIF_RELATION_KINDS as t };
|
|
2599
|
+
export type { ReferenceHarmony as $, AnalyzeChordOptions as A, BarPositionProfile as B, DetectCadenceOptions as D, ExplainRomanOptions as E, FormSection as F, MotifGraphNode as G, IdentifiedVoiceNote as I, MotifGraphOptions as J, KeyContext as K, MotifOccurrence as L, MotifRelation as N, MotifRelationKind as O, MotifRelationSummary as P, PivotChord as Q, ReduceProgressionOptions as U, ReducedChord as V, ReductionBasis as W, ReductionLevel as X, ReferenceCadence as Y, ReferenceChord as Z, ReferenceForm as _, AnalyzedNote as a, ReferenceKeyRegion as a0, ReferenceMelody as a1, ReferenceMotif as a2, ReferencePhrase as a3, ReferencePhraseMelody as a4, ReferenceProfile as a5, RejectedCandidate as a6, RhythmAnalysis as a7, RhythmAnalysisOptions as a8, RomanExplanation as a9, SuspensionFigure as aa, TheoryLabel as ab, VoiceNote as ac, BorrowedSource as b, CadenceHit as c, CadenceResult as d, CadenceType as e, ChordAnalysis as f, ChordSegmentation as g, ChordTimeline as h, ChordTimelineOptions as i, ChordTimelineResult as j, ChordToRomanOptions as k, ContourDirection as l, ExtractMotifsOptions as m, FormSectionOptions as n, HarmonicFunction as o, KeyProfileName as p, KeyProfilePair as q, KeyRegion as r, KeyTimelineOptions as s, MelodicContour as u, MelodicContourShape as v, MelodicPhrase as w, MotifData as x, MotifGraph as y, MotifGraphEdge as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { b as MeterLike, T as TimeSignature, c as MeterMap } from './index-
|
|
2
|
-
import { c as ChordLike, K as KeyLike, i as ChordToneRole, r as SpelledKeyLike, b as Chord, n as ResolvedKey, j as KeyRelation, e as ChordSegment, V as VoiceSnapshot, g as ChordSpan } from './coerce-
|
|
3
|
-
import { h as NoteEvent, e as KeyScale, S as SpelledInterval } from './index-
|
|
1
|
+
import { b as MeterLike, T as TimeSignature, c as MeterMap } from './index-5VJgKz_Q.cjs';
|
|
2
|
+
import { c as ChordLike, K as KeyLike, i as ChordToneRole, r as SpelledKeyLike, b as Chord, n as ResolvedKey, j as KeyRelation, e as ChordSegment, V as VoiceSnapshot, g as ChordSpan } from './coerce-Ytt2Eiq9.cjs';
|
|
3
|
+
import { h as NoteEvent, e as KeyScale, S as SpelledInterval } from './index-C9MYbuKS.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The vocabulary the analyze layer explains itself in.
|
|
@@ -346,6 +346,8 @@ declare function detectCadence(from: ChordLike, to: ChordLike, key: KeyLike, opt
|
|
|
346
346
|
* @param text The Roman numeral.
|
|
347
347
|
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
348
348
|
* @returns The chord.
|
|
349
|
+
* @throws If `key` names no key, `text` is not a string, or it is no numeral
|
|
350
|
+
* this reads.
|
|
349
351
|
* @example
|
|
350
352
|
* ```ts
|
|
351
353
|
* import { romanToChord, majorKey } from '@libraz/libcantus';
|
|
@@ -368,7 +370,7 @@ type ChordToRomanOptions = {
|
|
|
368
370
|
* Off by default: naming the root is always a correct spelling, whereas
|
|
369
371
|
* whether a chromatic dominant is genuinely applied is a reading only the
|
|
370
372
|
* caller can make. Turning it on names the chords {@link secondaryDominant}
|
|
371
|
-
* builds as the applied numerals they are, except in
|
|
373
|
+
* builds as the applied numerals they are, except in four cases, where the
|
|
372
374
|
* numeral names the root against the home key as it does with this off:
|
|
373
375
|
*
|
|
374
376
|
* - The target degree carries no perfect fifth: nothing tonicizes a
|
|
@@ -379,6 +381,9 @@ type ChordToRomanOptions = {
|
|
|
379
381
|
* - The chord is diatonic to the key, which keeps the function of its degree
|
|
380
382
|
* rather than tonicizing another. `G7` is `VII7` in A minor and `I7` in C
|
|
381
383
|
* mixolydian, not `V7/III` and `V7/IV`.
|
|
384
|
+
* - The chord is borrowed from the parallel mode, which sounds its own degree:
|
|
385
|
+
* the Picardy tonic and the major IV of a minor key are `I` and `IV`, not
|
|
386
|
+
* `V/iv` and `V/VII`.
|
|
382
387
|
*/
|
|
383
388
|
applied?: boolean;
|
|
384
389
|
/**
|
|
@@ -659,13 +664,14 @@ declare function analyzeChord(chord: ChordLike, key: KeyLike, opts?: AnalyzeChor
|
|
|
659
664
|
* @param targetDegree 1-based scale degree to tonicize: 5 is the dominant, so
|
|
660
665
|
* `secondaryDominant(5, majorKey(0))` is the V of V.
|
|
661
666
|
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
662
|
-
* @returns A dominant-seventh chord a fifth above the target's root
|
|
667
|
+
* @returns A dominant-seventh chord a fifth above the target's root, spelled the
|
|
668
|
+
* way the key writes it (in Ab major the V of IV is Eb7, not D#7).
|
|
663
669
|
* @throws If `targetDegree` is not an integer naming a degree in `key`.
|
|
664
670
|
* @see {@link secondaryDominantOf} to tonicize a chord that has no degree in
|
|
665
671
|
* the key, or when no key is at hand.
|
|
666
672
|
* @category Functional Harmony
|
|
667
673
|
*/
|
|
668
|
-
declare function secondaryDominant(targetDegree: number, key:
|
|
674
|
+
declare function secondaryDominant(targetDegree: number, key: SpelledKeyLike): Chord;
|
|
669
675
|
/**
|
|
670
676
|
* The secondary dominant (V7) that tonicizes a chord.
|
|
671
677
|
*
|
|
@@ -733,7 +739,8 @@ type PivotChord = {
|
|
|
733
739
|
* so it may be any scale; its numerals then follow the {@link chordToRoman}
|
|
734
740
|
* fallback of naming roots against the parallel major.
|
|
735
741
|
*
|
|
736
|
-
* @param from The key being left, as a key name, a key/scale, or a `Key
|
|
742
|
+
* @param from The key being left, as a key name, a key/scale, or a `Key`; the
|
|
743
|
+
* shared chords are spelled the way it writes them.
|
|
737
744
|
* @param to The key being entered, in any of the same forms.
|
|
738
745
|
* @returns The shared triads, ascending by their degree in `from`.
|
|
739
746
|
* @example
|
|
@@ -744,7 +751,7 @@ type PivotChord = {
|
|
|
744
751
|
* ```
|
|
745
752
|
* @category Functional Harmony
|
|
746
753
|
*/
|
|
747
|
-
declare function pivotChords(from:
|
|
754
|
+
declare function pivotChords(from: SpelledKeyLike, to: SpelledKeyLike): PivotChord[];
|
|
748
755
|
|
|
749
756
|
/**
|
|
750
757
|
* The key as it changes over time.
|
|
@@ -1123,7 +1130,9 @@ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote
|
|
|
1123
1130
|
* onset are treated as simultaneous cluster members, not melodic neighbors, so
|
|
1124
1131
|
* they receive no melodic labels (suspension, passing, neighbor, anticipation,
|
|
1125
1132
|
* escape) — only harmonic ones. Callers with truly polyphonic material should
|
|
1126
|
-
* split it into monophonic sub-voices first, as `analyzeArrangement` does.
|
|
1133
|
+
* split it into monophonic sub-voices first, as `analyzeArrangement` does. A
|
|
1134
|
+
* note of zero length sounds nothing: it gets no melodic labels and is no other
|
|
1135
|
+
* note's neighbour.
|
|
1127
1136
|
*
|
|
1128
1137
|
* @param voice The monophonic voice, in time order. A note without an `id` is
|
|
1129
1138
|
* identified by its position, so plain note events can be passed straight in.
|
|
@@ -1132,7 +1141,8 @@ declare function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote
|
|
|
1132
1141
|
* covering the whole voice, or a callback giving the key in force at a given
|
|
1133
1142
|
* beat, for music that modulates.
|
|
1134
1143
|
* @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to
|
|
1135
|
-
* none, which is the whole story for a solo line
|
|
1144
|
+
* none, which is the whole story for a solo line: a suspension's dissonance
|
|
1145
|
+
* is then judged against the chord.
|
|
1136
1146
|
* @returns One annotation per input note.
|
|
1137
1147
|
* @example
|
|
1138
1148
|
* ```ts
|
|
@@ -1158,7 +1168,11 @@ declare function analyzeVoice(voice: readonly VoiceNote[], chordAtBeat: (beat: n
|
|
|
1158
1168
|
* @category Arrangement & Analysis
|
|
1159
1169
|
*/
|
|
1160
1170
|
type ChordTimeline = {
|
|
1161
|
-
/**
|
|
1171
|
+
/**
|
|
1172
|
+
* The chord sounding at a beat, or null when no segment covers it. An
|
|
1173
|
+
* inferred timeline covers a rest shorter than its harmonic rhythm with the
|
|
1174
|
+
* chord around it; see {@link chordTimelineFromNotes}.
|
|
1175
|
+
*/
|
|
1162
1176
|
at: (beat: number) => Chord | null;
|
|
1163
1177
|
segments: ChordSegment[];
|
|
1164
1178
|
};
|
|
@@ -1251,13 +1265,14 @@ type ChordTimelineOptions = {
|
|
|
1251
1265
|
* The first downbeat is beat 0 whatever precedes it, so an upbeat is written
|
|
1252
1266
|
* at negative beats — a one-beat pickup in 4/4 sounds at beat -1. Declaring
|
|
1253
1267
|
* its length rejects a note that starts before the pickup does; leave it
|
|
1254
|
-
* unset to accept any
|
|
1268
|
+
* unset to accept any onset within ±`Number.MAX_SAFE_INTEGER` (2^53 - 1).
|
|
1255
1269
|
*
|
|
1256
1270
|
* @defaultValue no declared pickup
|
|
1257
1271
|
*/
|
|
1258
1272
|
pickupBeats?: number;
|
|
1259
1273
|
/**
|
|
1260
|
-
* Where chord boundaries may fall; defaults to `'dynamic'`.
|
|
1274
|
+
* Where chord boundaries may fall; defaults to `'dynamic'`. Any other value
|
|
1275
|
+
* is an input error.
|
|
1261
1276
|
*
|
|
1262
1277
|
* @defaultValue `'dynamic'`
|
|
1263
1278
|
*/
|
|
@@ -1348,8 +1363,13 @@ type ChordTimelineResult = {
|
|
|
1348
1363
|
* Each settled segment's chord is inferred from a pitch-class weight histogram
|
|
1349
1364
|
* of the notes overlapping it (weight = overlap duration x velocity x
|
|
1350
1365
|
* metric-accent bonus for onsets in the segment). Adjacent segments carrying
|
|
1351
|
-
* the identical chord are merged
|
|
1352
|
-
* so `at(beat)` returns null
|
|
1366
|
+
* the identical chord are merged. Under `'dynamic'` segmentation a silence at
|
|
1367
|
+
* least `harmonicRhythm` long produces no segment, so `at(beat)` returns null
|
|
1368
|
+
* there, while a shorter rest is absorbed into the chord around it — a C triad
|
|
1369
|
+
* for two beats, two beats of rest and a G triad read C over beats 0-4. Under `'grid'` any
|
|
1370
|
+
* window with nothing sounding produces no segment. A window whose notes give
|
|
1371
|
+
* fewer than two tones of any chord produces none either: a lone pitch class
|
|
1372
|
+
* names no chord. Each segment carries a confidence in [0, 1]
|
|
1353
1373
|
* — the fraction of its weight explained by chord tones, reduced when the match
|
|
1354
1374
|
* is inexact, and duration-weighted across anything merged into it.
|
|
1355
1375
|
*
|
|
@@ -1367,7 +1387,8 @@ type ChordTimelineResult = {
|
|
|
1367
1387
|
* @param opts Analysis options; see {@link ChordTimelineOptions}.
|
|
1368
1388
|
* @returns The inferred timeline, the key regions it ran against, the key that
|
|
1369
1389
|
* prevails across them, and per-segment confidences.
|
|
1370
|
-
* @throws If `harmonicRhythm` is not positive
|
|
1390
|
+
* @throws If `harmonicRhythm` is not positive, or `segmentation` names neither
|
|
1391
|
+
* mode.
|
|
1371
1392
|
* @example
|
|
1372
1393
|
* ```ts
|
|
1373
1394
|
* import { chordTimelineFromNotes } from '@libraz/libcantus';
|
|
@@ -1422,10 +1443,9 @@ type CadenceHit = {
|
|
|
1422
1443
|
* @example
|
|
1423
1444
|
* ```ts
|
|
1424
1445
|
* import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';
|
|
1425
|
-
* const
|
|
1426
|
-
* { pitch
|
|
1427
|
-
*
|
|
1428
|
-
* ];
|
|
1446
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
1447
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
1448
|
+
* const notes = [...triad([55, 59, 62], 0), ...triad([48, 52, 55], 4)]; // G, then C
|
|
1429
1449
|
* const { timeline, prevailingKey } = chordTimelineFromNotes(notes);
|
|
1430
1450
|
* const cadences = detectCadences(timeline, prevailingKey);
|
|
1431
1451
|
* ```
|
|
@@ -1921,7 +1941,9 @@ type MotifRelationSummary = Pick<MotifRelation, 'kind' | 'sequence' | 'semitones
|
|
|
1921
1941
|
* them does, so a statement a fourth higher in doubled note values is
|
|
1922
1942
|
* recognised for what it is, and a cell that reads as both an inversion and a
|
|
1923
1943
|
* retrograde is named by the way its rhythm runs rather than by which name came
|
|
1924
|
-
* to hand first.
|
|
1944
|
+
* to hand first. An evenly timed cell runs the same both ways, so there the
|
|
1945
|
+
* reading that needs no transposition wins: a cell played backwards on its own
|
|
1946
|
+
* pitches is a retrograde. A pair whose rhythms correspond in neither direction — the same
|
|
1925
1947
|
* way round for a repetition or a transposition, the model played backwards for
|
|
1926
1948
|
* a retrograde, note values and all — is not a transformation of the motif but a
|
|
1927
1949
|
* different figure, and answers null; {@link melodicSimilarity} is what scores
|
|
@@ -2573,4 +2595,5 @@ type ReferenceMotif = {
|
|
|
2573
2595
|
occurrences: number;
|
|
2574
2596
|
};
|
|
2575
2597
|
|
|
2576
|
-
export {
|
|
2598
|
+
export { CADENCE_TYPES as C, HARMONIC_FUNCTIONS as H, MELODIC_CONTOUR_SHAPES as M, REDUCTION_LEVELS as R, REFERENCE_PROFILE_VERSION as S, RHYTHM_IOI_BINS as T, reduceProgression as aA, relateMotifs as aB, romanToChord as aC, secondaryDominant as aD, secondaryDominantOf as aE, sectionsFromNotes as aF, toVoiceNotes as aG, analyzeChord as ad, analyzeRhythm as ae, analyzeVoice as af, assertChordTimeline as ag, borrowedSource as ah, chordTimelineFromChords as ai, chordTimelineFromNotes as aj, chordToRoman as ak, detectCadence as al, detectCadences as am, detectModulations as an, explainRoman as ao, extractMotifs as ap, functionOf as aq, isBorrowedChord as ar, isDiatonic as as, isMinorKey as at, keyTimelineFromNotes as au, melodicContour as av, motifFromNotes as aw, motifGraph as ax, pivotChords as ay, prevailingKeyOf as az, MOTIF_RELATION_KINDS as t };
|
|
2599
|
+
export type { ReferenceHarmony as $, AnalyzeChordOptions as A, BarPositionProfile as B, DetectCadenceOptions as D, ExplainRomanOptions as E, FormSection as F, MotifGraphNode as G, IdentifiedVoiceNote as I, MotifGraphOptions as J, KeyContext as K, MotifOccurrence as L, MotifRelation as N, MotifRelationKind as O, MotifRelationSummary as P, PivotChord as Q, ReduceProgressionOptions as U, ReducedChord as V, ReductionBasis as W, ReductionLevel as X, ReferenceCadence as Y, ReferenceChord as Z, ReferenceForm as _, AnalyzedNote as a, ReferenceKeyRegion as a0, ReferenceMelody as a1, ReferenceMotif as a2, ReferencePhrase as a3, ReferencePhraseMelody as a4, ReferenceProfile as a5, RejectedCandidate as a6, RhythmAnalysis as a7, RhythmAnalysisOptions as a8, RomanExplanation as a9, SuspensionFigure as aa, TheoryLabel as ab, VoiceNote as ac, BorrowedSource as b, CadenceHit as c, CadenceResult as d, CadenceType as e, ChordAnalysis as f, ChordSegmentation as g, ChordTimeline as h, ChordTimelineOptions as i, ChordTimelineResult as j, ChordToRomanOptions as k, ContourDirection as l, ExtractMotifsOptions as m, FormSectionOptions as n, HarmonicFunction as o, KeyProfileName as p, KeyProfilePair as q, KeyRegion as r, KeyTimelineOptions as s, MelodicContour as u, MelodicContourShape as v, MelodicPhrase as w, MotifData as x, MotifGraph as y, MotifGraphEdge as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libraz/libcantus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Pure-TypeScript music theory engine: harmonic analysis, chord and scale vocabulary, voicing, reharmonization, and seeded part generation over MIDI note events.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"engines": {
|
|
32
32
|
"node": ">=22"
|
|
33
33
|
},
|
|
34
|
-
"packageManager": "yarn@4.18.
|
|
34
|
+
"packageManager": "yarn@4.18.1",
|
|
35
35
|
"main": "./dist/index.cjs",
|
|
36
36
|
"module": "./dist/index.js",
|
|
37
37
|
"types": "./dist/index.d.ts",
|
|
@@ -121,7 +121,8 @@
|
|
|
121
121
|
"dist",
|
|
122
122
|
"README.md",
|
|
123
123
|
"README_ja.md",
|
|
124
|
-
"LICENSE"
|
|
124
|
+
"LICENSE",
|
|
125
|
+
"NOTICE"
|
|
125
126
|
],
|
|
126
127
|
"sideEffects": false,
|
|
127
128
|
"scripts": {
|
|
@@ -130,28 +131,27 @@
|
|
|
130
131
|
"lint": "biome check .",
|
|
131
132
|
"lint:fix": "biome check --write .",
|
|
132
133
|
"format": "biome format --write .",
|
|
133
|
-
"examples": "vitest list >/dev/null",
|
|
134
|
-
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json && yarn examples && tsc -p tsconfig.examples.json",
|
|
134
|
+
"examples": "vitest list -c ../tests/spec/vitest.config.ts >/dev/null",
|
|
135
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json && yarn examples && tsc -p ../tests/spec/tsconfig.examples.json",
|
|
135
136
|
"test": "vitest run",
|
|
136
137
|
"test:watch": "vitest",
|
|
137
|
-
"test:coverage": "vitest run --coverage",
|
|
138
|
+
"test:coverage": "vitest run --coverage -c vitest.coverage.config.ts",
|
|
138
139
|
"bench:timeline": "yarn build && node --expose-gc benchmark/timeline-index.mjs",
|
|
139
140
|
"docs": "typedoc",
|
|
140
141
|
"figures": "node scripts/figures/build.mjs",
|
|
141
|
-
"prepare": "[ -d node_modules/typescript ] && tsup || true",
|
|
142
|
+
"prepare": "[ -d node_modules/typescript ] || [ -d ../node_modules/typescript ] && tsup || true",
|
|
142
143
|
"prepublishOnly": "yarn build"
|
|
143
144
|
},
|
|
144
145
|
"devDependencies": {
|
|
145
|
-
"@biomejs/biome": "2.5.14",
|
|
146
146
|
"@libraz/typedoc-theme": "^0.1.1",
|
|
147
|
-
"@types/node": "^26.6.
|
|
148
|
-
"@vitest/coverage-v8": "5.0.
|
|
149
|
-
"tsup": "
|
|
147
|
+
"@types/node": "^26.6.6",
|
|
148
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
149
|
+
"tsup": "9.0.0",
|
|
150
150
|
"typedoc": "0.28.20",
|
|
151
151
|
"typedoc-plugin-extras": "4.0.1",
|
|
152
152
|
"typedoc-plugin-mdn-links": "5.1.1",
|
|
153
153
|
"typescript": "6.0.3",
|
|
154
|
-
"vite": "8.3.
|
|
155
|
-
"vitest": "5.0.
|
|
154
|
+
"vite": "8.3.5",
|
|
155
|
+
"vitest": "5.0.5"
|
|
156
156
|
}
|
|
157
157
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-35NBV2FT.cjs","../src/core/event-index/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-35NBV2FT.cjs","sourcesContent":[null,"import { BEAT_EPS } from '../meter/internal.js';\nimport type { NoteEvent } from '../types.js';\nimport { assertFiniteNumber, assertNoteEvents, assertOptions } from '../validation/index.js';\n\n/**\n * A validated note retaining its position in the caller's original array.\n *\n * @category Core\n */\nexport type IndexedNoteEvent = {\n readonly note: Readonly<NoteEvent>;\n readonly originalIndex: number;\n readonly endBeat: number;\n};\n\n/**\n * Which note wins when several sound from the same onset.\n *\n * `'highest'` and `'lowest'` name a voice, so the answer does not depend on the\n * order the caller happened to store the chord in; `'last'` takes the one\n * latest in the input array. Two notes of the same pitch name no voice apart,\n * so both voice tie-breaks settle a unison on the lower `originalIndex` and\n * name the same note.\n *\n * @category Core\n */\nexport type OnsetTieBreak = 'highest' | 'lowest' | 'last';\n\n/**\n * Binary-searchable, stable-sorted index over note onsets and active spans.\n *\n * @category Core\n */\nexport type NoteEventIndex = {\n notes: readonly IndexedNoteEvent[];\n /**\n * Latest-onset note sounding at `beat`. Simultaneous onsets are resolved by\n * the index's {@link OnsetTieBreak}.\n *\n * @throws If `beat` is not finite.\n */\n at: (beat: number) => IndexedNoteEvent | undefined;\n /**\n * Whether one or more sounding notes attack at `beat`.\n *\n * @throws If `beat` is not finite.\n */\n attacksAt: (beat: number) => boolean;\n /**\n * Unique sounding-note attack beats strictly inside `(startBeat, endBeat)`.\n *\n * @throws If either bound is not finite.\n */\n onsetsBetween: (startBeat: number, endBeat: number) => number[];\n};\n\nfunction upperBound(values: readonly IndexedNoteEvent[], beat: number): number {\n let low = 0;\n let high = values.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((values[middle]?.note.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * Copy note events and put them in stable onset order.\n *\n * The order is the one {@link createNoteEventIndex} numbers its notes in, so a\n * pass may read the sequence here and the index there and see the same notes in\n * the same places. What it does not build is the index itself: a caller that\n * only walks the notes in time order would otherwise pay for a segment tree and\n * a wrapper per note that it never queries.\n *\n * The events are copied, so the sequence is the caller's own and two entries\n * that arrived as one shared object stay two.\n *\n * @param events The events to order; already validated by the caller.\n * @returns The copies, earliest onset first, ties in input order.\n */\nexport function sortedNoteEvents(events: readonly NoteEvent[]): NoteEvent[] {\n const sorted = events.map((note) => ({ ...note }));\n // Array sort is stable, so events sharing an onset keep the order they were\n // given in — the tie-break the index spells out as `originalIndex`.\n sorted.sort((a, b) => a.startBeat - b.startBeat);\n return sorted;\n}\n\n/**\n * How an index reads the events it is built from.\n *\n * @category Core\n */\nexport type NoteEventIndexOptions = {\n allowNonPositiveDuration?: boolean;\n tieBreak?: OnsetTieBreak;\n budget?: number;\n /** What the events are, for the validation error message. */\n name?: string;\n};\n\n/**\n * Validate and stable-sort note events once, then expose onset and active-note\n * lookups that cost `O(log n + k)`, where `k` is how many notes share the onset\n * the answer comes from. Non-positive-duration notes may be retained for\n * callers that intentionally filter them later, but never count as sounding.\n *\n * @category Core\n */\nexport function createNoteEventIndex(\n events: readonly NoteEvent[],\n options: NoteEventIndexOptions = {},\n): NoteEventIndex {\n const asked = assertOptions(options, 'options');\n assertNoteEvents(events, asked.name ?? 'note events', asked);\n const tieBreak = asked.tieBreak ?? 'highest';\n // Each wrapper is frozen, not only the array and the note inside it: `endBeat`\n // is what the active-note search is built from, so a host that writes to it\n // would leave the index answering from a span nothing else knows about.\n const notes = Object.freeze(\n events\n .map((note, originalIndex) =>\n Object.freeze({\n note: Object.freeze({ ...note }),\n originalIndex,\n endBeat: note.startBeat + note.durationBeat,\n }),\n )\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.originalIndex - b.originalIndex),\n );\n // Segment-tree maxima find the latest active onset in logarithmic time even\n // when an early, very long note would defeat a prefix-max backwards scan.\n //\n // It is built on the first `at` query rather than on construction: the onset\n // queries answer from the sorted notes alone, so a caller that never asks\n // which note is sounding would otherwise pay for a structure it never reads.\n let maxEndTree: number[] | undefined;\n let treeSize = 0;\n const endTree = (): number[] => {\n if (maxEndTree !== undefined) {\n return maxEndTree;\n }\n treeSize = 1;\n while (treeSize < notes.length) treeSize *= 2;\n const tree = new Array<number>(treeSize * 2).fill(Number.NEGATIVE_INFINITY);\n for (let index = 0; index < notes.length; index += 1) {\n tree[treeSize + index] = notes[index]?.endBeat ?? Number.NEGATIVE_INFINITY;\n }\n for (let index = treeSize - 1; index > 0; index -= 1) {\n tree[index] = Math.max(\n tree[index * 2] ?? Number.NEGATIVE_INFINITY,\n tree[index * 2 + 1] ?? Number.NEGATIVE_INFINITY,\n );\n }\n maxEndTree = tree;\n return tree;\n };\n const latestActiveIndex = (exclusive: number, beat: number): number => {\n const tree = endTree();\n const find = (node: number, start: number, end: number): number => {\n if (start >= exclusive || (tree[node] ?? Number.NEGATIVE_INFINITY) <= beat + BEAT_EPS) {\n return -1;\n }\n if (end - start === 1) return start < notes.length ? start : -1;\n const mid = Math.floor((start + end) / 2);\n const right = find(node * 2 + 1, mid, end);\n return right >= 0 ? right : find(node * 2, start, mid);\n };\n return find(1, 0, treeSize);\n };\n\n return {\n notes,\n at(beat) {\n // A query beat comes from wherever the host got it — a scrub position, a\n // parsed field — so it is checked like any other entry argument. `NaN`\n // compares false against every bound, which would read as an open window\n // rather than as the missing value it is.\n assertFiniteNumber(beat, 'beat');\n const index = latestActiveIndex(upperBound(notes, beat + BEAT_EPS), beat);\n if (index < 0) return undefined;\n const onset = notes[index]?.note.startBeat;\n let best: IndexedNoteEvent | undefined;\n for (let candidateIndex = index; candidateIndex >= 0; candidateIndex -= 1) {\n const indexed = notes[candidateIndex];\n if (indexed !== undefined && indexed.note.startBeat !== onset) break;\n if (\n indexed === undefined ||\n indexed.note.durationBeat <= 0 ||\n indexed.note.startBeat - BEAT_EPS > beat ||\n beat >= indexed.endBeat - BEAT_EPS\n ) {\n continue;\n }\n if (best === undefined) {\n best = indexed;\n continue;\n }\n if (tieBreak === 'last') continue;\n const difference = indexed.note.pitch - best.note.pitch;\n // Equal pitches name no voice apart, so they are settled on the lower\n // original index rather than on whichever end of the array the scan\n // happens to reach last — otherwise `'highest'` and `'lowest'` would\n // answer a unison differently.\n const wins =\n difference === 0\n ? indexed.originalIndex < best.originalIndex\n : difference > 0 === (tieBreak === 'highest');\n if (wins) {\n best = indexed;\n }\n }\n return best;\n },\n attacksAt(beat) {\n assertFiniteNumber(beat, 'beat');\n // Only a sounding note attacks: a zero-length artefact must not make\n // `attacksAt(b)` true on a beat where `at(b)` finds nothing.\n let index = upperBound(notes, beat + BEAT_EPS) - 1;\n while (index >= 0) {\n const candidate = notes[index];\n if (candidate === undefined || Math.abs(candidate.note.startBeat - beat) >= BEAT_EPS) {\n return false;\n }\n if (candidate.note.durationBeat > 0) {\n return true;\n }\n index -= 1;\n }\n return false;\n },\n onsetsBetween(startBeat, endBeat) {\n assertFiniteNumber(startBeat, 'startBeat');\n assertFiniteNumber(endBeat, 'endBeat');\n const result: number[] = [];\n let index = upperBound(notes, startBeat + BEAT_EPS);\n while (index < notes.length) {\n const indexed = notes[index];\n const onset = indexed?.note.startBeat;\n if (indexed === undefined || onset === undefined || onset >= endBeat - BEAT_EPS) {\n break;\n }\n index += 1;\n if (indexed.note.durationBeat <= 0) {\n continue;\n }\n const previous = result[result.length - 1];\n if (previous === undefined || Math.abs(previous - onset) >= BEAT_EPS) {\n result.push(onset);\n }\n }\n return result;\n },\n };\n}\n"]}
|