@libraz/libcantus 0.9.1 → 0.9.2
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 +15 -0
- package/dist/analyze/index.cjs +1697 -0
- package/dist/analyze/index.cjs.map +1 -0
- package/dist/analyze/index.d.cts +429 -0
- package/dist/analyze/index.d.ts +429 -0
- package/dist/analyze/index.js +51 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/chunk-5HTNMQW4.js +653 -0
- package/dist/chunk-5HTNMQW4.js.map +1 -0
- package/dist/chunk-7Y7WF64D.js +410 -0
- package/dist/chunk-7Y7WF64D.js.map +1 -0
- package/dist/chunk-A4532ZFR.js +259 -0
- package/dist/chunk-A4532ZFR.js.map +1 -0
- package/dist/chunk-BHUT2S5G.js +22 -0
- package/dist/chunk-BHUT2S5G.js.map +1 -0
- package/dist/chunk-CCBLVAPK.js +42 -0
- package/dist/chunk-CCBLVAPK.js.map +1 -0
- package/dist/chunk-COKTX2G5.js +907 -0
- package/dist/chunk-COKTX2G5.js.map +1 -0
- package/dist/chunk-DYWFNC4A.js +152 -0
- package/dist/chunk-DYWFNC4A.js.map +1 -0
- package/dist/chunk-GQKN5KDT.js +159 -0
- package/dist/chunk-GQKN5KDT.js.map +1 -0
- package/dist/chunk-LN7ZZMD2.js +530 -0
- package/dist/chunk-LN7ZZMD2.js.map +1 -0
- package/dist/chunk-MSWFZF7J.js +118 -0
- package/dist/chunk-MSWFZF7J.js.map +1 -0
- package/dist/chunk-R3S2LUBH.js +2722 -0
- package/dist/chunk-R3S2LUBH.js.map +1 -0
- package/dist/chunk-SDM3VJJ5.js +41 -0
- package/dist/chunk-SDM3VJJ5.js.map +1 -0
- package/dist/chunk-XTC3X6MS.js +816 -0
- package/dist/chunk-XTC3X6MS.js.map +1 -0
- package/dist/chunk-ZXW5YIWK.js +55 -0
- package/dist/chunk-ZXW5YIWK.js.map +1 -0
- package/dist/core/index.cjs +432 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +210 -0
- package/dist/core/index.d.ts +210 -0
- package/dist/core/index.js +73 -0
- package/dist/core/index.js.map +1 -0
- package/dist/function-2bJW1Zp_.d.ts +187 -0
- package/dist/function-r2sh-jOO.d.cts +187 -0
- package/dist/generate/index.cjs +3787 -0
- package/dist/generate/index.cjs.map +1 -0
- package/dist/generate/index.d.cts +980 -0
- package/dist/generate/index.d.ts +980 -0
- package/dist/generate/index.js +52 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index-BMPxwjwV.d.ts +151 -0
- package/dist/index-BOBXBNwP.d.cts +157 -0
- package/dist/index-BdeQ1r_p.d.cts +56 -0
- package/dist/index-BreoJRw0.d.ts +127 -0
- package/dist/index-CGMZNCx6.d.cts +140 -0
- package/dist/index-CGMZNCx6.d.ts +140 -0
- package/dist/index-CM5W4rcm.d.ts +157 -0
- package/dist/index-CP3wAfEH.d.ts +56 -0
- package/dist/index-Cl7mhOFM.d.cts +164 -0
- package/dist/index-Cl7mhOFM.d.ts +164 -0
- package/dist/index-DZ1M21do.d.cts +127 -0
- package/dist/index-D_ZTUNKp.d.cts +151 -0
- package/dist/index.cjs +3016 -2984
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3982
- package/dist/index.d.ts +14 -3982
- package/dist/index.js +170 -6504
- package/dist/index.js.map +1 -1
- package/dist/model/index.cjs +2435 -0
- package/dist/model/index.cjs.map +1 -0
- package/dist/model/index.d.cts +630 -0
- package/dist/model/index.d.ts +630 -0
- package/dist/model/index.js +21 -0
- package/dist/model/index.js.map +1 -0
- package/dist/styled-BjXtOFNk.d.cts +282 -0
- package/dist/styled-D7BAx3S3.d.ts +282 -0
- package/dist/theory/index.cjs +1677 -0
- package/dist/theory/index.cjs.map +1 -0
- package/dist/theory/index.d.cts +535 -0
- package/dist/theory/index.d.ts +535 -0
- package/dist/theory/index.js +149 -0
- package/dist/theory/index.js.map +1 -0
- package/dist/types-MGP29r3t.d.cts +30 -0
- package/dist/types-MGP29r3t.d.ts +30 -0
- package/package.json +27 -2
- package/src/analyze/arrange/index.ts +21 -0
- package/src/analyze/arrange/internal.ts +125 -0
- package/src/analyze/arrange/tension.ts +187 -0
- package/src/analyze/arrange/tracks.ts +279 -0
- package/src/{detect → analyze/detect}/index.ts +4 -4
- package/src/analyze/functional/borrowed.ts +84 -0
- package/src/analyze/functional/cadence.ts +66 -0
- package/src/analyze/functional/function.ts +184 -0
- package/src/analyze/functional/index.ts +23 -0
- package/src/analyze/functional/internal.ts +29 -0
- package/src/{functional/index.ts → analyze/functional/roman.ts} +9 -327
- package/src/analyze/index.ts +49 -0
- package/src/{timeline → analyze/timeline}/index.ts +7 -11
- package/src/{analysis → analyze/voice}/index.ts +8 -5
- package/src/core/index.ts +47 -0
- package/src/{bass → generate/bass}/index.ts +5 -5
- package/src/{countermelody → generate/countermelody}/index.ts +11 -11
- package/src/{drums → generate/drums}/index.ts +2 -2
- package/src/{drums → generate/drums}/rng.ts +2 -2
- package/src/{groove → generate/groove}/index.ts +3 -3
- package/src/{harmonize → generate/harmonize}/index.ts +13 -13
- package/src/generate/index.ts +59 -0
- package/src/{motif → generate/motif}/index.ts +7 -7
- package/src/{progression → generate/progression}/index.ts +11 -25
- package/src/{reharmony → generate/reharmony}/index.ts +11 -11
- package/src/{rhythm → generate/rhythm}/index.ts +2 -2
- package/src/index.ts +130 -117
- package/src/model/chord.ts +423 -0
- package/src/model/index.ts +5 -1096
- package/src/model/interval.ts +82 -0
- package/src/model/key.ts +266 -0
- package/src/model/note.ts +176 -0
- package/src/model/progression.ts +161 -0
- package/src/model/shared.ts +12 -0
- package/src/{chord → theory/chord}/index.ts +19 -1
- package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
- package/src/theory/index.ts +107 -0
- package/src/{safety → theory/safety}/index.ts +1 -1
- package/src/theory/scale/degrees.ts +123 -0
- package/src/theory/scale/index.ts +28 -0
- package/src/theory/scale/key.ts +56 -0
- package/src/theory/scale/masks.ts +153 -0
- package/src/{spelling → theory/spelling}/index.ts +3 -3
- package/src/{symbol → theory/symbol}/index.ts +8 -1
- package/src/theory/voicing/index.ts +5 -0
- package/src/theory/voicing/internal.ts +168 -0
- package/src/theory/voicing/leading.ts +112 -0
- package/src/theory/voicing/satb.ts +195 -0
- package/src/theory/voicing/styled.ts +176 -0
- package/src/arrange/index.ts +0 -571
- package/src/scale/index.ts +0 -331
- package/src/voicing/index.ts +0 -623
- /package/src/{interval → core/interval}/index.ts +0 -0
- /package/src/{meter → core/meter}/index.ts +0 -0
- /package/src/{pitch → core/pitch}/index.ts +0 -0
- /package/src/{random → core/random}/index.ts +0 -0
- /package/src/{tuning → core/tuning}/index.ts +0 -0
- /package/src/{types.ts → core/types.ts} +0 -0
- /package/src/{drums → generate/drums}/beat.ts +0 -0
- /package/src/{drums → generate/drums}/euclid.ts +0 -0
- /package/src/{drums → generate/drums}/fills.ts +0 -0
- /package/src/{drums → generate/drums}/ghost.ts +0 -0
- /package/src/{drums → generate/drums}/hihat.ts +0 -0
- /package/src/{drums → generate/drums}/hit.ts +0 -0
- /package/src/{drums → generate/drums}/internal.ts +0 -0
- /package/src/{drums → generate/drums}/kick.ts +0 -0
- /package/src/{drums → generate/drums}/percussion.ts +0 -0
- /package/src/{drums → generate/drums}/swing.ts +0 -0
- /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
- /package/src/{harmony → theory/harmony}/index.ts +0 -0
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (tonic / subdominant / dominant), and cadence detection.
|
|
2
|
+
* Roman-numeral parsing and formatting.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The {@link ROMAN_STYLE} table is the single source of truth for both
|
|
5
|
+
* directions, so {@link chordToRoman} and {@link romanToChord} stay mutual
|
|
6
|
+
* inverses by construction.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
import type {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
MAJOR_MASK,
|
|
16
|
-
majorKey,
|
|
17
|
-
NATURAL_MINOR_MASK,
|
|
18
|
-
scaleTonesInDegreeOrder,
|
|
19
|
-
} from '../scale/index.js';
|
|
20
|
-
import type { KeyScale } from '../types.js';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The three broad harmonic functions of tonal music.
|
|
24
|
-
*
|
|
25
|
-
* @category Functional Harmony
|
|
26
|
-
*/
|
|
27
|
-
export type HarmonicFunction = 'tonic' | 'subdominant' | 'dominant';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A recognized cadence type, or null when a chord pair forms none.
|
|
31
|
-
*
|
|
32
|
-
* @category Functional Harmony
|
|
33
|
-
*/
|
|
34
|
-
export type Cadence = 'authentic' | 'plagal' | 'half' | 'deceptive' | null;
|
|
9
|
+
import type { KeyScale } from '../../core/types.js';
|
|
10
|
+
import type { Chord, ChordQuality } from '../../theory/chord/index.js';
|
|
11
|
+
import { makeChord } from '../../theory/chord/index.js';
|
|
12
|
+
import { majorKey, scaleTonesInDegreeOrder } from '../../theory/scale/index.js';
|
|
13
|
+
import { degreeRootPc, mod12 } from './internal.js';
|
|
35
14
|
|
|
36
15
|
/** Roman numeral glyphs indexed by degree number - 1. */
|
|
37
16
|
const ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] as const;
|
|
@@ -63,46 +42,6 @@ const OFFSET_SPELLING: readonly [number, string][] = [
|
|
|
63
42
|
[7, ''],
|
|
64
43
|
];
|
|
65
44
|
|
|
66
|
-
/** Harmonic function of each semitone offset above the tonic (major context). */
|
|
67
|
-
const FUNCTION_BY_OFFSET: readonly HarmonicFunction[] = [
|
|
68
|
-
'tonic', // 0 I
|
|
69
|
-
'subdominant', // 1 bII (Neapolitan)
|
|
70
|
-
'subdominant', // 2 ii
|
|
71
|
-
'tonic', // 3 bIII
|
|
72
|
-
'tonic', // 4 iii
|
|
73
|
-
'subdominant', // 5 IV
|
|
74
|
-
'dominant', // 6 #IV / bV
|
|
75
|
-
'dominant', // 7 V
|
|
76
|
-
'subdominant', // 8 bVI
|
|
77
|
-
'tonic', // 9 vi
|
|
78
|
-
'subdominant', // 10 bVII
|
|
79
|
-
'dominant', // 11 vii
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
function mod12(n: number): number {
|
|
83
|
-
return ((n % 12) + 12) % 12;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Whether a key's scale has a minor third and no major third (a minor key).
|
|
88
|
-
*
|
|
89
|
-
* @category Functional Harmony
|
|
90
|
-
*/
|
|
91
|
-
export function isMinorKey(key: KeyScale): boolean {
|
|
92
|
-
const hasMinorThird = (key.modeMask12 >> 3) & 1;
|
|
93
|
-
const hasMajorThird = (key.modeMask12 >> 4) & 1;
|
|
94
|
-
return Boolean(hasMinorThird) && !hasMajorThird;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Diatonic pitch class of a 1-based scale degree in a key. */
|
|
98
|
-
function degreeRootPc(degreeNumber: number, key: KeyScale): number {
|
|
99
|
-
const tones = scaleTonesInDegreeOrder(key);
|
|
100
|
-
if (tones.length === 0) {
|
|
101
|
-
return mod12(key.rootPc);
|
|
102
|
-
}
|
|
103
|
-
return tones[(degreeNumber - 1) % tones.length] ?? mod12(key.rootPc);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
45
|
/** Choose a seventh-chord quality from a triad label and the numeral case. */
|
|
107
46
|
function seventhQuality(base: 'maj' | 'min' | 'dim' | 'aug', halfDim: boolean): ChordQuality {
|
|
108
47
|
if (halfDim) {
|
|
@@ -452,260 +391,3 @@ export function chordToRoman(chord: Chord, key: KeyScale): string {
|
|
|
452
391
|
}
|
|
453
392
|
return `${accidental}${cased}${suffix}`;
|
|
454
393
|
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* The harmonic function of a chord in a key, from its root's offset above the
|
|
458
|
-
* tonic. The mapping follows common-practice major-key function and is a useful
|
|
459
|
-
* approximation in minor and for borrowed chords.
|
|
460
|
-
*
|
|
461
|
-
* @param chord The chord.
|
|
462
|
-
* @param key The prevailing key.
|
|
463
|
-
* @returns The harmonic function.
|
|
464
|
-
* @category Functional Harmony
|
|
465
|
-
*/
|
|
466
|
-
export function functionOf(chord: Chord, key: KeyScale): HarmonicFunction {
|
|
467
|
-
return FUNCTION_BY_OFFSET[mod12(chord.rootPc - key.rootPc)] ?? 'tonic';
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* The origin of a recognized non-diatonic chord, or null when none applies.
|
|
472
|
-
*
|
|
473
|
-
* @category Functional Harmony
|
|
474
|
-
*/
|
|
475
|
-
export type BorrowedSource = 'parallel-minor' | 'parallel-major' | 'neapolitan' | null;
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* The result of {@link analyzeChord}: function, borrowing, and Roman numeral.
|
|
479
|
-
*
|
|
480
|
-
* @category Functional Harmony
|
|
481
|
-
*/
|
|
482
|
-
export type ChordAnalysis = {
|
|
483
|
-
function: HarmonicFunction;
|
|
484
|
-
borrowed: boolean;
|
|
485
|
-
source: BorrowedSource;
|
|
486
|
-
roman: string;
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Whether every pitch class of a chord belongs to the key's scale.
|
|
491
|
-
*
|
|
492
|
-
* The test is strict against the key's own mode mask: in a natural-minor key
|
|
493
|
-
* the harmonic-minor dominant (major V) is *not* diatonic, since the raised
|
|
494
|
-
* leading tone lies outside the mask. Borrowing predicates treat that case as
|
|
495
|
-
* an in-key alteration separately (see {@link isBorrowedChord}).
|
|
496
|
-
*
|
|
497
|
-
* @param chord The chord to test.
|
|
498
|
-
* @param key The prevailing key.
|
|
499
|
-
* @returns True if all chord pitch classes are scale tones.
|
|
500
|
-
* @category Functional Harmony
|
|
501
|
-
*/
|
|
502
|
-
export function isDiatonic(chord: Chord, key: KeyScale): boolean {
|
|
503
|
-
return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* The parallel key: same tonic, opposite mode.
|
|
508
|
-
*
|
|
509
|
-
* A key with a minor third (natural/harmonic/melodic minor, dorian, phrygian)
|
|
510
|
-
* maps to the parallel major; any other key maps to the parallel natural minor.
|
|
511
|
-
*
|
|
512
|
-
* @param key The key to mirror.
|
|
513
|
-
* @returns The parallel major or natural-minor key on the same tonic.
|
|
514
|
-
* @category Functional Harmony
|
|
515
|
-
*/
|
|
516
|
-
export function parallelKey(key: KeyScale): KeyScale {
|
|
517
|
-
return {
|
|
518
|
-
rootPc: mod12(key.rootPc),
|
|
519
|
-
modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* In a minor key, whether a non-diatonic chord is explained by the harmonic
|
|
525
|
-
* minor scale on the same tonic (major V, V7, the raised-leading-tone viio).
|
|
526
|
-
* Such chords are in-key chromatic alterations, not modal interchange.
|
|
527
|
-
*/
|
|
528
|
-
function isHarmonicMinorAlteration(chord: Chord, key: KeyScale): boolean {
|
|
529
|
-
return (
|
|
530
|
-
isMinorKey(key) &&
|
|
531
|
-
isDiatonic(chord, { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK })
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/** Whether a chord is the Neapolitan: a major triad on the flat second degree. */
|
|
536
|
-
function isNeapolitan(chord: Chord, key: KeyScale): boolean {
|
|
537
|
-
return mod12(chord.rootPc - key.rootPc) === 1 && chord.quality === 'maj';
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Whether a chord is borrowed from the parallel mode (modal interchange).
|
|
542
|
-
*
|
|
543
|
-
* True when the chord is not diatonic to `key` but is diatonic to its
|
|
544
|
-
* {@link parallelKey} — e.g. iv, bVI, or bVII in a major key, or the Picardy
|
|
545
|
-
* tonic and major IV in a minor key. Two non-diatonic families are excluded:
|
|
546
|
-
* chords diatonic to neither mode (they are chromatic, not borrowed), and, in
|
|
547
|
-
* a minor key, chords explained by the harmonic minor scale (the major
|
|
548
|
-
* dominant and raised-leading-tone chords), which are in-key alterations
|
|
549
|
-
* rather than interchange even though they happen to fit the parallel major.
|
|
550
|
-
*
|
|
551
|
-
* @param chord The chord to test.
|
|
552
|
-
* @param key The prevailing key.
|
|
553
|
-
* @returns True if the chord is borrowed from the parallel mode.
|
|
554
|
-
* @category Functional Harmony
|
|
555
|
-
*/
|
|
556
|
-
export function isBorrowedChord(chord: Chord, key: KeyScale): boolean {
|
|
557
|
-
if (isDiatonic(chord, key)) {
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
if (isHarmonicMinorAlteration(chord, key)) {
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
return isDiatonic(chord, parallelKey(key));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Identify where a non-diatonic chord comes from.
|
|
568
|
-
*
|
|
569
|
-
* The Neapolitan (major triad on b2) is recognized first, since it sits
|
|
570
|
-
* outside both parallel modes; parallel-mode borrowing follows the
|
|
571
|
-
* {@link isBorrowedChord} rules. Diatonic chords, harmonic-minor alterations,
|
|
572
|
-
* and unrecognized chromatic chords all yield null.
|
|
573
|
-
*
|
|
574
|
-
* @param chord The chord to classify.
|
|
575
|
-
* @param key The prevailing key.
|
|
576
|
-
* @returns The borrowing source, or null.
|
|
577
|
-
* @category Functional Harmony
|
|
578
|
-
*/
|
|
579
|
-
export function borrowedSource(chord: Chord, key: KeyScale): BorrowedSource {
|
|
580
|
-
if (isDiatonic(chord, key)) {
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
if (isNeapolitan(chord, key)) {
|
|
584
|
-
return 'neapolitan';
|
|
585
|
-
}
|
|
586
|
-
if (isBorrowedChord(chord, key)) {
|
|
587
|
-
return isMinorKey(key) ? 'parallel-major' : 'parallel-minor';
|
|
588
|
-
}
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/** Diminished-family qualities: diminished triad, dim7, half-diminished. */
|
|
593
|
-
function isDiminishedQuality(quality: ChordQuality): boolean {
|
|
594
|
-
return quality === 'dim' || quality === 'dim7' || quality === 'm7b5';
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/** Whether the chord's interval template carries a major third above the root. */
|
|
598
|
-
function hasMajorThird(chord: Chord): boolean {
|
|
599
|
-
return chord.intervals.some((interval) => mod12(interval) === 4);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* Quality-aware harmonic function, refining the offset table where chord
|
|
604
|
-
* quality disambiguates: diminished chords on the leading tone or the raised
|
|
605
|
-
* subdominant resolve by semitone and act as dominants; the Neapolitan and the
|
|
606
|
-
* major-third chords on bVI/bVII borrowed from the parallel minor act as
|
|
607
|
-
* subdominant (predominant) harmony. All other chords keep {@link functionOf}.
|
|
608
|
-
*/
|
|
609
|
-
function qualityAwareFunction(chord: Chord, key: KeyScale): HarmonicFunction {
|
|
610
|
-
const offset = mod12(chord.rootPc - key.rootPc);
|
|
611
|
-
if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {
|
|
612
|
-
return 'dominant';
|
|
613
|
-
}
|
|
614
|
-
if (isNeapolitan(chord, key)) {
|
|
615
|
-
return 'subdominant';
|
|
616
|
-
}
|
|
617
|
-
if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
|
|
618
|
-
return 'subdominant';
|
|
619
|
-
}
|
|
620
|
-
return functionOf(chord, key);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
/**
|
|
624
|
-
* Analyze a chord in a key: harmonic function, borrowing, and Roman numeral.
|
|
625
|
-
*
|
|
626
|
-
* The function is quality-aware (see the predicates behind it), the source
|
|
627
|
-
* follows {@link borrowedSource}, and the numeral comes from
|
|
628
|
-
* {@link chordToRoman}. `borrowed` is true whenever a source is identified —
|
|
629
|
-
* including the Neapolitan, which the stricter parallel-mode predicate
|
|
630
|
-
* {@link isBorrowedChord} does not count.
|
|
631
|
-
*
|
|
632
|
-
* @param chord The chord to analyze.
|
|
633
|
-
* @param key The prevailing key.
|
|
634
|
-
* @returns The chord analysis.
|
|
635
|
-
* @example
|
|
636
|
-
* ```ts
|
|
637
|
-
* import { analyzeChord, makeChord, majorKey } from '@libraz/libcantus';
|
|
638
|
-
* analyzeChord(makeChord(7, 'dom7'), majorKey(0));
|
|
639
|
-
* // { function: 'dominant', borrowed: false, source: null, roman: 'V7' }
|
|
640
|
-
* ```
|
|
641
|
-
* @category Functional Harmony
|
|
642
|
-
*/
|
|
643
|
-
export function analyzeChord(chord: Chord, key: KeyScale): ChordAnalysis {
|
|
644
|
-
const source = borrowedSource(chord, key);
|
|
645
|
-
return {
|
|
646
|
-
function: qualityAwareFunction(chord, key),
|
|
647
|
-
borrowed: source !== null,
|
|
648
|
-
source,
|
|
649
|
-
roman: chordToRoman(chord, key),
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Classify the cadence formed by moving from one chord to the next.
|
|
655
|
-
*
|
|
656
|
-
* - authentic: V (dominant a fifth above the tonic) to I
|
|
657
|
-
* - plagal: IV (a fourth above the tonic) to I
|
|
658
|
-
* - deceptive: V to the submediant (diatonic vi or borrowed bVI in major, VI in
|
|
659
|
-
* minor)
|
|
660
|
-
* - half: any chord other than the dominant itself to V
|
|
661
|
-
*
|
|
662
|
-
* A static V-to-V repeat with no root motion is not a cadence and yields null.
|
|
663
|
-
*
|
|
664
|
-
* @param from The penultimate chord.
|
|
665
|
-
* @param to The final chord.
|
|
666
|
-
* @param key The prevailing key.
|
|
667
|
-
* @returns The cadence type, or null.
|
|
668
|
-
* @example
|
|
669
|
-
* ```ts
|
|
670
|
-
* import { detectCadence, makeChord, majorKey } from '@libraz/libcantus';
|
|
671
|
-
* detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0)); // => 'authentic'
|
|
672
|
-
* ```
|
|
673
|
-
* @category Functional Harmony
|
|
674
|
-
*/
|
|
675
|
-
export function detectCadence(from: Chord, to: Chord, key: KeyScale): Cadence {
|
|
676
|
-
const tonic = mod12(key.rootPc);
|
|
677
|
-
const fromOffset = mod12(from.rootPc - tonic);
|
|
678
|
-
const toOffset = mod12(to.rootPc - tonic);
|
|
679
|
-
// Deceptive targets: the diatonic submediant (vi at offset 9 in major, VI at
|
|
680
|
-
// offset 8 in minor) plus, in a major key, the borrowed flat-submediant bVI
|
|
681
|
-
// at offset 8.
|
|
682
|
-
const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];
|
|
683
|
-
if (fromOffset === 7 && toOffset === 0) {
|
|
684
|
-
return 'authentic';
|
|
685
|
-
}
|
|
686
|
-
if (fromOffset === 5 && toOffset === 0) {
|
|
687
|
-
return 'plagal';
|
|
688
|
-
}
|
|
689
|
-
if (fromOffset === 7 && deceptiveTargets.includes(toOffset)) {
|
|
690
|
-
return 'deceptive';
|
|
691
|
-
}
|
|
692
|
-
// A move to the dominant is a half cadence, but a no-root-motion V-to-V repeat
|
|
693
|
-
// is not a cadence at all.
|
|
694
|
-
if (toOffset === 7 && fromOffset !== 7) {
|
|
695
|
-
return 'half';
|
|
696
|
-
}
|
|
697
|
-
return null;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* The secondary dominant (V7) that tonicizes a scale degree.
|
|
702
|
-
*
|
|
703
|
-
* @param targetDegree 0-based scale degree to tonicize.
|
|
704
|
-
* @param key The prevailing key.
|
|
705
|
-
* @returns A dominant-seventh chord a fifth above the target's root.
|
|
706
|
-
* @category Functional Harmony
|
|
707
|
-
*/
|
|
708
|
-
export function secondaryDominant(targetDegree: number, key: KeyScale): Chord {
|
|
709
|
-
const targetRoot = degreeRootPc(targetDegree + 1, key);
|
|
710
|
-
return makeChord(mod12(targetRoot + 7), 'dom7');
|
|
711
|
-
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of the analyze layer. Re-exports every analyze-layer module;
|
|
3
|
+
* also available from the package root.
|
|
4
|
+
*/
|
|
5
|
+
export type {
|
|
6
|
+
ArrangementAnalysis,
|
|
7
|
+
ArrangementOptions,
|
|
8
|
+
ArrangementTrack,
|
|
9
|
+
Conflict,
|
|
10
|
+
TensionPoint,
|
|
11
|
+
TrackAnalysis,
|
|
12
|
+
TrackRole,
|
|
13
|
+
} from './arrange/index.js';
|
|
14
|
+
export { analyzeArrangement, tensionCurve } from './arrange/index.js';
|
|
15
|
+
export type { ChordMatch, KeyMatch } from './detect/index.js';
|
|
16
|
+
export { detectChord, detectChordBest, detectKey } from './detect/index.js';
|
|
17
|
+
export type {
|
|
18
|
+
BorrowedSource,
|
|
19
|
+
Cadence,
|
|
20
|
+
ChordAnalysis,
|
|
21
|
+
HarmonicFunction,
|
|
22
|
+
} from './functional/index.js';
|
|
23
|
+
export {
|
|
24
|
+
analyzeChord,
|
|
25
|
+
borrowedSource,
|
|
26
|
+
chordToRoman,
|
|
27
|
+
detectCadence,
|
|
28
|
+
functionOf,
|
|
29
|
+
isBorrowedChord,
|
|
30
|
+
isDiatonic,
|
|
31
|
+
isMinorKey,
|
|
32
|
+
parallelKey,
|
|
33
|
+
romanToChord,
|
|
34
|
+
secondaryDominant,
|
|
35
|
+
} from './functional/index.js';
|
|
36
|
+
export type {
|
|
37
|
+
CadenceHit,
|
|
38
|
+
ChordSegment,
|
|
39
|
+
ChordTimeline,
|
|
40
|
+
ChordTimelineOptions,
|
|
41
|
+
ChordTimelineResult,
|
|
42
|
+
} from './timeline/index.js';
|
|
43
|
+
export {
|
|
44
|
+
chordTimelineFromChords,
|
|
45
|
+
chordTimelineFromNotes,
|
|
46
|
+
detectCadences,
|
|
47
|
+
} from './timeline/index.js';
|
|
48
|
+
export type { AnalyzedNote, TheoryLabel, VoiceNote } from './voice/index.js';
|
|
49
|
+
export { analyzeVoice } from './voice/index.js';
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { TimeSignature } from '../../core/meter/index.js';
|
|
2
|
+
import { beatsPerBar, metricWeight, parseTimeSignature } from '../../core/meter/index.js';
|
|
3
|
+
import type { KeyScale, NoteEvent } from '../../core/types.js';
|
|
4
|
+
import type { Chord, ChordSpan } from '../../theory/chord/index.js';
|
|
5
|
+
import { chordPitchClasses, makeChord } from '../../theory/chord/index.js';
|
|
6
|
+
import { isScaleTone, majorKey } from '../../theory/scale/index.js';
|
|
3
7
|
import type { ChordMatch } from '../detect/index.js';
|
|
4
8
|
import { detectChord, detectChordBest, detectKey } from '../detect/index.js';
|
|
5
9
|
import type { Cadence } from '../functional/index.js';
|
|
6
10
|
import { detectCadence } from '../functional/index.js';
|
|
7
|
-
import type { TimeSignature } from '../meter/index.js';
|
|
8
|
-
import { beatsPerBar, metricWeight, parseTimeSignature } from '../meter/index.js';
|
|
9
|
-
import type { GeneratedChord } from '../progression/index.js';
|
|
10
|
-
import { isScaleTone, majorKey } from '../scale/index.js';
|
|
11
|
-
import type { KeyScale, NoteEvent } from '../types.js';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* A chord occupying a half-open beat span.
|
|
@@ -45,10 +44,7 @@ export type ChordTimeline = {
|
|
|
45
44
|
* @returns A queryable chord timeline.
|
|
46
45
|
* @category Arrangement & Analysis
|
|
47
46
|
*/
|
|
48
|
-
export function chordTimelineFromChords(
|
|
49
|
-
chords: GeneratedChord[],
|
|
50
|
-
totalBeats: number,
|
|
51
|
-
): ChordTimeline {
|
|
47
|
+
export function chordTimelineFromChords(chords: ChordSpan[], totalBeats: number): ChordTimeline {
|
|
52
48
|
const sorted = [...chords].sort((a, b) => a.startBeat - b.startBeat);
|
|
53
49
|
const segments: ChordSegment[] = sorted
|
|
54
50
|
.map((gc, i) => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
1
|
+
import type { KeyScale, NoteEvent } from '../../core/types.js';
|
|
2
|
+
import type { Chord } from '../../theory/chord/index.js';
|
|
3
|
+
import { chordPitchClasses, chordToneRole } from '../../theory/chord/index.js';
|
|
4
|
+
import {
|
|
5
|
+
createsVerticalDissonance,
|
|
6
|
+
isLeadingToneResolution,
|
|
7
|
+
} from '../../theory/counterpoint/index.js';
|
|
8
|
+
import type { VoiceSnapshot } from '../../theory/safety/index.js';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* A theory annotation attached to a note.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API of the core layer. Re-exports every core-layer module;
|
|
3
|
+
* also available from the package root.
|
|
4
|
+
*/
|
|
5
|
+
export {
|
|
6
|
+
classifyInterval,
|
|
7
|
+
IntervalQuality,
|
|
8
|
+
isConsonantInterval,
|
|
9
|
+
isPerfectInterval,
|
|
10
|
+
} from './interval/index.js';
|
|
11
|
+
export type { BarPosition, TimeSignature } from './meter/index.js';
|
|
12
|
+
export {
|
|
13
|
+
barPositionToBeat,
|
|
14
|
+
beatsPerBar,
|
|
15
|
+
beatToBarPosition,
|
|
16
|
+
formatTimeSignature,
|
|
17
|
+
isCompound,
|
|
18
|
+
isStrongBeat,
|
|
19
|
+
metricWeight,
|
|
20
|
+
parseTimeSignature,
|
|
21
|
+
pulsesPerBar,
|
|
22
|
+
tuplet,
|
|
23
|
+
} from './meter/index.js';
|
|
24
|
+
export type { Note as NoteData, SpelledInterval } from './pitch/index.js';
|
|
25
|
+
export {
|
|
26
|
+
formatNote,
|
|
27
|
+
midiToNote,
|
|
28
|
+
noteToMidi,
|
|
29
|
+
noteToPitchClass,
|
|
30
|
+
parseNote,
|
|
31
|
+
spelledInterval,
|
|
32
|
+
} from './pitch/index.js';
|
|
33
|
+
export type { Rng } from './random/index.js';
|
|
34
|
+
export { createRng } from './random/index.js';
|
|
35
|
+
export type { Tuning } from './tuning/index.js';
|
|
36
|
+
export {
|
|
37
|
+
centsBetweenFreq,
|
|
38
|
+
centsOfSteps,
|
|
39
|
+
edo,
|
|
40
|
+
frequencyOf,
|
|
41
|
+
JUST_RATIOS,
|
|
42
|
+
justDeviationCents,
|
|
43
|
+
nearestStep,
|
|
44
|
+
ratioToCents,
|
|
45
|
+
TWELVE_TET,
|
|
46
|
+
} from './tuning/index.js';
|
|
47
|
+
export type { KeyScale, NoteEvent } from './types.js';
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* library-wide convention.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
15
|
+
import { isStrongBeat, type TimeSignature } from '../../core/meter/index.js';
|
|
16
|
+
import { createRng } from '../../core/random/index.js';
|
|
17
|
+
import type { KeyScale, NoteEvent } from '../../core/types.js';
|
|
18
|
+
import type { Chord } from '../../theory/chord/index.js';
|
|
19
|
+
import { nearestScaleTone } from '../../theory/scale/index.js';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* A chord sounding over a half-open beat span `[startBeat, endBeat)`.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
1
|
+
import type { TimeSignature } from '../../core/meter/index.js';
|
|
2
|
+
import { isStrongBeat } from '../../core/meter/index.js';
|
|
3
|
+
import type { Rng } from '../../core/random/index.js';
|
|
4
|
+
import { createRng } from '../../core/random/index.js';
|
|
5
|
+
import type { KeyScale, NoteEvent } from '../../core/types.js';
|
|
6
|
+
import type { Chord } from '../../theory/chord/index.js';
|
|
7
|
+
import { chordToneRole } from '../../theory/chord/index.js';
|
|
8
|
+
import { createsParallelPerfect, isForbiddenMelodicLeap } from '../../theory/counterpoint/index.js';
|
|
9
|
+
import type { VoiceSnapshot } from '../../theory/safety/index.js';
|
|
10
|
+
import { enumerateSafePitches } from '../../theory/safety/index.js';
|
|
11
|
+
import { nearestScaleTone } from '../../theory/scale/index.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Options controlling {@link generateCounterMelody}.
|
|
@@ -99,7 +99,7 @@ export type EuclideanKick = {
|
|
|
99
99
|
*
|
|
100
100
|
* @category Composition
|
|
101
101
|
*/
|
|
102
|
-
export type
|
|
102
|
+
export type DrumsOptions = {
|
|
103
103
|
bars: number;
|
|
104
104
|
bpm: number;
|
|
105
105
|
style: GrooveStyle;
|
|
@@ -163,7 +163,7 @@ export type DrumGenOptions = {
|
|
|
163
163
|
*
|
|
164
164
|
* @category Composition
|
|
165
165
|
*/
|
|
166
|
-
export function generateDrums(opts:
|
|
166
|
+
export function generateDrums(opts: DrumsOptions): DrumHit[] {
|
|
167
167
|
const track = new HitList();
|
|
168
168
|
const rng = createRng(opts.seed ?? 0);
|
|
169
169
|
const mapping = mapStyle(opts.style);
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* `DrumRng` is retained as the drum-facing name of the shared {@link Rng} type.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export type { Rng as DrumRng } from '
|
|
7
|
-
export { createRng } from '
|
|
6
|
+
export type { Rng as DrumRng } from '../../core/random/index.js';
|
|
7
|
+
export { createRng } from '../../core/random/index.js';
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
formatTimeSignature,
|
|
14
14
|
metricWeight,
|
|
15
15
|
type TimeSignature,
|
|
16
|
-
} from '
|
|
17
|
-
import { createRng } from '
|
|
18
|
-
import type { NoteEvent } from '
|
|
16
|
+
} from '../../core/meter/index.js';
|
|
17
|
+
import { createRng } from '../../core/random/index.js';
|
|
18
|
+
import type { NoteEvent } from '../../core/types.js';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Options controlling {@link humanize}.
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
1
|
+
import type { TimeSignature } from '../../core/meter/index.js';
|
|
2
|
+
import { isStrongBeat } from '../../core/meter/index.js';
|
|
3
|
+
import type { KeyScale, NoteEvent } from '../../core/types.js';
|
|
4
|
+
import type { Chord, ChordQuality } from '../../theory/chord/index.js';
|
|
5
|
+
import { chordPitchClasses, diatonicTriad, makeChord } from '../../theory/chord/index.js';
|
|
6
|
+
import type { HarmonyRole } from '../../theory/harmony/index.js';
|
|
7
|
+
import { roleOf } from '../../theory/harmony/index.js';
|
|
8
8
|
import {
|
|
9
9
|
isScaleTone,
|
|
10
10
|
majorKey,
|
|
11
11
|
minorKey,
|
|
12
12
|
NATURAL_MINOR_MASK,
|
|
13
13
|
scaleTonesInDegreeOrder,
|
|
14
|
-
} from '
|
|
15
|
-
import type {
|
|
14
|
+
} from '../../theory/scale/index.js';
|
|
15
|
+
import type { ChordSpan } from '../progression/index.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* A melody note supplied to the harmonizer: a {@link NoteEvent}.
|
|
@@ -48,7 +48,7 @@ export type HarmonizeOptions = {
|
|
|
48
48
|
export type HarmonizeResult = {
|
|
49
49
|
transposeSemitones: number;
|
|
50
50
|
key: KeyScale;
|
|
51
|
-
chords:
|
|
51
|
+
chords: ChordSpan[];
|
|
52
52
|
melodyRoles: { noteIndex: number; role: HarmonyRole }[];
|
|
53
53
|
};
|
|
54
54
|
|
|
@@ -356,7 +356,7 @@ function harmonizeOnce(
|
|
|
356
356
|
* reharmonize: 'diatonic',
|
|
357
357
|
* placement: { transposeSearch: false, octaveSearch: false },
|
|
358
358
|
* });
|
|
359
|
-
* result.chords; // one
|
|
359
|
+
* result.chords; // one ChordSpan per harmonic-rhythm segment
|
|
360
360
|
* ```
|
|
361
361
|
* @category Reharmonization
|
|
362
362
|
*/
|
|
@@ -413,9 +413,9 @@ export function harmonizeMelody(opts: HarmonizeOptions): HarmonizeResult {
|
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
const chords:
|
|
416
|
+
const chords: ChordSpan[] = bestPath.map((ci, s) => {
|
|
417
417
|
const cand = candAt(ci);
|
|
418
|
-
const chord:
|
|
418
|
+
const chord: ChordSpan = {
|
|
419
419
|
rootPc: cand.rootPc,
|
|
420
420
|
quality: cand.quality,
|
|
421
421
|
startBeat: segments[s]?.startBeat ?? s * hr,
|