@libraz/libcantus 1.0.1 → 1.1.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/README.md +27 -65
- package/README_ja.md +28 -66
- package/dist/analyze/index.cjs +11 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +79 -144
- package/dist/analyze/index.d.ts +79 -144
- package/dist/analyze/index.js +20 -16
- package/dist/{chunk-TTCLJX6D.js → chunk-57WB2EDF.js} +10 -8
- package/dist/chunk-57WB2EDF.js.map +1 -0
- package/dist/chunk-5JJ6EHEZ.cjs +457 -0
- package/dist/chunk-5JJ6EHEZ.cjs.map +1 -0
- package/dist/{chunk-AFM2ULJH.cjs → chunk-5KW7DNCJ.cjs} +1567 -1067
- package/dist/chunk-5KW7DNCJ.cjs.map +1 -0
- package/dist/chunk-7GOKQJTQ.js +145 -0
- package/dist/chunk-7GOKQJTQ.js.map +1 -0
- package/dist/{chunk-H3VORETI.js → chunk-AWEMD747.js} +487 -592
- package/dist/chunk-AWEMD747.js.map +1 -0
- package/dist/{chunk-PKFNUHDY.js → chunk-DKICR7AN.js} +1216 -611
- package/dist/chunk-DKICR7AN.js.map +1 -0
- package/dist/{chunk-INHVXP6Z.js → chunk-FPBZFNCD.js} +1159 -415
- package/dist/chunk-FPBZFNCD.js.map +1 -0
- package/dist/{chunk-6XH6QZYQ.cjs → chunk-GDKE6RQW.cjs} +1453 -848
- package/dist/chunk-GDKE6RQW.cjs.map +1 -0
- package/dist/chunk-H66ARFU5.js +363 -0
- package/dist/chunk-H66ARFU5.js.map +1 -0
- package/dist/{chunk-YH3BIKHH.js → chunk-HOHFYGKW.js} +146 -49
- package/dist/chunk-HOHFYGKW.js.map +1 -0
- package/dist/chunk-HSY2VQXZ.js +2272 -0
- package/dist/chunk-HSY2VQXZ.js.map +1 -0
- package/dist/{chunk-E3F4AHAH.cjs → chunk-J772DL3B.cjs} +221 -134
- package/dist/chunk-J772DL3B.cjs.map +1 -0
- package/dist/chunk-K6OZXIR6.js +1 -0
- package/dist/{chunk-VTI6FOLB.js → chunk-PQL7E5F6.js} +709 -133
- package/dist/chunk-PQL7E5F6.js.map +1 -0
- package/dist/chunk-Q6R3QAZD.cjs +3329 -0
- package/dist/chunk-Q6R3QAZD.cjs.map +1 -0
- package/dist/{chunk-MXO72PLY.cjs → chunk-QCXYKSZS.cjs} +21 -19
- package/dist/chunk-QCXYKSZS.cjs.map +1 -0
- package/dist/{chunk-VOHZB45S.js → chunk-R5VUXQMP.js} +1321 -821
- package/dist/chunk-R5VUXQMP.js.map +1 -0
- package/dist/chunk-R7B5N2JK.cjs +363 -0
- package/dist/chunk-R7B5N2JK.cjs.map +1 -0
- package/dist/{chunk-WWURE4XT.cjs → chunk-RVS4G4MR.cjs} +624 -729
- package/dist/chunk-RVS4G4MR.cjs.map +1 -0
- package/dist/chunk-TT6PKMPR.cjs +145 -0
- package/dist/chunk-TT6PKMPR.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -0
- package/dist/chunk-VLE5R7PF.js +3329 -0
- package/dist/chunk-VLE5R7PF.js.map +1 -0
- package/dist/{chunk-FVSI36UV.js → chunk-X6RJAHII.js} +197 -110
- package/dist/chunk-X6RJAHII.js.map +1 -0
- package/dist/{chunk-Q2WGKJNN.cjs → chunk-XKSO2V5I.cjs} +699 -123
- package/dist/chunk-XKSO2V5I.cjs.map +1 -0
- package/dist/chunk-XVMJO3QF.cjs +2272 -0
- package/dist/chunk-XVMJO3QF.cjs.map +1 -0
- package/dist/{chunk-UADYAYAT.cjs → chunk-YPB3A3EY.cjs} +1309 -565
- package/dist/chunk-YPB3A3EY.cjs.map +1 -0
- package/dist/{index-C_8o--VE.d.cts → coerce-CJlgULrQ.d.cts} +210 -199
- package/dist/{index-szBv3496.d.ts → coerce-wwdELNYS.d.ts} +210 -199
- package/dist/core/index.cjs +9 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +122 -108
- package/dist/core/index.d.ts +122 -108
- package/dist/core/index.js +32 -28
- package/dist/generate/index.cjs +8 -8
- package/dist/generate/index.d.cts +168 -84
- package/dist/generate/index.d.ts +168 -84
- package/dist/generate/index.js +7 -7
- package/dist/{index-CGpRv5sl.d.cts → index-9eoQs2C1.d.ts} +34 -19
- package/dist/{index-Dzr13q3d.d.cts → index-B3WCj_Hm.d.cts} +135 -58
- package/dist/{index-BO6xE9YU.d.cts → index-BUtIf2go.d.ts} +460 -254
- package/dist/{index-9wcMhG73.d.ts → index-BaoKVVhE.d.cts} +293 -56
- package/dist/{index-Chbst3Jx.d.ts → index-BpWOhkkE.d.ts} +135 -58
- package/dist/{index-CgHqxdps.d.ts → index-BzLNkmA7.d.cts} +196 -114
- package/dist/{index-CunYKLiu.d.ts → index-CBCP84rv.d.cts} +1 -1
- package/dist/{index-BkGmlyI3.d.cts → index-Ci3xyows.d.ts} +60 -23
- package/dist/{index-CCNJRG-P.d.cts → index-CnHQgqNh.d.ts} +293 -56
- package/dist/{index-B4o55NDr.d.cts → index-CnIdP4EZ.d.ts} +1 -1
- package/dist/{index-B9MNZboQ.d.ts → index-Cy15oTvh.d.cts} +52 -22
- package/dist/{index-B9MNZboQ.d.cts → index-Cy15oTvh.d.ts} +52 -22
- package/dist/{index-D57QRbVn.d.ts → index-DTsKaPaC.d.cts} +460 -254
- package/dist/{index-Cj49F892.d.ts → index-D_hzxD8w.d.cts} +60 -23
- package/dist/{index-BGFW-8P2.d.cts → index-DpC-mDlV.d.ts} +196 -114
- package/dist/{index-G1uwGGWQ.d.ts → index-cHvbYgJI.d.cts} +34 -19
- package/dist/index.cjs +42 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +82 -54
- package/dist/{masks-DYbE485J.d.ts → masks-C8TWLFIi.d.cts} +49 -2
- package/dist/{masks-DYbE485J.d.cts → masks-O73Rhj5d.d.ts} +49 -2
- package/dist/model/index.cjs +19 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +502 -181
- package/dist/model/index.d.ts +502 -181
- package/dist/model/index.js +18 -11
- package/dist/{positional-Cptlj-xs.d.ts → positional-DJgs6ltD.d.ts} +85 -8
- package/dist/{positional-DvH_sd8u.d.cts → positional-DMZer-FM.d.cts} +85 -8
- package/dist/theory/index.cjs +35 -6
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +257 -62
- package/dist/theory/index.d.ts +257 -62
- package/dist/theory/index.js +41 -12
- package/package.json +6 -5
- package/dist/chunk-2LCPM3EG.js +0 -3224
- package/dist/chunk-2LCPM3EG.js.map +0 -1
- package/dist/chunk-6XH6QZYQ.cjs.map +0 -1
- package/dist/chunk-6XPTBE7Y.cjs +0 -49
- package/dist/chunk-6XPTBE7Y.cjs.map +0 -1
- package/dist/chunk-AFM2ULJH.cjs.map +0 -1
- package/dist/chunk-B6XNKWZV.cjs +0 -3224
- package/dist/chunk-B6XNKWZV.cjs.map +0 -1
- package/dist/chunk-E3F4AHAH.cjs.map +0 -1
- package/dist/chunk-ELCTZUJJ.js +0 -379
- package/dist/chunk-ELCTZUJJ.js.map +0 -1
- package/dist/chunk-FVSI36UV.js.map +0 -1
- package/dist/chunk-H3VORETI.js.map +0 -1
- package/dist/chunk-H42UHPYW.js +0 -1577
- package/dist/chunk-H42UHPYW.js.map +0 -1
- package/dist/chunk-HFPS7JFD.js +0 -1
- package/dist/chunk-INHVXP6Z.js.map +0 -1
- package/dist/chunk-K4ZJUGF4.cjs +0 -379
- package/dist/chunk-K4ZJUGF4.cjs.map +0 -1
- package/dist/chunk-KNJEKLKV.js +0 -49
- package/dist/chunk-KNJEKLKV.js.map +0 -1
- package/dist/chunk-MXO72PLY.cjs.map +0 -1
- package/dist/chunk-NLEU4QSE.cjs +0 -360
- package/dist/chunk-NLEU4QSE.cjs.map +0 -1
- package/dist/chunk-OE6WK2Y2.cjs +0 -1577
- package/dist/chunk-OE6WK2Y2.cjs.map +0 -1
- package/dist/chunk-PKFNUHDY.js.map +0 -1
- package/dist/chunk-Q2WGKJNN.cjs.map +0 -1
- package/dist/chunk-RHIJ3S4I.cjs +0 -1
- package/dist/chunk-RHIJ3S4I.cjs.map +0 -1
- package/dist/chunk-TTCLJX6D.js.map +0 -1
- package/dist/chunk-UADYAYAT.cjs.map +0 -1
- package/dist/chunk-VOHZB45S.js.map +0 -1
- package/dist/chunk-VTI6FOLB.js.map +0 -1
- package/dist/chunk-WWURE4XT.cjs.map +0 -1
- package/dist/chunk-YH3BIKHH.js.map +0 -1
- /package/dist/{chunk-HFPS7JFD.js.map → chunk-K6OZXIR6.js.map} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { d as ChordQuality, b as Chord, N as NoteSafety, S as SafetyProfile } from './
|
|
3
|
-
import { r as ScaleName } from './masks-
|
|
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-
|
|
5
|
-
import {
|
|
1
|
+
import { e as KeyScale, h as NoteEvent, i as NoteLike, g as Note, S as SpelledInterval } from './index-Cy15oTvh.cjs';
|
|
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-CJlgULrQ.cjs';
|
|
3
|
+
import { r as ScaleName } from './masks-C8TWLFIi.cjs';
|
|
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-B3WCj_Hm.cjs';
|
|
5
|
+
import { b as MeterLike } from './index-D_hzxD8w.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Church-mode candidates for {@link detectKey}.
|
|
@@ -164,12 +164,7 @@ type KeyMatch = {
|
|
|
164
164
|
*/
|
|
165
165
|
alternatives?: RejectedCandidate[];
|
|
166
166
|
};
|
|
167
|
-
|
|
168
|
-
* Which form of a scale a {@link KeyMatch} settled on.
|
|
169
|
-
*
|
|
170
|
-
* @category Recognition
|
|
171
|
-
*/
|
|
172
|
-
type KeyVariant = 'major' | 'natural' | 'harmonic' | 'melodic' | 'modal';
|
|
167
|
+
|
|
173
168
|
/**
|
|
174
169
|
* Input weighting for {@link detectKey}.
|
|
175
170
|
*
|
|
@@ -249,8 +244,13 @@ type DetectKeyOptions = {
|
|
|
249
244
|
* A match is reported when all of the chord's tones are present in the input, or
|
|
250
245
|
* when the only absent tone is the perfect fifth (a common omission in shell
|
|
251
246
|
* voicings); matches are ranked best-first by fewest extra notes, then fewest
|
|
252
|
-
* missing notes, then
|
|
253
|
-
* no missing) is flagged and ranked
|
|
247
|
+
* missing notes, then root position over an inversion, then most specific
|
|
248
|
+
* (largest) chord. An exact match (no extras, no missing) is flagged and ranked
|
|
249
|
+
* first.
|
|
250
|
+
*
|
|
251
|
+
* The root-position step decides between readings of the same pitches that are
|
|
252
|
+
* otherwise equally good: `[55, 59, 62, 64]` is read as G6 rather than Em7,
|
|
253
|
+
* because G is in the bass.
|
|
254
254
|
*
|
|
255
255
|
* @param pitches MIDI pitches or bare pitch classes (octave-agnostic).
|
|
256
256
|
* @returns Ranked chord interpretations (may be empty).
|
|
@@ -329,7 +329,7 @@ declare function detectChordBest(pitches: readonly number[], opts?: DetectChordO
|
|
|
329
329
|
* import { detectKey } from '@libraz/libcantus';
|
|
330
330
|
* const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale
|
|
331
331
|
* keys[0].mode; // 'major', with keys[0].key.rootPc === 0
|
|
332
|
-
* keys[1].mode; // 'minor' on 9: A minor, the relative, ranked just below
|
|
332
|
+
* keys[1].mode; // 'minor' — on 9: A minor, the relative, ranked just below
|
|
333
333
|
* const riff = detectKey([62, 62, 65, 69, 71, 69, 65, 62], { modes: true });
|
|
334
334
|
* riff[0].scaleName; // 'dorian', on rootPc 2, still reported as mode 'minor'
|
|
335
335
|
* ```
|
|
@@ -342,11 +342,21 @@ declare function detectKey(pitches: readonly number[], opts?: DetectKeyOptions):
|
|
|
342
342
|
*
|
|
343
343
|
* {@link detectKey} counts every pitch once, which lets a run of fast ornamental
|
|
344
344
|
* notes outvote the sustained harmony that establishes the key. This weighs
|
|
345
|
-
* each note by
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
345
|
+
* each note by the same measure chord inference weighs its own histogram by, so
|
|
346
|
+
* the two agree on what the music emphasises: duration times
|
|
347
|
+
* velocity, with a note that carries no velocity counted at full weight rather
|
|
348
|
+
* than at an assumed one. Notes that never sound (zero or negative duration)
|
|
349
|
+
* are ignored. The weighted distribution is then ranked exactly as
|
|
350
|
+
* {@link detectKey} ranks it, by correlation against a key profile, so `score`
|
|
351
|
+
* is a finite correlation in [-1, 1].
|
|
352
|
+
*
|
|
353
|
+
* The one term of that measure this cannot share is the metric accent, which
|
|
354
|
+
* chord inference adds to an onset by the beat it falls on: the notes arrive
|
|
355
|
+
* here without the meter they are counted in, and reading them against an
|
|
356
|
+
* assumed one would rank the same notes differently under a signature the
|
|
357
|
+
* caller never named. Ranking a whole piece is the coarser question, and the
|
|
358
|
+
* accents are what separate one window from the next rather than one key from
|
|
359
|
+
* another.
|
|
350
360
|
*
|
|
351
361
|
* @param notes The note events to weigh.
|
|
352
362
|
* @param opts Which profile to rank with, whether the modes take part, and the
|
|
@@ -398,6 +408,158 @@ declare function detectKeyFromNotes(notes: readonly NoteEvent[], opts?: Omit<Det
|
|
|
398
408
|
*/
|
|
399
409
|
declare function detectKeyBest(pitches: readonly number[], opts?: DetectKeyOptions): KeyMatch | null;
|
|
400
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Augmented-sixth chords: the Italian, French, and German sixths.
|
|
413
|
+
*
|
|
414
|
+
* All three stand on the lowered submediant (b6) in the bass and carry an
|
|
415
|
+
* augmented sixth above it, and all three are predominants whose two outer
|
|
416
|
+
* voices resolve outward onto the dominant. The family is defined by that bass
|
|
417
|
+
* and that interval rather than by a stack of thirds, so a chord is recognized
|
|
418
|
+
* here from the tones sounding over that bass rather than through the
|
|
419
|
+
* Roman-numeral degree tables.
|
|
420
|
+
*
|
|
421
|
+
* The interval is spelled, so recognition reads letters and not only pitch
|
|
422
|
+
* classes: over an Ab bass the augmented sixth is F# and the minor seventh of
|
|
423
|
+
* the bVI7 it otherwise sounds exactly like is Gb. A caller holding pitch
|
|
424
|
+
* classes alone — an analysis of a MIDI track, which names no accidentals —
|
|
425
|
+
* makes that reading with {@link augmentedSixthFromPitchClasses}, and the chord
|
|
426
|
+
* it hands back carries the spelling that decided it.
|
|
427
|
+
*/
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* The three augmented-sixth chords, under the nationalities convention gives
|
|
431
|
+
* them.
|
|
432
|
+
*
|
|
433
|
+
* Over an Ab bass in C: the Italian sixth is Ab C F#, the French sixth adds the
|
|
434
|
+
* supertonic (Ab C D F#), and the German sixth adds the lowered mediant
|
|
435
|
+
* (Ab C Eb F#).
|
|
436
|
+
*
|
|
437
|
+
* @category Functional Harmony
|
|
438
|
+
*/
|
|
439
|
+
type AugmentedSixthKind = 'italian' | 'french' | 'german';
|
|
440
|
+
/**
|
|
441
|
+
* Build an augmented-sixth chord in a key.
|
|
442
|
+
*
|
|
443
|
+
* The chord always carries an explicit `bassPc` on the lowered submediant,
|
|
444
|
+
* which is what makes it an augmented sixth rather than the dominant seventh or
|
|
445
|
+
* the altered supertonic seventh it shares pitch classes with, and an explicit
|
|
446
|
+
* interval template naming exactly the tones that sound.
|
|
447
|
+
*
|
|
448
|
+
* It also carries its spelling: the tones as `toneSpellings`, and the root and
|
|
449
|
+
* bass as the usual hints. Those are what let the ordinary chord speller and
|
|
450
|
+
* the symbol formatter name the chord — `Ab C Eb F#` and `Ab7` for the German
|
|
451
|
+
* sixth of C major — since a stack of thirds over that root would put a Gb
|
|
452
|
+
* where the augmented sixth belongs. The spelling is the one the key is written
|
|
453
|
+
* on, so a flat-side key takes the double flats it genuinely needs.
|
|
454
|
+
*
|
|
455
|
+
* The lowered submediant is taken as eight semitones above the tonic in every
|
|
456
|
+
* mode, since the chord is a chromatic alteration rather than a degree of the
|
|
457
|
+
* prevailing scale.
|
|
458
|
+
*
|
|
459
|
+
* @param kind Which of the three augmented sixths to build.
|
|
460
|
+
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
461
|
+
* @returns The chord, with its bass on the lowered submediant.
|
|
462
|
+
* @throws If `kind` is not one of the three, or the key's root is not finite.
|
|
463
|
+
* @see {@link spellAugmentedSixth} to spell the family from a tonic without
|
|
464
|
+
* building a chord at all.
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* import { augmentedSixthChord, majorKey } from '@libraz/libcantus';
|
|
468
|
+
* augmentedSixthChord('german', majorKey(0));
|
|
469
|
+
* // Ab C Eb F# in C: { rootPc: 8, quality: 'dom7', intervals: [0, 4, 7, 10], bassPc: 8, ... }
|
|
470
|
+
* ```
|
|
471
|
+
* @category Functional Harmony
|
|
472
|
+
*/
|
|
473
|
+
declare function augmentedSixthChord(kind: AugmentedSixthKind, key: SpelledKeyLike): Chord;
|
|
474
|
+
/**
|
|
475
|
+
* Identify a chord as one of the augmented sixths, or null when it is none.
|
|
476
|
+
*
|
|
477
|
+
* A chord qualifies when it sounds the lowered submediant of `key` in the bass,
|
|
478
|
+
* its pitch classes are exactly one of the three sets built over that bass, and
|
|
479
|
+
* its own letters spell the augmented sixth above it. All three are needed: the
|
|
480
|
+
* same pitch classes over the same bass are an ordinary bVI7 when the tone ten
|
|
481
|
+
* semitones above the bass is written as a minor seventh, and the letters are
|
|
482
|
+
* the only place that distinction lives.
|
|
483
|
+
*
|
|
484
|
+
* A chord naming no `bassPc` sounds its root lowest, which is how the two
|
|
485
|
+
* conventions for that field — the sounding bass always, or only a bass that
|
|
486
|
+
* differs from the root — are read alike here.
|
|
487
|
+
*
|
|
488
|
+
* The root the chord is measured from is not consulted, so a chord voiced or
|
|
489
|
+
* rebuilt around any of its own tones still identifies as long as it keeps its
|
|
490
|
+
* spelling.
|
|
491
|
+
*
|
|
492
|
+
* @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.
|
|
493
|
+
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
494
|
+
* @returns The kind of augmented sixth, or null.
|
|
495
|
+
* @see {@link augmentedSixthFromPitchClasses} to make the reading from pitch
|
|
496
|
+
* classes that carry no spelling at all.
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* import { augmentedSixthKind, majorKey, romanToChord } from '@libraz/libcantus';
|
|
500
|
+
* augmentedSixthKind(romanToChord('Fr6', majorKey(0)), majorKey(0)); // 'french'
|
|
501
|
+
* ```
|
|
502
|
+
* @category Functional Harmony
|
|
503
|
+
*/
|
|
504
|
+
declare function augmentedSixthKind(chord: ChordLike, key: KeyLike): AugmentedSixthKind | null;
|
|
505
|
+
/**
|
|
506
|
+
* Read a set of sounding pitch classes over a sounding bass as an augmented
|
|
507
|
+
* sixth, or null when they are none.
|
|
508
|
+
*
|
|
509
|
+
* The reading a caller holding no spelling has to make for itself: pitch
|
|
510
|
+
* classes name no accidentals, so nothing downstream can tell the German sixth
|
|
511
|
+
* from the bVI7 it sounds like once the letters are gone. The chord returned is
|
|
512
|
+
* the one {@link augmentedSixthChord} builds, spelling and all, so it keeps
|
|
513
|
+
* identifying as an augmented sixth everywhere it travels.
|
|
514
|
+
*
|
|
515
|
+
* Not every b6-1-b3-#4 over a b6 bass is an augmented sixth, and this cannot
|
|
516
|
+
* know which one is: it answers what the tones would spell, and the caller
|
|
517
|
+
* decides whether that reading is the one the music supports.
|
|
518
|
+
*
|
|
519
|
+
* @param pcs The sounding pitch classes; duplicates and unreduced values are
|
|
520
|
+
* fine.
|
|
521
|
+
* @param bassPc The pitch class sounding in the bass.
|
|
522
|
+
* @param key The prevailing key, as a key name, a key/scale, or a `Key`.
|
|
523
|
+
* @returns The augmented-sixth chord, or null.
|
|
524
|
+
* @see {@link augmentedSixthKind} to read a chord that already carries its
|
|
525
|
+
* spelling.
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* import { augmentedSixthFromPitchClasses, majorKey } from '@libraz/libcantus';
|
|
529
|
+
* // Ab C Eb Gb sounding over Ab in C: the German sixth, spelled with its F#.
|
|
530
|
+
* augmentedSixthFromPitchClasses([8, 0, 3, 6], 8, majorKey(0))?.quality; // 'dom7'
|
|
531
|
+
* ```
|
|
532
|
+
* @category Functional Harmony
|
|
533
|
+
*/
|
|
534
|
+
declare function augmentedSixthFromPitchClasses(pcs: readonly number[], bassPc: number, key: SpelledKeyLike): Chord | null;
|
|
535
|
+
/**
|
|
536
|
+
* Spell an augmented-sixth chord from a spelled tonic, lowest note first.
|
|
537
|
+
*
|
|
538
|
+
* This is the spelled counterpart of {@link augmentedSixthChord}, and the table
|
|
539
|
+
* the chord's own spelling is built from: every tone is placed by a named
|
|
540
|
+
* interval above the bass, so the outer interval is an augmented sixth in every
|
|
541
|
+
* key rather than the minor seventh its ten semitones would otherwise imply.
|
|
542
|
+
* Reach for it when a spelled tonic is all there is; spelling the chord itself
|
|
543
|
+
* with {@link spellChord} names the same notes, in the chord's own order.
|
|
544
|
+
*
|
|
545
|
+
* A tonic carrying an octave yields notes carrying octaves, ascending from the
|
|
546
|
+
* bass; an octave-less tonic yields octave-less notes.
|
|
547
|
+
*
|
|
548
|
+
* @param kind Which of the three augmented sixths to spell.
|
|
549
|
+
* @param tonic The spelled tonic of the key, as a note name, a MIDI number,
|
|
550
|
+
* note data, or a `Note`.
|
|
551
|
+
* @returns The spelled tones, bass first and ascending.
|
|
552
|
+
* @throws If `kind` is not one of the three, or `tonic` is not a note.
|
|
553
|
+
* @example
|
|
554
|
+
* ```ts
|
|
555
|
+
* import { noteNames, parseNote, spellAugmentedSixth } from '@libraz/libcantus';
|
|
556
|
+
* noteNames(spellAugmentedSixth('german', parseNote('C'))); // ['Ab', 'C', 'Eb', 'F#']
|
|
557
|
+
* noteNames(spellAugmentedSixth('italian', 'E')); // ['C', 'E', 'A#']
|
|
558
|
+
* ```
|
|
559
|
+
* @category Functional Harmony
|
|
560
|
+
*/
|
|
561
|
+
declare function spellAugmentedSixth(kind: AugmentedSixthKind, tonic: NoteLike): Note[];
|
|
562
|
+
|
|
401
563
|
/**
|
|
402
564
|
* Arrangement track-role analysis: a single harmony is inferred from every
|
|
403
565
|
* track's notes pooled together, each track is then analysed against that shared
|
|
@@ -485,8 +647,11 @@ type ArrangementAnalysis = {
|
|
|
485
647
|
* started in for every bar after it leaves.
|
|
486
648
|
*/
|
|
487
649
|
keys: KeyRegion[];
|
|
488
|
-
/**
|
|
489
|
-
|
|
650
|
+
/**
|
|
651
|
+
* The key held longest across {@link ArrangementAnalysis.keys}, whole: the
|
|
652
|
+
* pitch classes with the spelling and the scale form the regions carry.
|
|
653
|
+
*/
|
|
654
|
+
prevailingKey: ResolvedKey;
|
|
490
655
|
timeline: ChordTimeline;
|
|
491
656
|
segmentConfidence: number[];
|
|
492
657
|
cadences: CadenceHit[];
|
|
@@ -504,8 +669,11 @@ type ArrangementOptions = {
|
|
|
504
669
|
* A single key held across the whole arrangement. Omit it, and `keys` with
|
|
505
670
|
* it, to have the key searched for over time so a modulating piece is read
|
|
506
671
|
* against the key actually in force.
|
|
672
|
+
*
|
|
673
|
+
* Taken in whatever form the caller holds a key, and kept whole: the region
|
|
674
|
+
* built from it carries the spelling and the scale form it was named with.
|
|
507
675
|
*/
|
|
508
|
-
key?:
|
|
676
|
+
key?: KeyLike;
|
|
509
677
|
/**
|
|
510
678
|
* Key regions to judge against, when a previous pass already worked them out.
|
|
511
679
|
* Takes precedence over `key`; supplying both is answering the same question
|
|
@@ -536,14 +704,14 @@ type ArrangementOptions = {
|
|
|
536
704
|
*
|
|
537
705
|
* @defaultValue `4/4`
|
|
538
706
|
*/
|
|
539
|
-
ts?:
|
|
707
|
+
ts?: MeterLike;
|
|
540
708
|
/**
|
|
541
709
|
* The meter as it changes over the arrangement. Bar lines, downbeats and
|
|
542
710
|
* metric weight all follow the signature in force at the beat in question.
|
|
543
711
|
*
|
|
544
712
|
* @defaultValue 4/4 throughout
|
|
545
713
|
*/
|
|
546
|
-
meters?:
|
|
714
|
+
meters?: MeterLike;
|
|
547
715
|
/**
|
|
548
716
|
* Length of the pickup in beats, when the piece starts with one.
|
|
549
717
|
*
|
|
@@ -555,7 +723,13 @@ type ArrangementOptions = {
|
|
|
555
723
|
*/
|
|
556
724
|
pickupBeats?: number;
|
|
557
725
|
/**
|
|
558
|
-
*
|
|
726
|
+
* Expected chord length in beats; defaults to the length of the opening bar.
|
|
727
|
+
*
|
|
728
|
+
* A prior rather than a grid: the arrangement analysis infers its harmony
|
|
729
|
+
* with dynamic segmentation, where the longer a chord is expected to last the
|
|
730
|
+
* more evidence a change needs before the search will place one. A chord may
|
|
731
|
+
* still begin at any position the notes argue for. Supply `timeline` to
|
|
732
|
+
* analyse against boundaries you have fixed yourself.
|
|
559
733
|
*
|
|
560
734
|
* @defaultValue the length of the opening bar
|
|
561
735
|
*/
|
|
@@ -578,6 +752,26 @@ type ArrangementOptions = {
|
|
|
578
752
|
*/
|
|
579
753
|
budget?: number;
|
|
580
754
|
};
|
|
755
|
+
/**
|
|
756
|
+
* Label every note of one polyphonic passage against the harmony under it and
|
|
757
|
+
* the voices around it.
|
|
758
|
+
*
|
|
759
|
+
* {@link analyzeVoice} reads a single line: it takes the note before and the note
|
|
760
|
+
* after each note as that note's melodic neighbours, and it is given no other
|
|
761
|
+
* voices, so over a flat array of everything that sounds it invents figures
|
|
762
|
+
* across parts — the alto between two soprano notes becomes a passing tone —
|
|
763
|
+
* and can never find a suspension, which is by definition a dissonance against
|
|
764
|
+
* something else sounding. So the passage is partitioned into monophonic
|
|
765
|
+
* sub-voices first and each is read with the others handed to it, which is what
|
|
766
|
+
* {@link analyzeArrangement} does with the tracks it is given.
|
|
767
|
+
*
|
|
768
|
+
* @param notes The passage, in any order; the ids are indices into this array.
|
|
769
|
+
* @param chordAtBeat Chord sounding at a given beat, or null.
|
|
770
|
+
* @param key The key, or the key in force at a given beat.
|
|
771
|
+
* @returns One annotation per note, in the order the notes were given, each
|
|
772
|
+
* carrying its position in that array as its id.
|
|
773
|
+
*/
|
|
774
|
+
declare function analyzePolyphony(notes: readonly NoteEvent[], chordAtBeat: (beat: number) => Chord | null, key: KeyContext): AnalyzedNote[];
|
|
581
775
|
/**
|
|
582
776
|
* Analyse a whole arrangement against the harmony inferred from it.
|
|
583
777
|
*
|
|
@@ -622,7 +816,7 @@ type ArrangementOptions = {
|
|
|
622
816
|
* { pitch: 67, startBeat: 2, durationBeat: 2 },
|
|
623
817
|
* ];
|
|
624
818
|
* const { prevailingKey, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);
|
|
625
|
-
* prevailingKey.rootPc; // the key held longest across the analysis
|
|
819
|
+
* prevailingKey.scale.rootPc; // the key held longest across the analysis
|
|
626
820
|
* conflicts; // notes clashing with the inferred harmony, worst severity first
|
|
627
821
|
* ```
|
|
628
822
|
* @category Arrangement & Analysis
|
|
@@ -786,6 +980,11 @@ declare function tensionCurve(tracks: ArrangementTrack[], opts?: ArrangementOpti
|
|
|
786
980
|
* twice over the same notes. This takes the analysis it already produced, so
|
|
787
981
|
* the curve and the annotations describe the same harmony by construction.
|
|
788
982
|
*
|
|
983
|
+
* The analysis is the harmony to fall back on rather than one laid over the
|
|
984
|
+
* caller: `key`, `keys` and `harmonyTracks` each name a harmony the analysis
|
|
985
|
+
* does not hold, so naming one is asking for a different reading, and the curve
|
|
986
|
+
* is taken under it. Nothing else about the analysis is re-derived.
|
|
987
|
+
*
|
|
789
988
|
* @param tracks The same tracks that were analysed.
|
|
790
989
|
* @param analysis The result of {@link analyzeArrangement} for those tracks.
|
|
791
990
|
* @param opts Analysis options plus an optional `step` (default one beat).
|
|
@@ -875,9 +1074,11 @@ type HypermeterOptions = {
|
|
|
875
1074
|
* Groupings of 2, 3, 4, 6, and 8 bars are tried at every phase, and each
|
|
876
1075
|
* reading is scored on two counts: how much more harmonic change lands on the
|
|
877
1076
|
* bars it calls group heads than on the bars between them, and how many of the
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
1077
|
+
* hyperbar ends it predicts are closed by one of the cadences it is given.
|
|
1078
|
+
* Where cadences are given they settle the phase, since a hyperbar is what a
|
|
1079
|
+
* cadence ends; the harmony then chooses among the phases they agree with. The
|
|
1080
|
+
* commonest groupings are believed a little more readily than the rare ones,
|
|
1081
|
+
* which settles ties without letting a prior overrule the music.
|
|
881
1082
|
*
|
|
882
1083
|
* The meter is read at the beat in question rather than at the start, so a
|
|
883
1084
|
* piece that changes metre keeps being cut at its own bar lines. Bar 0 is the
|
|
@@ -989,15 +1190,17 @@ type PhraseOptions = {
|
|
|
989
1190
|
*
|
|
990
1191
|
* @defaultValue `4/4`
|
|
991
1192
|
*/
|
|
992
|
-
ts?:
|
|
1193
|
+
ts?: MeterLike;
|
|
993
1194
|
/**
|
|
994
|
-
* The meter as it changes over the span. Bar lines
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1195
|
+
* The meter as it changes over the span. Bar lines and hypermetric downbeats
|
|
1196
|
+
* follow the signature in force at the beat in question, so a piece that
|
|
1197
|
+
* changes metre is cut at its own bar lines; the length defaults below are
|
|
1198
|
+
* lengths rather than positions, and each is taken once from the meter the
|
|
1199
|
+
* span opens in.
|
|
997
1200
|
*
|
|
998
1201
|
* @defaultValue 4/4 throughout
|
|
999
1202
|
*/
|
|
1000
|
-
meters?:
|
|
1203
|
+
meters?: MeterLike;
|
|
1001
1204
|
/**
|
|
1002
1205
|
* Key context for cadence detection: one key for the whole span, or the key
|
|
1003
1206
|
* in force at a given beat. Omit it to have the key searched for over time,
|
|
@@ -1084,7 +1287,10 @@ type StructuralCadence = {
|
|
|
1084
1287
|
*
|
|
1085
1288
|
* Three kinds of evidence are gathered independently and then reconciled. The
|
|
1086
1289
|
* harmony contributes its cadences, each closing at the end of the chord it
|
|
1087
|
-
* arrives on
|
|
1290
|
+
* arrives on, and at the bar line where that chord is held across one — a tonic
|
|
1291
|
+
* still sounding into the next phrase closed this one at the bar line. The
|
|
1292
|
+
* cadence reported for a phrase is always one that arrived inside it. The
|
|
1293
|
+
* melody contributes the rests it breathes at and the notes it
|
|
1088
1294
|
* holds long enough to read as arrivals. Repetition contributes the beats where
|
|
1089
1295
|
* the music restarts material it has just played. The metre contributes its
|
|
1090
1296
|
* hypermetric downbeats, which confirm a boundary but never declare one on
|
|
@@ -1107,18 +1313,25 @@ type StructuralCadence = {
|
|
|
1107
1313
|
* that never sound are dropped.
|
|
1108
1314
|
* @param opts Analysis options; see {@link PhraseOptions}.
|
|
1109
1315
|
* @returns The phrases, in time order; one phrase covering the whole span when
|
|
1110
|
-
* the span is too short to divide
|
|
1316
|
+
* the span is too short to divide, and none at all when nothing sounds —
|
|
1317
|
+
* silence is no material to hear a phrase in.
|
|
1111
1318
|
* @example
|
|
1112
1319
|
* ```ts
|
|
1113
1320
|
* import { chordTimelineFromNotes, phrasesFromTimeline } from '@libraz/libcantus';
|
|
1321
|
+
* // Four bars of C - F - G - C. Triads rather than single notes: one line at a
|
|
1322
|
+
* // time names no harmony, so nothing would cadence.
|
|
1323
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
1324
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
1114
1325
|
* const notes = [
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1326
|
+
* ...triad([60, 64, 67], 0),
|
|
1327
|
+
* ...triad([65, 69, 72], 4),
|
|
1328
|
+
* ...triad([67, 71, 74], 8),
|
|
1329
|
+
* ...triad([60, 64, 67], 12),
|
|
1118
1330
|
* ];
|
|
1119
1331
|
* const { timeline } = chordTimelineFromNotes(notes);
|
|
1120
1332
|
* const phrases = phrasesFromTimeline(timeline, notes);
|
|
1121
|
-
* phrases[phrases.length - 1]?.cadence?.cadence.type; // 'authentic'
|
|
1333
|
+
* phrases[phrases.length - 1]?.cadence?.cadence.type; // 'authentic'
|
|
1334
|
+
* phrases[phrases.length - 1]?.cadence?.atBeat; // 12, inside the phrase it closes
|
|
1122
1335
|
* ```
|
|
1123
1336
|
* @category Arrangement & Analysis
|
|
1124
1337
|
*/
|
|
@@ -1136,14 +1349,18 @@ declare function phrasesFromTimeline(timeline: ChordTimeline, notes: readonly No
|
|
|
1136
1349
|
* @example
|
|
1137
1350
|
* ```ts
|
|
1138
1351
|
* import { chordTimelineFromNotes, phrasesFromTimeline, structuralCadences } from '@libraz/libcantus';
|
|
1352
|
+
* const triad = (pitches: number[], startBeat: number) =>
|
|
1353
|
+
* pitches.map((pitch) => ({ pitch, startBeat, durationBeat: 4 }));
|
|
1139
1354
|
* const notes = [
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1355
|
+
* ...triad([60, 64, 67], 0),
|
|
1356
|
+
* ...triad([65, 69, 72], 4),
|
|
1357
|
+
* ...triad([67, 71, 74], 8),
|
|
1358
|
+
* ...triad([60, 64, 67], 12),
|
|
1143
1359
|
* ];
|
|
1144
1360
|
* const { timeline } = chordTimelineFromNotes(notes);
|
|
1145
1361
|
* const ranked = structuralCadences(phrasesFromTimeline(timeline, notes));
|
|
1146
|
-
* ranked[0]; // the cadence carrying the most
|
|
1362
|
+
* ranked[0]?.cadence.type; // 'authentic' — the cadence carrying the most weight
|
|
1363
|
+
* ranked[0]?.atBeat; // 12
|
|
1147
1364
|
* ```
|
|
1148
1365
|
* @category Arrangement & Analysis
|
|
1149
1366
|
*/
|
|
@@ -1206,14 +1423,14 @@ type FormSectionOptions = {
|
|
|
1206
1423
|
*
|
|
1207
1424
|
* @defaultValue `4/4`
|
|
1208
1425
|
*/
|
|
1209
|
-
ts?:
|
|
1426
|
+
ts?: MeterLike;
|
|
1210
1427
|
/**
|
|
1211
1428
|
* The meter as it changes over the span, so a piece that changes metre is cut
|
|
1212
1429
|
* at its own bar lines.
|
|
1213
1430
|
*
|
|
1214
1431
|
* @defaultValue 4/4 throughout
|
|
1215
1432
|
*/
|
|
1216
|
-
meters?:
|
|
1433
|
+
meters?: MeterLike;
|
|
1217
1434
|
/**
|
|
1218
1435
|
* Length of the unit spans are compared in, in bars; defaults to one hyperbar.
|
|
1219
1436
|
*
|
|
@@ -1278,58 +1495,15 @@ type FormSectionOptions = {
|
|
|
1278
1495
|
declare function sectionsFromNotes(notes: readonly NoteEvent[], opts?: FormSectionOptions): FormSection[];
|
|
1279
1496
|
|
|
1280
1497
|
/**
|
|
1281
|
-
*
|
|
1498
|
+
* The recurring cells a melody is built from.
|
|
1282
1499
|
*
|
|
1283
|
-
* A motif is
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
* no such relation can still be scored for similarity.
|
|
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.
|
|
1289
1505
|
*/
|
|
1290
1506
|
|
|
1291
|
-
/**
|
|
1292
|
-
* Step direction between two consecutive notes.
|
|
1293
|
-
*
|
|
1294
|
-
* @category Arrangement & Analysis
|
|
1295
|
-
*/
|
|
1296
|
-
type ContourDirection = 'up' | 'down' | 'same';
|
|
1297
|
-
/**
|
|
1298
|
-
* The overall shape of a line.
|
|
1299
|
-
*
|
|
1300
|
-
* The first four names are the vocabulary the motif generator's contours are
|
|
1301
|
-
* written in, and mean the same here. `'static'` is the shape a generator never
|
|
1302
|
-
* asks for and an analysis regularly meets: a line that does not move. A line
|
|
1303
|
-
* that dips and comes back — an arch upside down — reads as `'wave'`, since the
|
|
1304
|
-
* shared vocabulary has no name of its own for it.
|
|
1305
|
-
*
|
|
1306
|
-
* A wave has to turn more than once to be heard as one, so a cell the generator
|
|
1307
|
-
* writes for `'wave'` reads back as `'wave'` only from three bars up; the one-
|
|
1308
|
-
* and two-bar cells turn once and read as `'arch'`. The other three shapes come
|
|
1309
|
-
* back under their own name at every length.
|
|
1310
|
-
*
|
|
1311
|
-
* @category Arrangement & Analysis
|
|
1312
|
-
*/
|
|
1313
|
-
type MelodicContourShape = 'arch' | 'ascending' | 'descending' | 'wave' | 'static';
|
|
1314
|
-
/**
|
|
1315
|
-
* The abstract shape of a line: its step directions and what they add up to.
|
|
1316
|
-
*
|
|
1317
|
-
* @category Arrangement & Analysis
|
|
1318
|
-
*/
|
|
1319
|
-
type MelodicContour = {
|
|
1320
|
-
/** One direction per adjacent pair of notes, in time order. */
|
|
1321
|
-
directions: ContourDirection[];
|
|
1322
|
-
/** What the directions add up to. */
|
|
1323
|
-
shape: MelodicContourShape;
|
|
1324
|
-
/** Index of the highest note; ties keep the earliest. */
|
|
1325
|
-
peakIndex: number;
|
|
1326
|
-
/** Index of the lowest note; ties keep the earliest. */
|
|
1327
|
-
troughIndex: number;
|
|
1328
|
-
/** Semitone distance from the lowest note to the highest. */
|
|
1329
|
-
range: number;
|
|
1330
|
-
/** Why the line was given that shape. */
|
|
1331
|
-
rationale: string;
|
|
1332
|
-
};
|
|
1333
1507
|
/**
|
|
1334
1508
|
* One statement of a motif, addressed in the melody it was found in.
|
|
1335
1509
|
*
|
|
@@ -1418,71 +1592,6 @@ type ExtractMotifsOptions = {
|
|
|
1418
1592
|
*/
|
|
1419
1593
|
budget?: number;
|
|
1420
1594
|
};
|
|
1421
|
-
/**
|
|
1422
|
-
* How one statement of a motif stands to another.
|
|
1423
|
-
*
|
|
1424
|
-
* `'transposition'` is the literal one — every interval preserved, which is what
|
|
1425
|
-
* makes a sequence built on it a *real* sequence. `'tonalTransposition'` shifts
|
|
1426
|
-
* by scale degrees instead and lets the key resize the intervals, which is what
|
|
1427
|
-
* makes a sequence built on it a *tonal* sequence; the two are different devices
|
|
1428
|
-
* and are never reported as one. `'augmentation'` and `'diminution'` name the
|
|
1429
|
-
* relation only when the pitches are untouched, so an inversion in doubled note
|
|
1430
|
-
* values reads as `'inversion'` with a `timeRatio` of 2 rather than losing the
|
|
1431
|
-
* inversion to the stretch.
|
|
1432
|
-
*
|
|
1433
|
-
* @category Arrangement & Analysis
|
|
1434
|
-
*/
|
|
1435
|
-
type MotifRelationKind = 'repetition' | 'transposition' | 'tonalTransposition' | 'inversion' | 'retrograde' | 'retrogradeInversion' | 'augmentation' | 'diminution';
|
|
1436
|
-
/**
|
|
1437
|
-
* The transformation that turns one motif into another.
|
|
1438
|
-
*
|
|
1439
|
-
* @category Arrangement & Analysis
|
|
1440
|
-
*/
|
|
1441
|
-
type MotifRelation = {
|
|
1442
|
-
/** The named transformation. */
|
|
1443
|
-
kind: MotifRelationKind;
|
|
1444
|
-
/**
|
|
1445
|
-
* Whether the second statement begins where the first ends, which is what
|
|
1446
|
-
* separates a sequence from a restatement elsewhere in the piece: a
|
|
1447
|
-
* `'transposition'` that is one is a real sequence, a `'tonalTransposition'`
|
|
1448
|
-
* that is one is a tonal sequence.
|
|
1449
|
-
*/
|
|
1450
|
-
sequence: boolean;
|
|
1451
|
-
/** Semitones from the first statement's first note to the second's. */
|
|
1452
|
-
semitones: number;
|
|
1453
|
-
/** The same distance named as a spelled interval. */
|
|
1454
|
-
interval: SpelledInterval;
|
|
1455
|
-
/** Scale degrees the second statement stands above the first, when tonal. */
|
|
1456
|
-
degrees?: number;
|
|
1457
|
-
/**
|
|
1458
|
-
* How much the second statement is stretched against the first: 2 for doubled
|
|
1459
|
-
* note values, 0.5 for halved, 1 for unchanged.
|
|
1460
|
-
*/
|
|
1461
|
-
timeRatio: number;
|
|
1462
|
-
/** Why the pair was given that name. */
|
|
1463
|
-
rationale: string;
|
|
1464
|
-
};
|
|
1465
|
-
/**
|
|
1466
|
-
* Anything that can be read as a melodic line: a motif, or plain note events.
|
|
1467
|
-
*
|
|
1468
|
-
* @category Arrangement & Analysis
|
|
1469
|
-
*/
|
|
1470
|
-
type MelodicPhrase = MotifData | readonly NoteEvent[];
|
|
1471
|
-
/**
|
|
1472
|
-
* How alike two lines are, and how the answer was reached.
|
|
1473
|
-
*
|
|
1474
|
-
* @category Arrangement & Analysis
|
|
1475
|
-
*/
|
|
1476
|
-
type MelodicComparison = {
|
|
1477
|
-
/** Overall likeness in [0, 1]; 1 for the same line, transposition included. */
|
|
1478
|
-
similarity: number;
|
|
1479
|
-
/** Likeness of the interval sequences alone, in [0, 1]. */
|
|
1480
|
-
pitchSimilarity: number;
|
|
1481
|
-
/** Likeness of the rhythmic profiles alone, in [0, 1]. */
|
|
1482
|
-
rhythmSimilarity: number;
|
|
1483
|
-
/** Why the lines scored what they did. */
|
|
1484
|
-
rationale: string;
|
|
1485
|
-
};
|
|
1486
1595
|
/**
|
|
1487
1596
|
* Find the cells a melody keeps coming back to.
|
|
1488
1597
|
*
|
|
@@ -1496,9 +1605,11 @@ type MelodicComparison = {
|
|
|
1496
1605
|
* rather than every fragment of it.
|
|
1497
1606
|
*
|
|
1498
1607
|
* The melody is expected to be monophonic, as {@link analyzeVoice} expects a
|
|
1499
|
-
* voice to be; split polyphonic material into lines first.
|
|
1500
|
-
*
|
|
1501
|
-
* as
|
|
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.
|
|
1502
1613
|
*
|
|
1503
1614
|
* @param notes The melody, in time order. Notes that never sound are dropped.
|
|
1504
1615
|
* @param opts Cell-length bounds and the recurrence threshold; see
|
|
@@ -1529,7 +1640,13 @@ declare function extractMotifs(notes: readonly NoteEvent[], opts?: ExtractMotifs
|
|
|
1529
1640
|
* caller names one it already has — a subject, an answer, a phrase lifted out of
|
|
1530
1641
|
* a score — so that it can be handed to {@link relateMotifs}.
|
|
1531
1642
|
*
|
|
1532
|
-
*
|
|
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.
|
|
1533
1650
|
* @returns The motif, carrying exactly one occurrence.
|
|
1534
1651
|
* @example
|
|
1535
1652
|
* ```ts
|
|
@@ -1549,102 +1666,74 @@ declare function extractMotifs(notes: readonly NoteEvent[], opts?: ExtractMotifs
|
|
|
1549
1666
|
* @category Arrangement & Analysis
|
|
1550
1667
|
*/
|
|
1551
1668
|
declare function motifFromNotes(notes: readonly NoteEvent[]): MotifData;
|
|
1669
|
+
|
|
1552
1670
|
/**
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
* The two are compared as shapes, not as pitches: the interval sequences decide
|
|
1556
|
-
* the pitch transformation and the onset gaps decide the timing, so a statement
|
|
1557
|
-
* a fourth higher in doubled note values is recognised for what it is. A pair
|
|
1558
|
-
* whose rhythms do not correspond — the same way round for a repetition or a
|
|
1559
|
-
* transposition, back to front for a retrograde — is not a transformation of the
|
|
1560
|
-
* motif but a different figure, and answers null; {@link melodicSimilarity} is
|
|
1561
|
-
* what scores those.
|
|
1671
|
+
* What every reading of a melody has to agree on first.
|
|
1562
1672
|
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
|
|
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.
|
|
1569
1682
|
*
|
|
1570
|
-
* @param a The model statement.
|
|
1571
|
-
* @param b The statement to name against it.
|
|
1572
|
-
* @param key Key context for the tonal reading; without it, a statement that
|
|
1573
|
-
* only holds diatonically is not recognised.
|
|
1574
|
-
* @returns The relation, or null when the two stand in none.
|
|
1575
|
-
* @example
|
|
1576
|
-
* ```ts
|
|
1577
|
-
* import { majorKey, motifFromNotes, relateMotifs } from '@libraz/libcantus';
|
|
1578
|
-
* const model = motifFromNotes([
|
|
1579
|
-
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1580
|
-
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1581
|
-
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1582
|
-
* ]);
|
|
1583
|
-
* const answer = motifFromNotes([
|
|
1584
|
-
* { pitch: 62, startBeat: 3, durationBeat: 1 },
|
|
1585
|
-
* { pitch: 64, startBeat: 4, durationBeat: 1 },
|
|
1586
|
-
* { pitch: 65, startBeat: 5, durationBeat: 1 },
|
|
1587
|
-
* ]);
|
|
1588
|
-
* relateMotifs(model, answer, majorKey(0))?.kind; // 'tonalTransposition'
|
|
1589
|
-
* ```
|
|
1590
1683
|
* @category Arrangement & Analysis
|
|
1591
1684
|
*/
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
*
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1605
|
-
* its pitch shape, but a figure in a wholly different rhythm is a different
|
|
1606
|
-
* figure.
|
|
1607
|
-
*
|
|
1608
|
-
* @param a The first line: a motif, or plain note events.
|
|
1609
|
-
* @param b The second line.
|
|
1610
|
-
* @returns The overall likeness, the two terms behind it, and a rationale.
|
|
1611
|
-
* @example
|
|
1612
|
-
* ```ts
|
|
1613
|
-
* import { compareMelodies } from '@libraz/libcantus';
|
|
1614
|
-
* const line = [
|
|
1615
|
-
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1616
|
-
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1617
|
-
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1618
|
-
* ];
|
|
1619
|
-
* compareMelodies(line, line).similarity; // 1
|
|
1620
|
-
* ```
|
|
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
|
+
*
|
|
1621
1698
|
* @category Arrangement & Analysis
|
|
1622
1699
|
*/
|
|
1623
|
-
|
|
1700
|
+
type ContourDirection = 'up' | 'down' | 'same';
|
|
1624
1701
|
/**
|
|
1625
|
-
*
|
|
1702
|
+
* The overall shape of a line.
|
|
1626
1703
|
*
|
|
1627
|
-
* The
|
|
1628
|
-
*
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
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.
|
|
1631
1720
|
*
|
|
1632
|
-
* @param a The first line: a motif, or plain note events.
|
|
1633
|
-
* @param b The second line.
|
|
1634
|
-
* @returns The likeness in [0, 1].
|
|
1635
|
-
* @example
|
|
1636
|
-
* ```ts
|
|
1637
|
-
* import { melodicSimilarity } from '@libraz/libcantus';
|
|
1638
|
-
* const line = [
|
|
1639
|
-
* { pitch: 60, startBeat: 0, durationBeat: 1 },
|
|
1640
|
-
* { pitch: 62, startBeat: 1, durationBeat: 1 },
|
|
1641
|
-
* { pitch: 64, startBeat: 2, durationBeat: 1 },
|
|
1642
|
-
* ];
|
|
1643
|
-
* melodicSimilarity(line, line); // 1
|
|
1644
|
-
* ```
|
|
1645
1721
|
* @category Arrangement & Analysis
|
|
1646
1722
|
*/
|
|
1647
|
-
|
|
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
|
+
};
|
|
1648
1737
|
/**
|
|
1649
1738
|
* Read the shape of a line.
|
|
1650
1739
|
*
|
|
@@ -1655,9 +1744,17 @@ declare function melodicSimilarity(a: MelodicPhrase, b: MelodicPhrase): number;
|
|
|
1655
1744
|
* Anything that keeps changing direction is a wave, and a line that does not move
|
|
1656
1745
|
* is static.
|
|
1657
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
|
+
*
|
|
1658
1753
|
* @param notes The line: a motif, or plain note events. Notes that never sound
|
|
1659
|
-
* are dropped.
|
|
1660
|
-
* @returns The directions, the shape, the peak and trough, and a rationale.
|
|
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.
|
|
1661
1758
|
* @example
|
|
1662
1759
|
* ```ts
|
|
1663
1760
|
* import { melodicContour } from '@libraz/libcantus';
|
|
@@ -1672,6 +1769,113 @@ declare function melodicSimilarity(a: MelodicPhrase, b: MelodicPhrase): number;
|
|
|
1672
1769
|
*/
|
|
1673
1770
|
declare function melodicContour(notes: MelodicPhrase): MelodicContour;
|
|
1674
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
|
+
|
|
1675
1879
|
/**
|
|
1676
1880
|
* Harmonic reduction: which chords carry the progression and which decorate it.
|
|
1677
1881
|
*
|
|
@@ -1734,9 +1938,11 @@ type ReducedChord = {
|
|
|
1734
1938
|
* What a reduction treats as the frame.
|
|
1735
1939
|
*
|
|
1736
1940
|
* `'function'` is the functional reading: the tonic, the dominant and the
|
|
1737
|
-
* chords that cadence are the frame. `'duration'` is the salience reading:
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
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.
|
|
1740
1946
|
*
|
|
1741
1947
|
* @category Arrangement & Analysis
|
|
1742
1948
|
*/
|
|
@@ -1801,4 +2007,4 @@ type ReduceProgressionOptions = {
|
|
|
1801
2007
|
*/
|
|
1802
2008
|
declare function reduceProgression(timeline: ChordTimeline, key: KeyContext, opts?: ReduceProgressionOptions): ReducedChord[];
|
|
1803
2009
|
|
|
1804
|
-
export {
|
|
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 };
|